[
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "# Contributing\n\n- Please, please, please, make multiple PRs if you have many features/fixes, and don't shove your personal changes along with the PR, including changed defaults\n- We can accept features that we do not personally want, but in that case we will ask you to make it configurable/optionally loaded.\n- If you want to start working on something _big_ to contribute, it might be a good idea to ask first to not waste your effort (but if you've already done it for yourself, it doesn't hurt to submit).\n\n# Translations\n\nSee `dots/.config/quickshell/ii/translations/tools`\n\n# Code\n\n## Dynamic loading\n\n- If something's not always necessary, especially when guarded by a config option to enable/disable, put it in a `Loader`\n  - Note that you will need to declare positioning properties (like `anchors`) in the `Loader`, not the `sourceComponent`\n  - When something that's to be dynamically loaded doesn't affect its parent layout, you can have a fading animation by using FadeLoader and set the `shown` prop instead of `active` and `visible`\n\n## Practical concerns\n\n- Make sure what you add does not require significant resources for a minor purpose or harm usability just for the sake of looking nice. The dotfiles must remain practical for daily driving.\n- If there is something really fancy and impractical anyway, add a config option for it and make sure it's disabled by default (example: constantly rotating background clock)\n\n## Style\n\n- Spaces\n  - Space properties and children data into meaningful groups. (but of course, don't use 2+ blanks in a row)\n  - Put spaces between text and operators: `if (condition) { ... } else { ... }` instead of `if(condition){ ... }else{ ... }`\n- As you can see, it's pretty easy to use lots of nesting. There's no hard limit, end-4 himself nests a lot too, but avoid/mitigate that:\n  - Prefer early return: Use something like `if (!condition) return; doStuff();` instead of `if (condition) { doStuff() }`\n  - If you feel it's a bother to refractor something into a new file, remember there's `component` to declare reusable components in the same file.\n\n# Setting up\n\nThe following instruction assumes that you have an Arch(-based) Linux system.\n\n## Complete\n\n_Might not be necessary depending on what you change, but this is recommended._\n\n- [Install](https://ii.clsty.link/en/ii-qs/01setup/) the dotfiles (if you don't wanna replace your stuff completely, do it on a new user).\n- Make changes, copy changes to a fork, create PR.\n\n## Partially working shell\n\n_Most stuff in the shell will work but not everything._\n\n- Install Hyprland and the development version of Quickshell (`yay -S hyprland quickshell-git`).\n- Copy `dots/.config/quickshell` folder to your home directory.\n\n## Extra setup for Quickshell\n- Quickshell-specific LSP setup: Run `touch ~/.config/quickshell/ii/.qmlls.ini` for proper LSP support.\n- Hint for VSCode: Get the official \"Qt Qml\" extension, go to its settings and change custom exe path to `/usr/bin/qmlls6`.\n\n## Python\nIf your changes involves using python package or script, please use the virtual environment created by uv as described in `sdata/uv/README.md`.\n\n# Running\n\n- Launch Hyprland (not the \"uwsm-managed\" one)\n- For the shell:\n  - Open `~/.config/quickshell/ii` in your code editor.\n  - In a terminal run `pkill qs; qs -c ii` to start the shell in the terminal (for logs).\n  - Make edits in the opened folder. Changes are reloaded live.\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "# These are supported funding model platforms\n\ngithub: end-4 # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]\npatreon: # Replace with a single Patreon username\nopen_collective: # Replace with a single Open Collective username\nko_fi: # Replace with a single Ko-fi username\ntidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel\ncommunity_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry\nliberapay: # Replace with a single Liberapay username\nissuehunt: # Replace with a single IssueHunt username\nlfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry\npolar: # Replace with a single Polar username\nbuy_me_a_coffee: # Replace with a single Buy Me a Coffee username\nthanks_dev: # Replace with a single thanks.dev username\ncustom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-issue.yml",
    "content": "name: Issue\ndescription: for reporting any issue\nlabels: [\"ISSUE\"]\nbody:\n  - type: markdown\n    attributes:\n      value: \"**Welcome to submit a new issue!**\\n- Please search in [existing issues](https://github.com/end-4/dots-hyprland/issues?q=is%3Aissue) before continue.\\n- It takes only 3 steps, so please be patient :)\\n- NOTE 1: If your issue is not a feature request, and it does not fit into the following form, for example \\\"how can I edit some widget\\\", please use [Discussions](https://github.com/end-4/dots-hyprland/discussions) instead.\\n- NOTE 2: If your problem is distro specific and you do not use Arch(-based) distros, please submit [Discussion at Extra Distros](https://github.com/end-4/dots-hyprland/discussions/new?category=extra-distros) instead.\"\n  - type: checkboxes\n    attributes:\n      label: \"Step 1. Before you submit\"\n      description: \"Hint: The 2nd and 3rd checkbox is **not** forcely required as you may have failed to do so.\"\n      options:\n        - label: I have read the [Troubleshooting](https://ii.clsty.link/en/ii-qs/04troubleshooting/) and [Usage](https://ii.clsty.link/en/ii-qs/02usage/) pages.\n          required: true\n        - label: I've successfully updated to the latest version following the [guidance](https://ii.clsty.link/en/ii-qs/01setup/#updating).\n          required: false # Not required cuz user may have failed to do so\n        - label: I've successfully updated the system packages to the latest.\n          required: false # Not required cuz user may have failed to do so\n        - label: I've ticked the checkboxes without reading their contents\n          required: false # Obviously\n  # TODO: Use GitHub Action to auto add folding tag if the log contains more than 15 lines, instead of tell user to \"paste here\" cuz many users actually does not know its meaning (It's also not convenient anyway).\n  - type: textarea\n    attributes:\n      label: \"Step 2. Quick diagnose info\"\n      description: \"Run `./diagnose` inside the repo, and paste the result (which is also saved as file `./diagnose.result`) below.\"\n      value: \"<details><summary>Quick diagnose</summary>\\n\\n```\\n<!-- Run `./diagnose` inside the repo, and paste the result here! -->\\n```\\n\\n</details>\"\n    validations:\n      required: true\n\n  - type: markdown\n    attributes:\n      value: |\n        **Tips for the following Step 3**\n        1. Use `LANG=C LC_ALL=C` to get the output of a command in English, eg. `LANG=C LC_ALL=C date` displays time in English.\n        2. If it throws errors, **PLEASE**, attach logs and describe in detail if possible.\n           - Bar and widgets not showing? run `pkill qs; qs -c ii` for logs.\n           - Installation failed? Run installation again for logs.\n           - You may use more code blocks when needed.\n        3. In case you are confused, the `<details>`, `<summary>`, `</summary>`, `</details>` are HTML tags for folding the logs (typically very long) inside. Please do not touch them (unless you know what you are doing).\n        4. If the logs are suuuuuuper long, consider using an online pastebin service instead.\n\n  - type: textarea\n    attributes:\n      label: \"Step 3. Describe the issue\"\n      value: \"\\n<!-- Firsly describe your issue here! -->\\n\\n<details><summary>Logs</summary>\\n\\n```\\n<!-- Put your log content here!-->\\n```\\n\\n</details>\"\n    validations:\n      required: true\n\n  - type: checkboxes\n    attributes:\n      label: Reminder\n      options:\n        - label: I agree that it's usually impossible for others to help me without my logs.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/2-feature_request.yml",
    "content": "name: Feature request\ndescription: Suggest an idea for this project\nlabels: [\"FEATURE\"]\nbody:\n  - type: markdown\n    attributes:\n      value: \"NOTE:\\n- Please search in [existing issues](https://github.com/end-4/dots-hyprland/issues?q=is%3Aissue) before continue.\\n- Please write in **English**.\"\n\n  - type: textarea\n    attributes:\n      label: \"What would you like to be added?\"\n      description: \"Can be a suggestion for an existing feature. You can suggest a widget, minor user interaction changes.. whatever.\"\n\n  - type: textarea\n    attributes:\n      label: \"How will it help?\"\n      description: \"It's helpful to include examples (like in your use case).\"\n\n  - type: textarea\n    attributes:\n      label: \"Extra info\"\n      description: \"If you want a new widget, a pic of the inspiration (if available) would be awesome.\"\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/README.md",
    "content": "<div align=\"center\">\n    <h1>【 end_4's Hyprland dotfiles 】</h1>\n    <h3></h3>\n</div>\n\n<div align=\"center\"> \n\n![](https://img.shields.io/github/last-commit/end-4/dots-hyprland?&style=for-the-badge&color=8ad7eb&logo=git&logoColor=D9E0EE&labelColor=1E202B)\n![](https://img.shields.io/github/stars/end-4/dots-hyprland?style=for-the-badge&logo=andela&color=86dbd7&logoColor=D9E0EE&labelColor=1E202B)\n![](https://img.shields.io/github/repo-size/end-4/dots-hyprland?color=86dbce&label=SIZE&logo=protondrive&style=for-the-badge&logoColor=D9E0EE&labelColor=1E202B)\n<a href=\"https://discord.gg/GtdRBXgMwq\"> <img alt=\"Dynamic JSON Badge\" src=\"https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fdiscordapp.com%2Fapi%2Finvites%2FGtdRBXgMwq%3Fwith_counts%3Dtrue&query=approximate_member_count&style=for-the-badge&logo=discord&logoColor=D9E0EE&label=discord&labelColor=%231E202B&color=86dbc0&link=https%3A%2F%2Fdiscord.gg%2FGtdRBXgMwq\"> </a>\n\n</div>\n\n<div align=\"center\">\n    <h2>• overview •</h2>\n    <h3></h3>\n</div>\n\n> [!WARNING]  \n> Hyprland 0.55 update:\n> If your distro has not shipped Hyprland 0.55 and/or you're not ready for it, you should switch to the Pre-Hyprland Luaification release (or not update yet, if you're going to do that). See the wiki for more info: [Install](https://ii.clsty.link/en/ii-qs/01setup/#automated-installation) | [Update](https://ii.clsty.link/en/ii-qs/01setup/#updating)\n\n<details> \n  <summary>What this is/isn't</summary>\n\n  - Technically, configuration files\n  - Realistically, mostly the custom graphical shell\n  - NOT a system setup script: no graphic drivers, no zram setup, etc.\n  \n</details>\n\n<details> \n  <summary>Notable features</summary>\n     \n  - **Overview**: Shows open apps with live previews\n  - **AI**: Gemini, Ollama, and more\n  - **QoL**: screen translation, anti-flashbang, Google Lens\n  - **Material themes**: Choose your wallpaper, done, enjoy\n  - **Transparent installation**: Every command is shown before it's run\n</details>\n\n<details> \n  <summary>Installation</summary>\n\n   - **IMPORTANT: Hyprland 0.55 Update**: If your distro has not shipped Hyprland 0.55 and/or you're not ready for it, you should switch to the Pre-Hyprland Luaification release. See [the wiki](https://ii.clsty.link/en/ii-qs/01setup/) for more info\n   - Just run `bash <(curl -s https://ii.clsty.link/get)`\n     - Or, clone this repo and run `./setup install`\n     - See [the wiki](https://ii.clsty.link/en/ii-qs/01setup/) for more details\n   - **Keybinds**: Should be somewhat familiar to Windows or GNOME users. Important ones:\n     - `Super`+`/` = keybind list\n     - `Super`+`Enter` = terminal\n\n\n</details>\n\n<details>\n  <summary>Software overview</summary>\n\n  | Software | Purpose |\n  | ------------- | ------------- |\n  | [Hyprland](https://github.com/hyprwm/hyprland) | The compositor (manages and renders windows) |\n  | [Quickshell](https://quickshell.outfoxxed.me/) | A QtQuick-based widget system, used for the status bar, sidebars, etc. |\n  | Others | See [deps-info.md](https://github.com/end-4/dots-hyprland/blob/main/sdata/deps-info.md) |\n\n</details>\n\n<details>\n    <summary>Discord</summary>\n        <a href=\"https://discord.gg/GtdRBXgMwq\"> Server link</a> | I hope this provides a friendlier environment for support without needing me to personally accept every friend request/DM. For real issues, prefer GitHub\n\n</details>\n\n<div align=\"center\">\n    <h2>• screenshots •</h2>\n    <h3></h3>\n</div>\n\n<div align=\"center\">\n    <img src=\"assets/illogical-impulse.svg\" alt=\"illogical-impulse logo\" style=\"float:left; width:400;\">\n</div>\n\nWidget system: Quickshell | Support: Yes\n\n[Showcase video](https://www.youtube.com/watch?v=RPwovTInagE)\n\n| AI, settings app | Some widgets |\n|:---|:---------------|\n| <img width=\"1920\" height=\"1080\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5d4e7d07-d0b4-4406-a4c9-ed7ba90e3fe4\" /> | <img width=\"1920\" height=\"1080\" alt=\"image\" src=\"https://github.com/user-attachments/assets/6a32395f-9437-4192-8faf-2951a9e84cbe\" /> |\n| Window management | wow look its orange |\n| <img width=\"1920\" height=\"1080\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c51bed8b-3670-4d4c-9074-873be224fb8e\" /> | <img width=\"1920\" height=\"1080\" alt=\"image\" src=\"https://github.com/user-attachments/assets/98703a66-0743-439f-a721-cef7afa6ab95\" /> |\n\n<div align=\"center\">\n    <h2>• thank you •</h2>\n    <h3></h3>\n</div>\n\n - [@clsty](https://github.com/clsty) for making the dotfiles accessible by taking care of the install script and many other things\n - [@midn8hustlr](https://github.com/midn8hustlr) for greatly improving the color generation system\n - [@outfoxxed](https://github.com/outfoxxed/) for being extremely supportive in my Quickshell journey\n - Quickshell: [Soramane](https://github.com/caelestia-dots/shell/), [FridayFaerie](https://github.com/FridayFaerie/quickshell), [nydragon](https://github.com/nydragon/nysh)\n - AGS: [Aylur](https://github.com/Aylur/dotfiles/tree/ags-pre-ts), [kotontrion](https://github.com/kotontrion/dotfiles)\n - EWW: [fufexan](https://github.com/fufexan/dotfiles)\n\n<div align=\"center\">\n    <h2>• stonks •</h2>\n    <h3></h3>\n</div>\n\n- I promise not to attempt an +ULTRARICOSHOT irl... Coins can go here: https://github.com/sponsors/end-4\n- Tentacle cat hub twinkle internet points\n\n[![Stargazers over time](https://starchart.cc/end-4/dots-hyprland.svg?variant=adaptive)](https://starchart.cc/end-4/dots-hyprland)\n\n\n---\n\n<div align=\"center\">\n    <h2>• previous styles •</h2>\n    <h3></h3>\n</div>\n\n- **Unsupported!**\n- **Source**: illogical-impulse AGS in `ii-ags` branch, others in `archive` branch.\n- List is in reverse chronological order\n\n### illogical-impulse (AGS)\n\nWidget system: AGS | Support: No\n\n| AI | Common widgets |\n|:---|:---------------|\n| ![image](https://github.com/user-attachments/assets/9d7af13f-89ef-470d-ba78-d2288b79cf60) | ![image](https://github.com/end-4/dots-hyprland/assets/97237370/406b72b6-fa38-4f0d-a6c4-4d7d5d5ddcb7) |\n| Window management | Weeb power |\n| ![image](https://github.com/user-attachments/assets/02983b9b-79ba-4c25-8717-90bef2357ae5) | ![image](https://github.com/user-attachments/assets/bbb332ec-962a-4e88-a95b-486d0bd8ce76) |\n\n#### m3ww\n\nWidget system: EWW | Support: No\n\n<a href=\"https://streamable.com/85ch8x\">\n<img src=\"https://github.com/end-4/dots-hyprland/assets/97237370/09533e64-b6d7-47eb-a840-ee90c6776adf\" alt=\"Material Eww!\">\n</a>\n\n#### NovelKnock\n\nWidget system: EWW | Support: No\n\n<a href=\"https://streamable.com/7vo61k\">\n<img src=\"https://github.com/end-4/dots-hyprland/assets/97237370/42903d03-bf6f-49d4-be7f-dd77e6cb389d\" alt=\"Desktop Preview\">\n</a>\n\n#### Hybrid\n\nWidget system: EWW | Support: No\n\n<a href=\"https://streamable.com/4oogot\">\n<img src=\"https://github.com/end-4/dots-hyprland/assets/97237370/190deb1e-f6f5-46ce-8cf0-9b39944c079d\" alt=\"click the circles!\">\n</a>\n\n#### Windoes\n\nWidget system: EWW | Support: No\n\n<a href=\"https://streamable.com/5qx614\">\n<img src=\"https://github.com/end-4/dots-hyprland/assets/97237370/b15317b1-f295-49f5-b90c-fb6328b8d886\" alt=\"Desktop Preview\">\n</a>\n\n\n\n<div align=\"center\">\n    <h2>• inspirations/copying •</h2>\n    <h3></h3>\n</div>\n\n - Inspiration: osu!lazer (Hybrid), Windows 11 (Windoes), AvdanOS (NovelKnock), Material Design 3 (m3ww & later)\n - Copying: Absolutely, feel free. Just follow the license and it's all good\n \n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "## Describe your changes\n\n<!--- ONE FEATURE PER PULL REQUEST ONLY -->\n\n## Is it ready? Questions/feedback needed?\n\n\n"
  },
  {
    "path": ".github/workflows/auto-close-issue.yml",
    "content": "on:\n  issues:\n    types: [opened]\n\nname: Close issues when the \"ticked without reading\" checkbox is checked\n\npermissions:\n  issues: write\n\njobs:\n  detect-and-close:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Detect checked \"ticked without reading\" checkbox, comment, close and lock\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          OWNER: ${{ github.repository_owner }}\n          REPO: ${{ github.event.repository.name }}\n          ISSUE_NUMBER: ${{ github.event.issue.number }}\n          ISSUE_BODY: ${{ toJson(github.event.issue.body) }}\n          ISSUE_USER: ${{ github.event.issue.user.login }}\n        run: |\n          set -euo pipefail\n\n          # Normalize the JSON-encoded body into plain text\n          BODY=$(printf '%s' \"$ISSUE_BODY\" | sed -E 's/^\"(.*)\"$/\\1/' | sed 's/\\\\\"/\"/g' | sed 's/\\\\n/\\n/g')\n\n          echo \"Checking issue #${ISSUE_NUMBER} for the target checked checkbox...\"\n          # Use -- to stop option parsing so the leading - in the pattern isn't treated as an option\n          if printf '%s' \"$BODY\" | grep -Fiq -- \"- [x] I've ticked the checkboxes without reading their contents\"; then\n            echo \"Target checkbox is checked. Proceeding to comment, close and lock the issue.\"\n\n            # --- Get issue node id via GraphQL ---\n            QUERY='query($owner: String!, $name: String!, $number: Int!) { repository(owner: $owner, name: $name) { issue(number: $number) { id } } }'\n            GET_ID_PAYLOAD=$(jq -n --arg q \"$QUERY\" --arg owner \"$OWNER\" --arg name \"$REPO\" --argjson number \"$ISSUE_NUMBER\" '{query:$q, variables:{owner:$owner, name:$name, number:$number}}')\n\n            echo \"GraphQL: fetching issue node id...\"\n            RES=$(curl -s -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Content-Type: application/json\" -d \"$GET_ID_PAYLOAD\" https://api.github.com/graphql)\n            echo \"GraphQL response (get id):\"\n            printf '%s\\n' \"$RES\"\n\n            ISSUE_ID=$(printf '%s' \"$RES\" | jq -r '.data.repository.issue.id // empty')\n\n            if [ -z \"$ISSUE_ID\" ]; then\n              echo \"Failed to get issue id from GraphQL response. Aborting.\"\n              exit 1\n            fi\n            echo \"Issue node id: $ISSUE_ID\"\n\n            # --- Post a comment to the issue ---\n            COMMENT_BODY=\"Hi @${ISSUE_USER} — I noticed you checked \\\"I've ticked the checkboxes without reading their contents\\\" in the issue template. To help others assist you effectively, please read the template and provide the requested diagnostic information (Step 2 & Step 3). I will close this issue now. If you create a new issue with the required information, we can re-evaluate. Thank you!\"\n            MUT_ADD_COMMENT='mutation($id: ID!, $body: String!) { addComment(input: {subjectId: $id, body: $body}) { clientMutationId } }'\n            ADD_COMMENT_PAYLOAD=$(jq -n --arg q \"$MUT_ADD_COMMENT\" --arg id \"$ISSUE_ID\" --arg body \"$COMMENT_BODY\" '{query:$q, variables:{id:$id, body:$body}}')\n\n            echo \"GraphQL: adding comment...\"\n            RES_COMMENT=$(curl -s -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Content-Type: application/json\" -d \"$ADD_COMMENT_PAYLOAD\" https://api.github.com/graphql)\n            echo \"GraphQL response (add comment):\"\n            printf '%s\\n' \"$RES_COMMENT\"\n\n            ERR_COMMENT=$(printf '%s' \"$RES_COMMENT\" | jq -r '.errors[]?.message // empty')\n            if [ -n \"$ERR_COMMENT\" ]; then\n              echo \"addComment error: $ERR_COMMENT\"\n              exit 1\n            fi\n            echo \"Comment posted.\"\n\n            # --- Attempt to close via GraphQL updateIssue ---\n            MUT_UPDATE_ISSUE='mutation($id: ID!) { updateIssue(input: {id: $id, state: CLOSED, stateReason: NOT_PLANNED}) { issue { number, state, stateReason } } }'\n            UPDATE_PAYLOAD=$(jq -n --arg q \"$MUT_UPDATE_ISSUE\" --arg id \"$ISSUE_ID\" '{query:$q, variables:{id:$id}}')\n\n            echo \"GraphQL: updating issue (close with NOT_PLANNED)...\"\n            RES_UPDATE=$(curl -s -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Content-Type: application/json\" -d \"$UPDATE_PAYLOAD\" https://api.github.com/graphql)\n            echo \"GraphQL response (update issue):\"\n            printf '%s\\n' \"$RES_UPDATE\"\n\n            ERR_UPDATE=$(printf '%s' \"$RES_UPDATE\" | jq -r '.errors[]?.message // empty')\n            UPDATED_STATE=$(printf '%s' \"$RES_UPDATE\" | jq -r '.data.updateIssue.issue.state // empty')\n            UPDATED_REASON=$(printf '%s' \"$RES_UPDATE\" | jq -r '.data.updateIssue.issue.stateReason // empty')\n\n            CLOSED_OK=false\n\n            if [ -n \"$ERR_UPDATE\" ]; then\n              echo \"GraphQL updateIssue returned errors: $ERR_UPDATE\"\n            fi\n\n            if [ \"$UPDATED_STATE\" = \"CLOSED\" ]; then\n              echo \"Issue closed via GraphQL: state=$UPDATED_STATE, stateReason=$UPDATED_REASON\"\n              CLOSED_OK=true\n            else\n              echo \"GraphQL update did not confirm the issue is closed. Falling back to REST API PATCH to ensure the issue is closed.\"\n\n              # REST fallback to close the issue with state_reason \"not_planned\"\n              REST_PAYLOAD=$(jq -n --arg state \"closed\" --arg sr \"not_planned\" '{state:$state, state_reason:$sr}')\n              echo \"REST: PATCH /repos/$OWNER/$REPO/issues/$ISSUE_NUMBER payload: $REST_PAYLOAD\"\n              RES_REST=$(curl -s -w \"\\n%{http_code}\" -X PATCH \\\n                -H \"Authorization: Bearer $GITHUB_TOKEN\" \\\n                -H \"Accept: application/vnd.github+json\" \\\n                -H \"Content-Type: application/json\" \\\n                -d \"$REST_PAYLOAD\" \\\n                \"https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER\")\n\n              HTTP_STATUS=$(printf '%s' \"$RES_REST\" | tail -n1)\n              RESP_BODY=$(printf '%s' \"$RES_REST\" | sed '$d')\n\n              echo \"REST response body:\"\n              printf '%s\\n' \"$RESP_BODY\"\n              echo \"REST HTTP status: $HTTP_STATUS\"\n\n              if [ \"$HTTP_STATUS\" -ge 200 ] && [ \"$HTTP_STATUS\" -lt 300 ]; then\n                CLOSED_STATE=$(printf '%s' \"$RESP_BODY\" | jq -r '.state // empty')\n                CLOSED_REASON=$(printf '%s' \"$RESP_BODY\" | jq -r '.state_reason // empty')\n                echo \"Issue closed via REST: state=$CLOSED_STATE, state_reason=$CLOSED_REASON\"\n                if [ \"$CLOSED_STATE\" = \"closed\" ]; then\n                  CLOSED_OK=true\n                fi\n              else\n                echo \"REST fallback failed to close the issue. See REST response above.\"\n                exit 1\n              fi\n            fi\n\n            # --- Attempt to lock the conversation (GraphQL first, then REST fallback) ---\n            if [ \"$CLOSED_OK\" = \"true\" ]; then\n              echo \"Attempting to lock the conversation via GraphQL with reason NO_REASON...\"\n\n              MUT_LOCK='mutation($id: ID!, $reason: LockReason) { lockLockable(input:{lockableId:$id, lockReason:$reason}) { clientMutationId } }'\n              LOCK_PAYLOAD=$(jq -n --arg q \"$MUT_LOCK\" --arg id \"$ISSUE_ID\" --arg reason \"NO_REASON\" '{query:$q, variables:{id:$id, reason:$reason}}')\n\n              RES_LOCK=$(curl -s -H \"Authorization: Bearer $GITHUB_TOKEN\" -H \"Content-Type: application/json\" -d \"$LOCK_PAYLOAD\" https://api.github.com/graphql)\n              echo \"GraphQL response (lock):\"\n              printf '%s\\n' \"$RES_LOCK\"\n\n              LOCK_ERR=$(printf '%s' \"$RES_LOCK\" | jq -r '.errors[]?.message // empty')\n\n              if [ -n \"$LOCK_ERR\" ]; then\n                echo \"GraphQL lockLockable returned errors: $LOCK_ERR\"\n                echo \"Falling back to REST API to lock the conversation (no explicit reason).\"\n\n                # REST fallback to lock the issue (no lock_reason to indicate \"no reason\")\n                RES_REST_LOCK=$(curl -s -w \"\\n%{http_code}\" -X PUT \\\n                  -H \"Authorization: Bearer $GITHUB_TOKEN\" \\\n                  -H \"Accept: application/vnd.github+json\" \\\n                  \"https://api.github.com/repos/$OWNER/$REPO/issues/$ISSUE_NUMBER/lock\" -d '{}')\n\n                HTTP_STATUS_LOCK=$(printf '%s' \"$RES_REST_LOCK\" | tail -n1)\n                RESP_BODY_LOCK=$(printf '%s' \"$RES_REST_LOCK\" | sed '$d')\n\n                echo \"REST lock response body:\"\n                printf '%s\\n' \"$RESP_BODY_LOCK\"\n                echo \"REST lock HTTP status: $HTTP_STATUS_LOCK\"\n\n                if [ \"$HTTP_STATUS_LOCK\" -ge 200 ] && [ \"$HTTP_STATUS_LOCK\" -lt 300 ]; then\n                  echo \"Issue conversation locked via REST (no explicit reason).\"\n                else\n                  echo \"REST fallback failed to lock the conversation. See REST response above.\"\n                  exit 1\n                fi\n              else\n                echo \"Lock via GraphQL succeeded (or returned no errors).\"\n              fi\n            else\n              echo \"Issue was not successfully closed; skipping lock.\"\n            fi\n\n          else\n            echo \"Checkbox not present/checked. Nothing to do.\"\n          fi\n"
  },
  {
    "path": ".github/workflows/dist-update-notification.yml",
    "content": "name: Comment on Discussion When sdata/dist-arch/ Changes\n\non:\n  push:\n    branches:\n      - main\n    paths:\n      - \"sdata/dist-arch/**\"\n      - \"!sdata/dist-arch/README.md\"\n#  workflow_dispatch:\n\njobs:\n  comment_on_discussion:\n    runs-on: ubuntu-latest\n    if: github.ref == 'refs/heads/main' && github.repository == 'end-4/dots-hyprland'\n    steps:\n      - name: Create comment on discussion #2140\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          DISCUSSION_NUMBER: 2140\n        # https://docs.github.com/en/graphql/guides/using-the-graphql-api-for-discussions\n        # https://docs.github.com/en/graphql/reference/mutations#adddiscussioncomment\n        run: |\n          MESSAGE=\"**Auto notification:**\\n\"\n          MESSAGE+=\"Directory \\`sdata/dist-arch\\` has been updated.\\n\"\n          MESSAGE+=\"Commit HASH: ${{ github.sha }}\\n\"\n          MESSAGE+=\"Commit message: ${{ github.event.head_commit.message }}\"\n          REPO_OWNER=\"${{ github.repository_owner }}\"\n          REPO_NAME=\"${{ github.event.repository.name }}\"\n\n          DISCUSSION_NODE_ID=$(gh api graphql -f query='\n            query {\n              repository( owner: \"'${REPO_OWNER}'\", name: \"'${REPO_NAME}'\" )\n                { discussion(number: '${DISCUSSION_NUMBER}') { id } } \n              }' | \\\n            jq -r '.data.repository.discussion.id')\n          gh api graphql -f query='\n            mutation {\n              addDiscussionComment(input:{\n                discussionId: \"'$DISCUSSION_NODE_ID'\",\n                body: \"'\"$MESSAGE\"'\",\n              }) {\n                clientMutationId\n                comment {\n                  id\n                  body\n                }\n              }\n            }\n          '\n"
  },
  {
    "path": ".github/workflows/dump-github-context.yml",
    "content": "name: Dump github context\n\non:\n  workflow_dispatch:\n\njobs:\n  dump_github_context:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Dump github context\n        run: echo \"$GITHUB_CONTEXT\"\n        shell: bash\n        env:\n          GITHUB_CONTEXT: ${{ toJson(github) }}\n"
  },
  {
    "path": ".github/workflows/moderator.yml",
    "content": "name: AI Moderator\non:\n  issues:\n    types: [opened]\n  issue_comment:\n    types: [created]\n  pull_request_review_comment:\n    types: [created]\n\njobs:\n  spam-detection:\n    runs-on: ubuntu-latest\n    permissions:\n      issues: write\n      pull-requests: write\n      models: read\n      contents: read\n    steps:\n      - uses: actions/checkout@v4\n      - uses: github/ai-moderator@v1\n        with:\n          token: ${{ secrets.GITHUB_TOKEN }}\n          spam-label: 'spam'\n          ai-label: 'ai-generated'\n          minimize-detected-comments: true\n          # Built-in prompt configuration (all enabled by default)\n          enable-spam-detection: true\n          enable-link-spam-detection: true\n          enable-ai-detection: true\n          # custom-prompt-path: '.github/prompts/my-custom.prompt.yml'  # Optional\n"
  },
  {
    "path": ".gitignore",
    "content": "/diagnose.result\n/cache\n\n# Ignore Python cache files\n__pycache__/\n*.py[cod]\n\n/dots/.config/quickshell/ii/.qmlls.ini\n\n# exp-update\n/.update-lock\n\n# custom os-release\n/os-release\n\n# Emacs auto backup file\n*~\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"dots/.config/quickshell/ii/modules/common/widgets/shapes\"]\n\tpath = dots/.config/quickshell/ii/modules/common/widgets/shapes\n\turl = https://github.com/end-4/rounded-polygon-qmljs.git\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "diagnose",
    "content": "#!/usr/bin/env bash\n#\n# This script is for quickly generate helpful info\n#\n# It should be as independent as possible and should not source other files unless it has to\n#\n# TODO: How to get the Qt version which Quickshell was built against?\n\nSTY_RED='\\e[31m'\nSTY_RST='\\e[00m'\n\ncd \"$(dirname \"$0\")\";export base=\"$(pwd)\"\noutput_file=diagnose.result;rm $output_file\nexport LANG=C;export LC_ALL=C\ncase $(whoami) in\n  root)echo -e \"${STY_RED}[$0]: This script is NOT to be executed with sudo or as root. Aborting...${STY_RST}\";exit 1;;\nesac\n\n\nx() { _exec \"$@\" 2>&1 | tee -a $output_file ; }\ne() { _box \"$@\" | tee -a $output_file ; }\n_box() {\n  length=$(echo \"$1\" | wc -L);total_width=$((length + 2))\n  #line=$(printf \"═%.0s\" $(seq 1 $total_width))\n  #border_up=\"╔${line}╗\";border_down=\"╚${line}╝\"\n  #border_vertical=\"║\"\n  line=$(printf \"=%.0s\" $(seq 1 $total_width))\n  border_up=\"/${line}\\\\\";border_down=\"\\\\${line}/\"\n  border_vertical=\"|\"\n  echo -e \"\\n$border_up\"\n  echo \"$border_vertical $1 $border_vertical\"\n  echo \"$border_down\"\n}\n_exec() {\n  printf \"\\n[===diagnose===] $*\\n\"\n  \"$@\"\n  err=$?;if [ ! $err -eq 0 ];then echo \"[---EXIT $err---]\";else echo \"[---SUCCESS---]\";fi\n}\n_check_distro_id() {\n  OS_RELEASE_FILE=/etc/os-release\n  if [[ -f \"$OS_RELEASE_FILE\" ]]; then\n    OS_DISTRO_ID=$(awk -F'=' '/^ID=/ { gsub(/[\"\\x27]/,\"\",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null)\n    OS_DISTRO_ID_LIKE=$(awk -F'=' '/^ID_LIKE=/ { gsub(/[\"\\x27]/,\"\",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null)\n    echo \"distro ID: $OS_DISTRO_ID\"\n    echo \"distro ID_LIKE: $OS_DISTRO_ID_LIKE\"\n  else\n    echo \"$OS_RELEASE_FILE does not exist.\"\n  fi\n}\n_check_distro() {\n  lsb_release -a || cat /etc/os-release || cat /etc/lsb-release\n}\n_check_venv() {\n  source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n  which python\n  deactivate\n}\n_check_quickshell_version() {\n  pacman -Q | grep -E 'quickshell|qt6-base'\n}\n_check_PKGBUILD_version() {\n  pacman -Q | grep '^illogical-impulse-'\n}\n\ne \"Checking git repo info\"\nx git remote get-url origin\nx git rev-parse HEAD\nx git status\nx git submodule status --recursive\n\ne \"Checking distro\"\nx _check_distro_id\nx cat os-release\n#x _check_distro \n\ne \"Checking variables\"\nx declare -p XDG_CACHE_HOME # ~/.cache\nx declare -p XDG_CONFIG_HOME # ~/.config\nx declare -p XDG_DATA_HOME # ~/.local/share\nx declare -p XDG_STATE_HOME # ~/.local/state\nx declare -p ILLOGICAL_IMPULSE_VIRTUAL_ENV # $XDG_STATE_HOME/quickshell/.venv\n\ne \"Checking directories/files\"\nx ls -l ~/.local/state/quickshell/.venv\n\n#e \"Checking command existence\"\n#commands=(yay pacman zypper apt dnf yum)\n#commands+=(ags agsv1)\n#commands+=(Hyprland hypr{ctl,idle,lock,picker})\n#commands+=(uv)\n#for i in \"${commands[@]}\";do x command -v $i;done\n\ne \"Checking versions\"\nx Hyprland --version\nx _check_quickshell_version\nx _check_PKGBUILD_version\n\ne \"Finished. Output saved as \\\"$output_file\\\".\"\nif ! command -v curl 2>&1 >>/dev/null ;then echo \"\\\"curl\\\" not found, pastebin upload unavailable.\";exit;fi\necho \"(Optional) Do you agree to upload the file \\\"$output_file\\\" to the online pastebin (https://0x0.st)?\"\necho \"Notes:\"\necho \"1. It is a public service and the logfile will be expired in 15 days.\"\necho \"2. You should have a look at the content of \\\"$output_file\\\" before agreeing to upload it.\"\necho \"3. Only agree when necessary, typically when you are creating an issue and not able to upload the \\\"diagnose.result\\\" file there or copy-paste the output directly.\"\nread -p \"y=yes, n=no (default) ====> \" p\ncase $p in\n  [yY]) echo \"OK, uploading...\"\n        curl -F'file=@diagnose.result' -Fexpires=360 https://0x0.st && \\\n          echo \"Uploaded. Please attach the URL above when asking for help.\"\n        ;;\n  *) echo \"Uploading aborted.\";;\nesac\n"
  },
  {
    "path": "dots/.config/Kvantum/Colloid/Colloid.kvconfig",
    "content": "[%General]\nauthor=Vince Liuice, based on KvAdapta by Tsu Jan\ncomment=An uncomplicated theme inspired by the Materia GTK theme\nx11drag=none\nalt_mnemonic=true\nleft_tabs=false\nattach_active_tab=false\nmirror_doc_tabs=true\ngroup_toolbar_buttons=false\ntoolbar_item_spacing=0\ntoolbar_interior_spacing=2\nspread_progressbar=true\ncomposite=true\nmenu_shadow_depth=6\nspread_menuitems=false\ntooltip_shadow_depth=7\nsplitter_width=1\nscroll_width=9\nscroll_arrows=false\nscroll_min_extent=60\nslider_width=2\nslider_handle_width=23\nslider_handle_length=22\ntickless_slider_handle_size=22\ncenter_toolbar_handle=true\ncheck_size=24\ntextless_progressbar=false\nprogressbar_thickness=2\nmenubar_mouse_tracking=true\ntoolbutton_style=1\ndouble_click=false\ntranslucent_windows=false\nblurring=false\npopup_blurring=false\nvertical_spin_indicators=false\nspin_button_width=24\nfill_rubberband=false\nmerge_menubar_with_toolbar=true\nsmall_icon_size=16\nlarge_icon_size=32\nbutton_icon_size=16\ntoolbar_icon_size=16\ncombo_as_lineedit=true\nanimate_states=true\nbutton_contents_shift=false\ncombo_menu=true\nhide_combo_checkboxes=true\ncombo_focus_rect=false\ngroupbox_top_label=true\ninline_spin_indicators=true\njoined_inactive_tabs=false\nlayout_spacing=3\nlayout_margin=3\nscrollbar_in_view=true\ntransient_scrollbar=true\ntransient_groove=false\nsubmenu_overlap=0\ntooltip_delay=0\ntree_branch_line=false\nno_window_pattern=false\nopaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,trojita,dragon,digikam\nreduce_window_opacity=0\nrespect_DE=true\nscrollable_menu=false\nsubmenu_delay=150\nno_inactiveness=false\nreduce_menu_opacity=0\nclick_behavior=2\ncontrast=1.00\ndialog_button_layout=0\nintensity=1.00\nsaturation=1.00\nshadowless_popup=false\ndrag_from_buttons=false\nmenu_blur_radius=0\ntooltip_blur_radius=0\n\n[GeneralColors]\nwindow.color=#F5F5F5\nbase.color=#ffffff\nalt.base.color=#f8f8f8\nbutton.color=#f2f2f2\nlight.color=#ffffff\nmid.light.color=#f0f0f0\ndark.color=#c8c8c8\nmid.color=#e1e1e1\nhighlight.color=#3c84f7\ninactive.highlight.color=#3c84f7\ntext.color=#444444\nwindow.text.color=#444444\nbutton.text.color=#444444\ndisabled.text.color=#44444474\ntooltip.text.color=#444444\nhighlight.text.color=#333333\nlink.color=#0057AE\nlink.visited.color=#E040FB\nprogress.indicator.text.color=#444444\n\n[Hacks]\ntransparent_ktitle_label=true\ntransparent_dolphin_view=true\ntransparent_pcmanfm_sidepane=true\nblur_translucent=false\ntransparent_menutitle=true\nrespect_darkness=true\nkcapacitybar_as_progressbar=true\nforce_size_grip=true\niconless_pushbutton=false\niconless_menu=false\ndisabled_icon_opacity=100\nlxqtmainmenu_iconsize=16\nnormal_default_pushbutton=true\nsingle_top_toolbar=true\ntint_on_mouseover=0\ntransparent_pcmanfm_view=true\nno_selection_tint=true\ntransparent_arrow_button=true\nmiddle_click_scroll=false\nopaque_colors=false\nkinetic_scrolling=false\nscroll_jump_workaround=true\ncentered_forms=false\nnoninteger_translucency=false\nstyle_vertical_toolbars=false\nblur_only_active_window=true\n\n[PanelButtonCommand]\nframe=true\nframe.element=button\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ninterior=true\ninterior.element=button\nindicator.size=8\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=white\nhighlight.text.color=white\ntext.shadow=0\ntext.margin=4\ntext.iconspacing=4\nindicator.element=arrow\nframe.expansion=0\n\n[PanelButtonTool]\ninherits=PanelButtonCommand\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=white\ntext.disabled.color=#44444474\ntext.bold=false\nindicator.element=arrow\nindicator.size=8\nframe.expansion=0\n\n[ToolbarButton]\nframe=true\nframe.element=tbutton\ninterior.element=tbutton\nframe.top=14\nframe.bottom=14\nframe.left=14\nframe.right=14\nindicator.element=tarrow\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\ntext.disabled.color=#44444474\ntext.bold=false\nframe.expansion=28\n\n[Dock]\ninherits=PanelButtonCommand\ninterior.element=dock\nframe.element=dock\nframe.top=1\nframe.bottom=1\nframe.left=1\nframe.right=1\ntext.normal.color=#444444\n\n[DockTitle]\ninherits=PanelButtonCommand\nframe=false\ninterior=false\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.bold=false\n\n[IndicatorSpinBox]\ninherits=PanelButtonCommand\nframe=true\ninterior=true\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\nindicator.element=spin\nindicator.size=8\ntext.normal.color=#444444\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\n\n[RadioButton]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=radio\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nmin_width=+0.3font\nmin_height=+0.3font\n\n[CheckBox]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=checkbox\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nmin_width=+0.3font\nmin_height=+0.3font\n\n[Focus]\ninherits=PanelButtonCommand\nframe=true\nframe.element=focus\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\nframe.patternsize=14\n\n[GenericFrame]\ninherits=PanelButtonCommand\nframe=true\ninterior=false\nframe.element=common\ninterior.element=common\nframe.top=1\nframe.bottom=1\nframe.left=1\nframe.right=1\n\n[LineEdit]\ninherits=PanelButtonCommand\nframe.element=lineedit\ninterior.element=lineedit\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\n\n[ToolbarLineEdit]\nframe.element=lineedit\ninterior.element=lineedit\n\n[DropDownButton]\ninherits=PanelButtonCommand\nindicator.element=arrow-down\n\n[IndicatorArrow]\nindicator.element=arrow\nindicator.size=8\n\n[ToolboxTab]\ninherits=PanelButtonCommand\ntext.normal.color=#444444\ntext.press.color=#333333\ntext.focus.color=#444444\n\n[Tab]\ninherits=PanelButtonCommand\ninterior.element=tab\ntext.margin.left=8\ntext.margin.right=8\ntext.margin.top=0\ntext.margin.bottom=0\nframe.element=tab\nindicator.element=tab\nindicator.size=22\nframe.top=8\nframe.bottom=8\nframe.left=8\nframe.right=8\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nframe.expansion=0\ntext.bold=false\n\n[TabFrame]\ninherits=PanelButtonCommand\nframe.element=tabframe\ninterior.element=tabframe\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\n\n[TreeExpander]\ninherits=PanelButtonCommand\nindicator.size=8\nindicator.element=tree\n\n[HeaderSection]\ninherits=PanelButtonCommand\ninterior.element=header\nframe.element=header\nframe.top=0\nframe.bottom=1\nframe.left=1\nframe.right=1\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nframe.expansion=0\n\n[SizeGrip]\nindicator.element=resize-grip\n\n[Toolbar]\ninherits=PanelButtonCommand\nindicator.element=toolbar\nindicator.size=5\ntext.margin=0\ninterior.element=menubar\nframe.element=menubar\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nframe.left=6\nframe.right=6\nframe.top=0\nframe.bottom=1\nframe.expansion=0\n\n[Slider]\ninherits=PanelButtonCommand\nframe.element=slider\nfocusFrame=true\ninterior.element=slider\nframe.top=3\nframe.bottom=3\nframe.left=3\nframe.right=3\n\n[SliderCursor]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=slidercursor\n\n[Progressbar]\ninherits=PanelButtonCommand\nframe.element=progress\ninterior.element=progress\ntext.margin=0\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\ntext.bold=false\nframe.expansion=8\n\n[ProgressbarContents]\ninherits=PanelButtonCommand\nframe=true\nframe.element=progress-pattern\ninterior.element=progress-pattern\n\n[ItemView]\ninherits=PanelButtonCommand\ntext.margin=0\nframe.element=itemview\ninterior.element=itemview\nframe.top=4\nframe.bottom=4\nframe.left=4\nframe.right=4\ntext.margin.top=0\ntext.margin.bottom=0\ntext.margin.left=8\ntext.margin.right=8\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nmin_width=+0.3font\nmin_height=+0.3font\nframe.expansion=0\n\n[Splitter]\ninterior.element=splitter\nframe=false\nindicator.size=0\n\n[Scrollbar]\ninherits=PanelButtonCommand\nindicator.element=arrow\nindicator.size=12\n\n[ScrollbarSlider]\ninherits=PanelButtonCommand\nframe.element=scrollbarslider\ninterior=false\nframe.left=5\nframe.right=5\nframe.top=5\nframe.bottom=5\nindicator.element=grip\nindicator.size=12\n\n[ScrollbarGroove]\ninherits=PanelButtonCommand\ninterior=false\nframe=false\n\n[Menu]\ninherits=PanelButtonCommand\nframe.top=10\nframe.bottom=10\nframe.left=10\nframe.right=10\nframe.element=menu\ninterior.element=menu\ntext.normal.color=#444444\ntext.shadow=false\nframe.expansion=0\ntext.bold=false\n\n[MenuItem]\ninherits=PanelButtonCommand\nframe=true\nframe.element=menuitem\ninterior.element=menuitem\nindicator.element=menuitem\ntext.normal.color=#444444\ntext.focus.color=#333333\ntext.margin.top=0\ntext.margin.bottom=0\ntext.margin.left=6\ntext.margin.right=6\nframe.top=4\nframe.bottom=4\nframe.left=4\nframe.right=4\ntext.bold=false\nframe.expansion=0\n\n[MenuBar]\ninherits=PanelButtonCommand\nframe.element=menubar\ninterior.element=menubar\nframe.bottom=0\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\nframe.expansion=0\ntext.bold=false\n\n[MenuBarItem]\ninherits=PanelButtonCommand\ninterior=true\ninterior.element=menubaritem\nframe.element=menubaritem\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\ntext.margin.left=4\ntext.margin.right=4\ntext.margin.top=0\ntext.margin.bottom=0\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.press.color=#333333\ntext.toggle.color=#333333\ntext.bold=false\nmin_width=+0.3font\nmin_height=+0.3font\nframe.expansion=0\n\n[TitleBar]\ninherits=PanelButtonCommand\nframe=false\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\ninterior.element=titlebar\nindicator.size=16\nindicator.element=mdi\ntext.normal.color=#444444\ntext.focus.color=#444444\ntext.bold=false\ntext.italic=true\nframe.expansion=0\n\n[ComboBox]\ninherits=PanelButtonCommand\nframe.element=combo\ninterior.element=combo\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\ntext.normal.color=#444444\ntext.focus.color=#424242\ntext.press.color=#424242\ntext.toggle.color=#424242\n\n[GroupBox]\ninherits=GenericFrame\nframe=false\ntext.shadow=0\ntext.margin=0\ntext.normal.color=#444444\ntext.focus.color=#333333\ntext.bold=false\nframe.expansion=0\n\n[TabBarFrame]\ninherits=GenericFrame\nframe=false\ninterior=false\n\n[ToolTip]\ninherits=GenericFrame\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ninterior=true\ntext.shadow=0\ntext.margin=6\ninterior.element=tooltip\nframe.element=tooltip\ntext.normal.color=#444444\ntext.focus.color=#333333\ntext.press.color=#333333\ntext.toggle.color=#333333\nframe.expansion=6\n\n[StatusBar]\ninherits=GenericFrame\nframe=false\ninterior=false\n\n[Window]\ninterior=true\ninterior.element=window\nframe=true\nframe.element=window\nframe.bottom=10\nframe.top=10\n"
  },
  {
    "path": "dots/.config/Kvantum/Colloid/ColloidDark.kvconfig",
    "content": "[%General]\nauthor=Vince Liuice, based on KvAdapta by Tsu Jan\ncomment=An uncomplicated theme inspired by the Materia GTK theme\nx11drag=none\nalt_mnemonic=true\nleft_tabs=false\nattach_active_tab=false\nmirror_doc_tabs=true\ngroup_toolbar_buttons=false\ntoolbar_item_spacing=0\ntoolbar_interior_spacing=2\nspread_progressbar=true\ncomposite=true\nmenu_shadow_depth=6\nspread_menuitems=false\ntooltip_shadow_depth=7\nsplitter_width=1\nscroll_width=9\nscroll_arrows=false\nscroll_min_extent=60\nslider_width=2\nslider_handle_width=23\nslider_handle_length=22\ntickless_slider_handle_size=22\ncenter_toolbar_handle=true\ncheck_size=24\ntextless_progressbar=false\nprogressbar_thickness=2\nmenubar_mouse_tracking=true\ntoolbutton_style=1\ndouble_click=false\ntranslucent_windows=false\nblurring=false\npopup_blurring=false\nvertical_spin_indicators=false\nspin_button_width=24\nfill_rubberband=false\nmerge_menubar_with_toolbar=true\nsmall_icon_size=16\nlarge_icon_size=32\nbutton_icon_size=16\ntoolbar_icon_size=16\ncombo_as_lineedit=true\nanimate_states=true\nbutton_contents_shift=false\ncombo_menu=true\nhide_combo_checkboxes=true\ncombo_focus_rect=false\ngroupbox_top_label=true\ninline_spin_indicators=true\njoined_inactive_tabs=false\nlayout_spacing=3\nlayout_margin=3\nscrollbar_in_view=true\ntransient_scrollbar=true\ntransient_groove=false\nsubmenu_overlap=0\ntooltip_delay=0\ntree_branch_line=false\nno_window_pattern=false\nopaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,trojita,dragon,digikam\nreduce_window_opacity=0\nrespect_DE=true\nscrollable_menu=false\nsubmenu_delay=150\nno_inactiveness=false\nreduce_menu_opacity=0\nclick_behavior=2\ncontrast=1.00\ndialog_button_layout=0\nintensity=1.00\nsaturation=1.00\nshadowless_popup=false\ndrag_from_buttons=false\nmenu_blur_radius=0\ntooltip_blur_radius=0\n\n[GeneralColors]\nwindow.color=#2c2c2c\nbase.color=#2c2c2c\nalt.base.color=#2e2e2e\nbutton.color=#4d4d4d\nlight.color=#535353\nmid.light.color=#474747\ndark.color=#282828\nmid.color=#323232\nhighlight.color=#5b9bf8\ninactive.highlight.color=#5b9bf8\ntext.color=#dfdfdf\nwindow.text.color=#dfdfdf\nbutton.text.color=#dfdfdf\ndisabled.text.color=#696969\ntooltip.text.color=#efefef\nhighlight.text.color=#ffffff\nlink.color=#0057AE\nlink.visited.color=#E040FB\nprogress.indicator.text.color=#dfdfdf\n\n[Hacks]\ntransparent_ktitle_label=true\ntransparent_dolphin_view=true\ntransparent_pcmanfm_sidepane=true\nblur_translucent=false\ntransparent_menutitle=true\nrespect_darkness=true\nkcapacitybar_as_progressbar=true\nforce_size_grip=true\niconless_pushbutton=false\niconless_menu=false\ndisabled_icon_opacity=100\nlxqtmainmenu_iconsize=16\nnormal_default_pushbutton=true\nsingle_top_toolbar=true\ntint_on_mouseover=0\ntransparent_pcmanfm_view=true\nno_selection_tint=true\ntransparent_arrow_button=true\nmiddle_click_scroll=false\nopaque_colors=false\nkinetic_scrolling=false\nscroll_jump_workaround=true\ncentered_forms=false\nnoninteger_translucency=false\nstyle_vertical_toolbars=false\nblur_only_active_window=true\n\n[PanelButtonCommand]\nframe=true\nframe.element=button\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ninterior=true\ninterior.element=button\nindicator.size=8\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.press.color=white\ntext.toggle.color=#ffffff\ntext.shadow=0\ntext.margin=4\ntext.iconspacing=4\nindicator.element=arrow\nframe.expansion=0\n\n[PanelButtonTool]\ninherits=PanelButtonCommand\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.press.color=white\ntext.toggle.color=#ffffff\ntext.bold=false\nindicator.element=arrow\nindicator.size=8\nframe.expansion=0\n\n[ToolbarButton]\nframe=true\nframe.element=tbutton\ninterior.element=tbutton\nframe.top=16\nframe.bottom=16\nframe.left=16\nframe.right=16\nindicator.element=tarrow\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.press.color=white\ntext.toggle.color=white\ntext.bold=false\nframe.expansion=32\n\n[Dock]\ninherits=PanelButtonCommand\ninterior.element=dock\nframe.element=dock\nframe.top=1\nframe.bottom=1\nframe.left=1\nframe.right=1\ntext.normal.color=#dfdfdf\n\n[DockTitle]\ninherits=PanelButtonCommand\nframe=false\ninterior=false\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.bold=false\n\n[IndicatorSpinBox]\ninherits=PanelButtonCommand\nframe=true\ninterior=true\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\nindicator.element=spin\nindicator.size=8\ntext.normal.color=#dfdfdf\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\n\n[RadioButton]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=radio\ntext.normal.color=#dfdfdf\ntext.focus.color=white\nmin_width=+0.3font\nmin_height=+0.3font\n\n[CheckBox]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=checkbox\ntext.normal.color=#dfdfdf\ntext.focus.color=white\nmin_width=+0.3font\nmin_height=+0.3font\n\n[Focus]\ninherits=PanelButtonCommand\nframe=true\nframe.element=focus\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\nframe.patternsize=14\n\n[GenericFrame]\ninherits=PanelButtonCommand\nframe=true\ninterior=false\nframe.element=common\ninterior.element=common\nframe.top=1\nframe.bottom=1\nframe.left=1\nframe.right=1\n\n[LineEdit]\ninherits=PanelButtonCommand\nframe.element=lineedit\ninterior.element=lineedit\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\n\n[ToolbarLineEdit]\nframe.element=lineedit\ninterior.element=lineedit\n\n[DropDownButton]\ninherits=PanelButtonCommand\nindicator.element=arrow-down\n\n[IndicatorArrow]\nindicator.element=arrow\nindicator.size=8\n\n[ToolboxTab]\ninherits=PanelButtonCommand\ntext.normal.color=#dfdfdf\ntext.press.color=#dfdfdf\ntext.focus.color=white\n\n[Tab]\ninherits=PanelButtonCommand\ninterior.element=tab\ntext.margin.left=8\ntext.margin.right=8\ntext.margin.top=0\ntext.margin.bottom=0\nframe.element=tab\nindicator.element=tab\nindicator.size=22\nframe.top=8\nframe.bottom=8\nframe.left=8\nframe.right=8\ntext.normal.color=#dfdfdf\ntext.focus.color=#dfdfdf\ntext.press.color=white\ntext.toggle.color=white\nframe.expansion=0\ntext.bold=false\n\n[TabFrame]\ninherits=PanelButtonCommand\nframe.element=tabframe\ninterior.element=tabframe\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\n\n[TreeExpander]\ninherits=PanelButtonCommand\nindicator.size=8\nindicator.element=tree\n\n[HeaderSection]\ninherits=PanelButtonCommand\ninterior.element=header\nframe.element=header\nframe.top=0\nframe.bottom=1\nframe.left=1\nframe.right=1\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.press.color=white\ntext.toggle.color=white\nframe.expansion=0\n\n[SizeGrip]\nindicator.element=resize-grip\n\n[Toolbar]\ninherits=PanelButtonCommand\nindicator.element=toolbar\nindicator.size=5\ntext.margin=0\ninterior.element=menubar\nframe.element=menubar\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.press.color=#dfdfdf\ntext.toggle.color=white\nframe.left=6\nframe.right=6\nframe.top=0\nframe.bottom=1\nframe.expansion=0\n\n[Slider]\ninherits=PanelButtonCommand\nframe.element=slider\nfocusFrame=true\ninterior.element=slider\nframe.top=3\nframe.bottom=3\nframe.left=3\nframe.right=3\n\n[SliderCursor]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=slidercursor\n\n[Progressbar]\ninherits=PanelButtonCommand\nframe.element=progress\ninterior.element=progress\ntext.margin=0\ntext.normal.color=#dfdfdf\ntext.focus.color=#dfdfdf\ntext.press.color=#dfdfdf\ntext.toggle.color=#dfdfdf\ntext.bold=false\nframe.expansion=8\n\n[ProgressbarContents]\ninherits=PanelButtonCommand\nframe=true\nframe.element=progress-pattern\ninterior.element=progress-pattern\n\n[ItemView]\ninherits=PanelButtonCommand\ntext.margin=0\nframe.element=itemview\ninterior.element=itemview\nframe.top=4\nframe.bottom=4\nframe.left=4\nframe.right=4\ntext.margin.top=0\ntext.margin.bottom=0\ntext.margin.left=8\ntext.margin.right=8\ntext.normal.color=#dfdfdf\ntext.focus.color=#dfdfdf\ntext.press.color=#ffffff\ntext.toggle.color=#ffffff\nmin_width=+0.3font\nmin_height=+0.3font\nframe.expansion=0\n\n[Splitter]\ninterior.element=splitter\nframe=false\nindicator.size=0\n\n[Scrollbar]\ninherits=PanelButtonCommand\nindicator.element=arrow\nindicator.size=12\n\n[ScrollbarSlider]\ninherits=PanelButtonCommand\nframe.element=scrollbarslider\ninterior=false\nframe.left=5\nframe.right=5\nframe.top=5\nframe.bottom=5\nindicator.element=grip\nindicator.size=12\n\n[ScrollbarGroove]\ninherits=PanelButtonCommand\ninterior=false\nframe=false\n\n[Menu]\ninherits=PanelButtonCommand\nframe.top=10\nframe.bottom=10\nframe.left=10\nframe.right=10\nframe.element=menu\ninterior.element=menu\ntext.normal.color=#dfdfdf\ntext.shadow=false\nframe.expansion=0\ntext.bold=false\n\n[MenuItem]\ninherits=PanelButtonCommand\nframe=true\nframe.element=menuitem\ninterior.element=menuitem\nindicator.element=menuitem\ntext.normal.color=#dfdfdf\ntext.focus.color=#ffffff\ntext.margin.top=0\ntext.margin.bottom=0\ntext.margin.left=6\ntext.margin.right=6\nframe.top=4\nframe.bottom=4\nframe.left=4\nframe.right=4\ntext.bold=false\nframe.expansion=0\n\n[MenuBar]\ninherits=PanelButtonCommand\nframe.element=menubar\ninterior.element=menubar\nframe.bottom=0\ntext.normal.color=#dfdfdf\ntext.focus.color=#ffffff\ntext.press.color=#ffffff\ntext.toggle.color=#ffffff\nframe.expansion=0\ntext.bold=false\n\n[MenuBarItem]\ninherits=PanelButtonCommand\ninterior=true\ninterior.element=menubaritem\nframe.element=menubaritem\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\ntext.margin.left=4\ntext.margin.right=4\ntext.margin.top=0\ntext.margin.bottom=0\ntext.normal.color=#dfdfdf\ntext.focus.color=#ffffff\ntext.press.color=#ffffff\ntext.toggle.color=#ffffff\ntext.bold=false\nmin_width=+0.3font\nmin_height=+0.3font\nframe.expansion=0\n\n[TitleBar]\ninherits=PanelButtonCommand\nframe=false\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\ninterior.element=titlebar\nindicator.size=16\nindicator.element=mdi\ntext.normal.color=#787878\ntext.focus.color=#dfdfdf\ntext.bold=false\ntext.italic=true\nframe.expansion=0\n\n[ComboBox]\ninherits=PanelButtonCommand\nframe.element=combo\ninterior.element=combo\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\ntext.focus.color=white\ntext.press.color=#dfdfdf\ntext.toggle.color=white\n\n[GroupBox]\ninherits=GenericFrame\nframe=false\ntext.shadow=0\ntext.margin=0\ntext.normal.color=#dfdfdf\ntext.focus.color=white\ntext.bold=false\nframe.expansion=0\n\n[TabBarFrame]\ninherits=GenericFrame\nframe=false\nframe.element=tabBarFrame\ninterior=false\nframe.top=0\nframe.bottom=0\nframe.left=0\nframe.right=0\n\n[ToolTip]\ninherits=GenericFrame\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ninterior=true\ntext.shadow=0\ntext.margin=6\ninterior.element=tooltip\nframe.element=tooltip\nframe.expansion=6\n\n[StatusBar]\ninherits=GenericFrame\nframe=false\ninterior=false\n\n[Window]\ninterior=true\ninterior.element=window\nframe=true\nframe.element=window\nframe.bottom=10\nframe.top=10\n"
  },
  {
    "path": "dots/.config/Kvantum/MaterialAdw/MaterialAdw.kvconfig",
    "content": "[%General]\nauthor=Vince Liuice, based on KvAdapta by Tsu Jan\ncomment=An uncomplicated theme inspired by the Materia GTK theme\nx11drag=none\nalt_mnemonic=true\nleft_tabs=false\nattach_active_tab=false\nmirror_doc_tabs=true\ngroup_toolbar_buttons=false\ntoolbar_item_spacing=0\ntoolbar_interior_spacing=2\nspread_progressbar=true\ncomposite=true\nmenu_shadow_depth=6\nspread_menuitems=false\ntooltip_shadow_depth=7\nsplitter_width=1\nscroll_width=9\nscroll_arrows=false\nscroll_min_extent=60\nslider_width=2\nslider_handle_width=23\nslider_handle_length=22\ntickless_slider_handle_size=22\ncenter_toolbar_handle=true\ncheck_size=24\ntextless_progressbar=false\nprogressbar_thickness=2\nmenubar_mouse_tracking=true\ntoolbutton_style=1\ndouble_click=false\ntranslucent_windows=false\nblurring=false\npopup_blurring=false\nvertical_spin_indicators=false\nspin_button_width=24\nfill_rubberband=false\nmerge_menubar_with_toolbar=true\nsmall_icon_size=16\nlarge_icon_size=32\nbutton_icon_size=16\ntoolbar_icon_size=16\ncombo_as_lineedit=true\nanimate_states=true\nbutton_contents_shift=false\ncombo_menu=true\nhide_combo_checkboxes=true\ncombo_focus_rect=false\ngroupbox_top_label=true\ninline_spin_indicators=true\njoined_inactive_tabs=false\nlayout_spacing=3\nlayout_margin=3\nscrollbar_in_view=true\ntransient_scrollbar=true\ntransient_groove=false\nsubmenu_overlap=0\ntooltip_delay=0\ntree_branch_line=false\nno_window_pattern=false\nopaque=kaffeine,kmplayer,subtitlecomposer,kdenlive,vlc,smplayer,smplayer2,avidemux,avidemux2_qt4,avidemux3_qt4,avidemux3_qt5,kamoso,QtCreator,VirtualBox,VirtualBoxVM,trojita,dragon,digikam,lyx\nreduce_window_opacity=0\nrespect_DE=true\nscrollable_menu=false\nsubmenu_delay=150\nno_inactiveness=false\nreduce_menu_opacity=0\nclick_behavior=2\ncontrast=1.00\ndialog_button_layout=0\nintensity=1.00\nsaturation=1.00\nshadowless_popup=false\ndrag_from_buttons=false\nmenu_blur_radius=0\ntooltip_blur_radius=0\n\n[GeneralColors]\nwindow.color=#0F1416\nbase.color=#0F1416\nalt.base.color=#0F1416\nbutton.color=#1B2022\nlight.color=#171C1E\nmid.light.color=#1B2022\ndark.color=#303638\nmid.color=#252B2D\nhighlight.color=#84D2E7\ninactive.highlight.color=#84D2E7\ntext.color=#DEE3E5\nwindow.text.color=#DEE3E5\nbutton.text.color=#DEE3E5\ndisabled.text.color=#DEE3E5\ntooltip.text.color=#DEE3E5\nhighlight.text.color=#DEE3E5\nlink.color=#BFC4EB\nlink.visited.color=#DDE1FF\nprogress.indicator.text.color=#DEE3E5\n\n[Hacks]\ntransparent_ktitle_label=true\ntransparent_dolphin_view=true\ntransparent_pcmanfm_sidepane=true\nblur_translucent=false\ntransparent_menutitle=true\nrespect_darkness=true\nkcapacitybar_as_progressbar=true\nforce_size_grip=true\niconless_pushbutton=false\niconless_menu=false\ndisabled_icon_opacity=100\nlxqtmainmenu_iconsize=16\nnormal_default_pushbutton=true\nsingle_top_toolbar=true\ntint_on_mouseover=0\ntransparent_pcmanfm_view=true\nno_selection_tint=true\ntransparent_arrow_button=true\nmiddle_click_scroll=false\nopaque_colors=false\nkinetic_scrolling=false\nscroll_jump_workaround=true\ncentered_forms=false\nnoninteger_translucency=false\nstyle_vertical_toolbars=false\nblur_only_active_window=true\n\n[PanelButtonCommand]\nframe=true\nframe.element=button\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ninterior=true\ninterior.element=button\nindicator.size=8\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=white\ntext.toggle.color=#ffffff\ntext.shadow=0\ntext.margin=4\ntext.iconspacing=4\nindicator.element=arrow\nframe.expansion=0\n\n[PanelButtonTool]\ninherits=PanelButtonCommand\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=white\ntext.toggle.color=#ffffff\ntext.bold=false\nindicator.element=arrow\nindicator.size=8\nframe.expansion=0\n\n[ToolbarButton]\nframe=true\nframe.element=tbutton\ninterior.element=tbutton\nframe.top=16\nframe.bottom=16\nframe.left=16\nframe.right=16\nindicator.element=tarrow\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=white\ntext.toggle.color=white\ntext.bold=false\nframe.expansion=32\n\n[Dock]\ninherits=PanelButtonCommand\ninterior.element=dock\nframe.element=dock\nframe.top=1\nframe.bottom=1\nframe.left=1\nframe.right=1\ntext.normal.color=#DEE3E5\n\n[DockTitle]\ninherits=PanelButtonCommand\nframe=false\ninterior=false\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.bold=false\n\n[IndicatorSpinBox]\ninherits=PanelButtonCommand\nframe=true\ninterior=true\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\nindicator.element=spin\nindicator.size=8\ntext.normal.color=#DEE3E5\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\n\n[RadioButton]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=radio\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\nmin_width=+0.3font\nmin_height=+0.3font\n\n[CheckBox]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=checkbox\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\nmin_width=+0.3font\nmin_height=+0.3font\n\n[Focus]\ninherits=PanelButtonCommand\nframe=true\nframe.element=focus\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\nframe.patternsize=14\n\n[GenericFrame]\ninherits=PanelButtonCommand\nframe=true\ninterior=false\nframe.element=common\ninterior.element=common\nframe.top=1\nframe.bottom=1\nframe.left=1\nframe.right=1\n\n[LineEdit]\ninherits=PanelButtonCommand\nframe.element=lineedit\ninterior.element=lineedit\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\n\n[ToolbarLineEdit]\nframe.element=lineedit\ninterior.element=lineedit\n\n[DropDownButton]\ninherits=PanelButtonCommand\nindicator.element=arrow-down\n\n[IndicatorArrow]\nindicator.element=arrow\nindicator.size=8\n\n[ToolboxTab]\ninherits=PanelButtonCommand\ntext.normal.color=#DEE3E5\ntext.press.color=#dfdfdf\ntext.focus.color=#DEE3E5\n\n[Tab]\ninherits=PanelButtonCommand\ninterior.element=tab\ntext.margin.left=8\ntext.margin.right=8\ntext.margin.top=0\ntext.margin.bottom=0\nframe.element=tab\nindicator.element=tab\nindicator.size=22\nframe.top=8\nframe.bottom=8\nframe.left=8\nframe.right=8\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=white\ntext.toggle.color=white\nframe.expansion=0\ntext.bold=false\n\n[TabFrame]\ninherits=PanelButtonCommand\nframe.element=tabframe\ninterior.element=tabframe\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\n\n[TreeExpander]\ninherits=PanelButtonCommand\nindicator.size=8\nindicator.element=tree\n\n[HeaderSection]\ninherits=PanelButtonCommand\ninterior.element=header\nframe.element=header\nframe.top=0\nframe.bottom=1\nframe.left=1\nframe.right=1\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=white\ntext.toggle.color=white\nframe.expansion=0\n\n[SizeGrip]\nindicator.element=resize-grip\n\n[Toolbar]\ninherits=PanelButtonCommand\nindicator.element=toolbar\nindicator.size=5\ntext.margin=0\ninterior.element=menubar\nframe.element=menubar\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=#dfdfdf\ntext.toggle.color=white\nframe.left=6\nframe.right=6\nframe.top=0\nframe.bottom=1\nframe.expansion=0\n\n[Slider]\ninherits=PanelButtonCommand\nframe.element=slider\nfocusFrame=true\ninterior.element=slider\nframe.top=3\nframe.bottom=3\nframe.left=3\nframe.right=3\n\n[SliderCursor]\ninherits=PanelButtonCommand\nframe=false\ninterior.element=slidercursor\n\n[Progressbar]\ninherits=PanelButtonCommand\nframe.element=progress\ninterior.element=progress\ntext.margin=0\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=#dfdfdf\ntext.toggle.color=#dfdfdf\ntext.bold=false\nframe.expansion=8\n\n[ProgressbarContents]\ninherits=PanelButtonCommand\nframe=true\nframe.element=progress-pattern\ninterior.element=progress-pattern\n\n[ItemView]\ninherits=PanelButtonCommand\ntext.margin=0\nframe.element=itemview\ninterior.element=itemview\nframe.top=4\nframe.bottom=4\nframe.left=4\nframe.right=4\ntext.margin.top=0\ntext.margin.bottom=0\ntext.margin.left=8\ntext.margin.right=8\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=#ffffff\ntext.toggle.color=#ffffff\nmin_width=+0.3font\nmin_height=+0.3font\nframe.expansion=0\n\n[Splitter]\ninterior.element=splitter\nframe=false\nindicator.size=0\n\n[Scrollbar]\ninherits=PanelButtonCommand\nindicator.element=arrow\nindicator.size=12\n\n[ScrollbarSlider]\ninherits=PanelButtonCommand\nframe.element=scrollbarslider\ninterior=false\nframe.left=5\nframe.right=5\nframe.top=5\nframe.bottom=5\nindicator.element=grip\nindicator.size=12\n\n[ScrollbarGroove]\ninherits=PanelButtonCommand\ninterior=false\nframe=false\n\n[Menu]\ninherits=PanelButtonCommand\nframe.top=10\nframe.bottom=10\nframe.left=10\nframe.right=10\nframe.element=menu\ninterior.element=menu\ntext.normal.color=#DEE3E5\ntext.shadow=false\nframe.expansion=0\ntext.bold=false\n\n[MenuItem]\ninherits=PanelButtonCommand\nframe=true\nframe.element=menuitem\ninterior.element=menuitem\nindicator.element=menuitem\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.margin.top=0\ntext.margin.bottom=0\ntext.margin.left=6\ntext.margin.right=6\nframe.top=4\nframe.bottom=4\nframe.left=4\nframe.right=4\ntext.bold=false\nframe.expansion=0\n\n[MenuBar]\ninherits=PanelButtonCommand\nframe.element=menubar\ninterior.element=menubar\nframe.bottom=0\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=#ffffff\ntext.toggle.color=#ffffff\nframe.expansion=0\ntext.bold=false\n\n[MenuBarItem]\ninherits=PanelButtonCommand\ninterior=true\ninterior.element=menubaritem\nframe.element=menubaritem\nframe.top=2\nframe.bottom=2\nframe.left=2\nframe.right=2\ntext.margin.left=4\ntext.margin.right=4\ntext.margin.top=0\ntext.margin.bottom=0\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.press.color=#ffffff\ntext.toggle.color=#ffffff\ntext.bold=false\nmin_width=+0.3font\nmin_height=+0.3font\nframe.expansion=0\n\n[TitleBar]\ninherits=PanelButtonCommand\nframe=false\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\ninterior.element=titlebar\nindicator.size=16\nindicator.element=mdi\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.bold=false\ntext.italic=true\nframe.expansion=0\n\n[ComboBox]\ninherits=PanelButtonCommand\nframe.element=combo\ninterior.element=combo\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ntext.margin.top=2\ntext.margin.bottom=2\ntext.margin.left=2\ntext.margin.right=2\ntext.focus.color=#DEE3E5\ntext.press.color=#dfdfdf\ntext.toggle.color=white\n\n[GroupBox]\ninherits=GenericFrame\nframe=false\ntext.shadow=0\ntext.margin=0\ntext.normal.color=#DEE3E5\ntext.focus.color=#DEE3E5\ntext.bold=false\nframe.expansion=0\n\n[TabBarFrame]\ninherits=GenericFrame\nframe=false\nframe.element=tabBarFrame\ninterior=false\nframe.top=0\nframe.bottom=0\nframe.left=0\nframe.right=0\n\n[ToolTip]\ninherits=GenericFrame\nframe.top=6\nframe.bottom=6\nframe.left=6\nframe.right=6\ninterior=true\ntext.shadow=0\ntext.margin=6\ninterior.element=tooltip\nframe.element=tooltip\nframe.expansion=6\n\n[StatusBar]\ninherits=GenericFrame\nframe=false\ninterior=false\n\n[Window]\ninterior=true\ninterior.element=window\nframe=true\nframe.element=window\nframe.bottom=10\nframe.top=10\n\ntext.disabled.color=#0F1416\n"
  },
  {
    "path": "dots/.config/Kvantum/kvantum.kvconfig",
    "content": "[General]\ntheme=MaterialAdw\n"
  },
  {
    "path": "dots/.config/chrome-flags.conf",
    "content": "--password-store=gnome-libsecret\n--ozone-platform-hint=wayland\n--gtk-version=4\n--ignore-gpu-blocklist\n--enable-features=TouchpadOverscrollHistoryNavigation\n--enable-wayland-ime\n--disable-features=ExtensionManifestV2Unsupported\n"
  },
  {
    "path": "dots/.config/code-flags.conf",
    "content": "--ozone-platform-hint=wayland\n--gtk-version=4\n--ignore-gpu-blocklist\n--enable-features=TouchpadOverscrollHistoryNavigation\n--enable-wayland-ime\n--password-store=gnome-libsecret\n"
  },
  {
    "path": "dots/.config/darklyrc",
    "content": "[Style]\nWidgetDrawShadow=false\n"
  },
  {
    "path": "dots/.config/dolphinrc",
    "content": "[DetailsMode]\nPreviewSize=32\n\n[General]\nGlobalViewProps=false\nShowFullPath=true\nShowStatusBar=FullWidth\nVersion=202\n\n[KFileDialog Settings]\nPlaces Icons Auto-resize=false\nPlaces Icons Static Size=22\n\n[MainWindow]\nMenuBar=Disabled\n\n[PreviewSettings]\nPlugins=ffmpegthumbnailer,appimagethumbnail,audiothumbnail,avif,blenderthumbnail,comicbookthumbnail,cursorthumbnail,djvuthumbnail,ebookthumbnail,exrthumbnail,directorythumbnail,fontthumbnail,heif,imagethumbnail,jpegthumbnail,jxl,kraorathumbnail,windowsexethumbnail,windowsimagethumbnail,mobithumbnail,opendocumentthumbnail,gsthumbnail,rawthumbnail,librsvg,svgthumbnail,ffmpegthumbs,gdk-pixbuf-thumbnailer,gsf-office\n"
  },
  {
    "path": "dots/.config/fish/auto-Hypr.fish",
    "content": "# Auto start Hyprland on tty1\nif test -z \"$DISPLAY\" ;and test \"$XDG_VTNR\" -eq 1\n    mkdir -p ~/.cache\n    exec start-hyprland > ~/.cache/hyprland.log 2>&1\nend\n"
  },
  {
    "path": "dots/.config/fish/config.fish",
    "content": "# Commands to run in interactive sessions can go here\nif status is-interactive\n    # No greeting\n    set fish_greeting\n\n    # Use starship\n    function starship_transient_prompt_func\n        starship module character\n    end\n    if test \"$TERM\" != \"linux\"\n        starship init fish | source\n        enable_transience\n    end\n    \n    # Colors\n    if test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt\n        cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt\n    end\n\n    # Aliases\n    # kitty doesn't clear properly so we need to do this weird printing\n    alias clear \"printf '\\033[2J\\033[3J\\033[1;1H'\"\n    alias celar \"printf '\\033[2J\\033[3J\\033[1;1H'\"\n    alias claer \"printf '\\033[2J\\033[3J\\033[1;1H'\"\n    alias pamcan pacman\n    alias q 'qs -c ii'\n    if test \"$TERM\" != \"linux\"\n        alias ls 'eza --icons'\n    end\n    if test \"$TERM\" = \"xterm-kitty\"\n        alias ssh 'kitten ssh'\n    end\nend\n"
  },
  {
    "path": "dots/.config/fish/fish_variables",
    "content": "# This file contains fish universal variable definitions.\n# VERSION: 3.0\nSETUVAR __fish_initialized:3400\nSETUVAR _fisher_jorgebucaran_2F_fisher_files:\\x7e/\\x2econfig/fish/functions/fisher\\x2efish\\x1e\\x7e/\\x2econfig/fish/completions/fisher\\x2efish\nSETUVAR _fisher_plugins:jorgebucaran/fisher\nSETUVAR _fisher_upgraded_to_4_4:\\x1d\nSETUVAR fish_color_autosuggestion:555\\x1ebrblack\nSETUVAR fish_color_cancel:\\x2dr\nSETUVAR fish_color_command:blue\nSETUVAR fish_color_comment:red\nSETUVAR fish_color_cwd:green\nSETUVAR fish_color_cwd_root:red\nSETUVAR fish_color_end:green\nSETUVAR fish_color_error:brred\nSETUVAR fish_color_escape:brcyan\nSETUVAR fish_color_history_current:\\x2d\\x2dbold\nSETUVAR fish_color_host:normal\nSETUVAR fish_color_host_remote:yellow\nSETUVAR fish_color_normal:normal\nSETUVAR fish_color_operator:brcyan\nSETUVAR fish_color_param:cyan\nSETUVAR fish_color_quote:yellow\nSETUVAR fish_color_redirection:cyan\\x1e\\x2d\\x2dbold\nSETUVAR fish_color_search_match:\\x2d\\x2dbackground\\x3d111\nSETUVAR fish_color_selection:white\\x1e\\x2d\\x2dbold\\x1e\\x2d\\x2dbackground\\x3dbrblack\nSETUVAR fish_color_status:red\nSETUVAR fish_color_user:brgreen\nSETUVAR fish_color_valid_path:\\x2d\\x2dunderline\nSETUVAR fish_key_bindings:fish_default_key_bindings\nSETUVAR fish_pager_color_completion:normal\nSETUVAR fish_pager_color_description:B3A06D\\x1eyellow\\x1e\\x2di\nSETUVAR fish_pager_color_prefix:cyan\\x1e\\x2d\\x2dbold\\x1e\\x2d\\x2dunderline\nSETUVAR fish_pager_color_progress:brwhite\\x1e\\x2d\\x2dbackground\\x3dcyan\nSETUVAR fish_pager_color_selected_background:\\x2dr\n"
  },
  {
    "path": "dots/.config/fontconfig/fonts.conf",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE fontconfig SYSTEM \"urn:fontconfig:fonts.dtd\">\n<fontconfig>\n    <match target=\"font\">\n        <edit name=\"rgba\" mode=\"assign\">\n        <const>none</const>\n    </edit>\n  </match>\n</fontconfig>\n"
  },
  {
    "path": "dots/.config/foot/foot.ini",
    "content": "shell=fish\nterm=xterm-256color\n\ntitle=foot\n\nfont=JetBrainsMono Nerd Font:size=11\nletter-spacing=0\ndpi-aware=no\n\npad=25x25\n\nbold-text-in-bright=no\n\n[scrollback]\nlines=10000\n\n[cursor]\nstyle=beam\nblink=no\nbeam-thickness=1.5\n# underline-thickness=<font underline thickness>\n\n[key-bindings]\nscrollback-up-page=Page_Up\nscrollback-down-page=Page_Down\nclipboard-copy=Control+c\nclipboard-paste=Control+v\n# primary-paste=Shift+Insert\nsearch-start=Control+f\nfont-increase=Control+plus Control+equal Control+KP_Add\nfont-decrease=Control+minus Control+KP_Subtract\nfont-reset=Control+0 Control+KP_0\n\n[search-bindings]\ncancel=Escape\nfind-prev=Shift+F3\nfind-next=F3 Control+G\ndelete-prev-word=Control+BackSpace\n\n[text-bindings]\n\\x03=Control+Shift+c\n"
  },
  {
    "path": "dots/.config/fuzzel/fuzzel.ini",
    "content": "include=\"~/.config/fuzzel/fuzzel_theme.ini\"\nfont=Google Sans Flex:weight=medium\nterminal=kitty -1\nprompt=\">>  \"\nlayer=overlay\n\n[border]\nradius=17\nwidth=1\n\n[dmenu]\nexit-immediately-if-empty=yes\n"
  },
  {
    "path": "dots/.config/fuzzel/fuzzel_theme.ini",
    "content": "[colors]\nbackground=161217ff\ntext=e9e0e8ff\nselection=4b454dff\nselection-text=cdc3ceff\nborder=4b454ddd\nmatch=dfb8f6ff\nselection-match=dfb8f6ff\n"
  },
  {
    "path": "dots/.config/hypr/custom/env.lua",
    "content": "\n"
  },
  {
    "path": "dots/.config/hypr/custom/execs.lua",
    "content": "\n"
  },
  {
    "path": "dots/.config/hypr/custom/general.lua",
    "content": "\n"
  },
  {
    "path": "dots/.config/hypr/custom/keybinds.lua",
    "content": "hl.bind(\"CTRL+SUPER+ALT+Slash\", hl.dsp.exec_cmd(\"xdg-open ~/.config/hypr/custom/keybinds.lua\"), {description = \"Edit user keybinds\"} )\n"
  },
  {
    "path": "dots/.config/hypr/custom/rules.lua",
    "content": "\n"
  },
  {
    "path": "dots/.config/hypr/custom/scripts/__restore_video_wallpaper.sh",
    "content": "#!/bin/bash\n# The content of this script will be generated by switchwall.sh - Don't modify it by yourself.\n"
  },
  {
    "path": "dots/.config/hypr/custom/variables.lua",
    "content": "\n"
  },
  {
    "path": "dots/.config/hypr/hypridle.conf",
    "content": "$lock_cmd = hyprctl dispatch 'hl.dsp.global(\"quickshell:lock\")' & pidof qs quickshell hyprlock || hyprlock\n# $lock_cmd = pidof hyprlock || hyprlock\n$suspend_cmd = systemctl suspend || loginctl suspend\n\ngeneral {\n    lock_cmd = $lock_cmd\n    before_sleep_cmd = loginctl lock-session\n    after_sleep_cmd = hyprctl dispatch global quickshell:lockFocus\n    inhibit_sleep = 3\n}\n\nlistener {\n    timeout = 300 # 5mins\n    on-timeout = loginctl lock-session\n}\n\nlistener {\n    timeout = 600 # 10mins\n    on-timeout = hyprctl dispatch dpms off\n    on-resume = hyprctl dispatch dpms on\n}\n\nlistener {\n    timeout = 900 # 15mins\n    on-timeout = $suspend_cmd\n}\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/colors.lua",
    "content": "hl.config({\n    general = {\n        col = {\n            active_border   = \"rgba(44464f77)\",\n            inactive_border = \"rgba(1a1b2033)\",\n        },\n    },\n    misc = {\n        background_color = \"rgba(121318FF)\",\n    },\n})\n\nhl.window_rule({ -- not sure how to syntax \"pin 1\"\n    match        = { pin = 1 },\n    border_color = \"rgba(afc6ffAA) rgba(afc6ff77)\",\n})\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/env.lua",
    "content": "local home_dir = os.getenv(\"HOME\")\n\n-- Wayland\nhl.env(\"ELECTRON_OZONE_PLATFORM_HINT\", \"auto\")\n\n-- Applications\nlocal xdg_data_dirs_old = os.getenv(\"XDG_DATA_DIRS\") or \"\"\nhl.env(\"XDG_DATA_DIRS\", home_dir .. \"/.local/share/flatpak/exports/share:/var/lib/flatpak/exports/share:/usr/local/share:/usr/share:\" .. xdg_data_dirs_old)\n\n-- Themes\nhl.env(\"QT_QPA_PLATFORM\", \"wayland;xcb\")\nhl.env(\"QT_QPA_PLATFORMTHEME\", \"kde\")\nhl.env(\"XDG_MENU_PREFIX\", \"plasma-\")\n\n-- Virtual environment\nhl.env(\"ILLOGICAL_IMPULSE_VIRTUAL_ENV\", home_dir .. \"/.local/state/quickshell/.venv\")\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/execs.lua",
    "content": "-- put former exec-once commands inside the func and former exec commands outside\nhl.on(\"hyprland.start\", function ()\n\n    -- Bar, wallpaper\n    hl.exec_cmd(\"$HOME/.config/hypr/hyprland/scripts/start_geoclue_agent.sh\")\n    hl.exec_cmd(\"qs -c $qsConfig\")\n    hl.exec_cmd(\"$HOME/.config/hypr/custom/scripts/__restore_video_wallpaper.sh\")\n\n    -- Core components (authentication, lock screen, notification daemon)\n    hl.exec_cmd(\"gnome-keyring-daemon --start --components=secrets\")\n    hl.exec_cmd(\"hypridle\")\n    hl.exec_cmd(\"dbus-update-activation-environment --all\")\n    hl.exec_cmd(\"sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP\") -- Some fix idk\n\n    -- Audio\n    hl.exec_cmd(\"easyeffects --hide-window --service-mode\")\n\n    -- Clipboard: history\n    --hl.exec_cmd(\"wl-paste --watch cliphist store\")\n    hl.exec_cmd(\"wl-paste --type text --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update'\")\n    hl.exec_cmd(\"wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update'\")\n\n    -- Cursor\n    hl.exec_cmd(\"hyprctl setcursor Bibata-Modern-Classic 24\")\nend)\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/general.lua",
    "content": "-- MONITOR CONFIG\nhl.monitor({\n    output = \"\",\n    mode = \"preferred\",\n    position = \"auto\",\n    scale = \"1\"\n})\n\nhl.gesture({\n    fingers = 3,\n    direction = \"swipe\",\n    action = \"move\"\n})\nhl.gesture({\n    fingers = 3,\n    direction = \"pinch\",\n    action = \"fullscreen\"\n})\nhl.gesture({\n    fingers = 4,\n    direction = \"horizontal\",\n    action = \"workspace\"\n})\nhl.gesture({\n    fingers = 4,\n    direction = \"up\",\n    action = function()\n        hl.dispatch(hl.dsp.global(\"quickshell:overviewWorkspacesToggle\"))\n    end\n})\nhl.gesture({\n    fingers = 4,\n    direction = \"down\",\n    action = function()\n        hl.dispatch(hl.dsp.global(\"quickshell:overviewWorkspacesToggle\"))\n    end\n})\n\nhl.config({\n    gestures = {\n        workspace_swipe_distance = 700,\n        workspace_swipe_cancel_ratio = 0.2,\n        workspace_swipe_min_speed_to_force = 5,\n        workspace_swipe_direction_lock = true,\n        workspace_swipe_direction_lock_threshold = 10,\n        workspace_swipe_create_new = true\n    },\n    general = {\n        -- Gaps and border\n        gaps_in = 4,\n        gaps_out = 5,\n        gaps_workspaces = 50,\n\n        border_size = 1,\n\n        col = {\n            active_border = \"rgba(0DB7D455)\",\n            inactive_border = \"rgba(31313600)\"\n        },\n        resize_on_border = true,\n\n        no_focus_fallback = true,\n        allow_tearing = true, -- This just allows the `immediate` window rule to work\n        snap = {\n            enabled = true,\n            window_gap = 4,\n            monitor_gap = 5,\n            respect_gaps = true\n        }\n    },\n    decoration = {\n        -- 2 = circle, higher = squircle, 4 = very obvious squircle\n        -- Fuck clearly visible squircles. 100% Apple brainrot.\n        rounding_power = 2.5,\n        rounding = 18,\n\n        blur = {\n            enabled = true,\n            xray = true,\n            special = false,\n            new_optimizations = true,\n            size = 10,\n            passes = 3,\n            brightness = 1,\n            noise = 0.05,\n            contrast = 0.89,\n            vibrancy = 0.5,\n            vibrancy_darkness = 0.5,\n            popups = false,\n            popups_ignorealpha = 0.6,\n            input_methods = true,\n            input_methods_ignorealpha = 0.8\n        },\n        shadow = {\n            enabled = true,\n            range = 20,\n            offset = {0, 2},\n            render_power = 10,\n            color = \"rgba(00000020)\"\n\n        },\n        -- Dim\n        dim_inactive = true,\n        dim_strength = 0.05,\n        dim_special = 0.2\n    },\n    animations = {\n        enabled = true\n    },\n    dwindle = {\n        preserve_split = true,\n        smart_split = false,\n        smart_resizing = false\n        -- precise_mouse_move = true,\n    },\n})\n-- Curves\nhl.curve(\"expressiveFastSpatial\", {\n    type = \"bezier\",\n    points = {{0.42, 1.67}, {0.21, 0.90}}\n})\nhl.curve(\"expressiveSlowSpatial\", {\n    type = \"bezier\",\n    points = {{0.39, 1.29}, {0.35, 0.98}}\n})\nhl.curve(\"expressiveDefaultSpatial\", {\n    type = \"bezier\",\n    points = {{0.38, 1.21}, {0.22, 1.00}}\n})\nhl.curve(\"emphasizedDecel\", {\n    type = \"bezier\",\n    points = {{0.05, 0.7}, {0.1, 1}}\n})\nhl.curve(\"emphasizedAccel\", {\n    type = \"bezier\",\n    points = {{0.3, 0}, {0.8, 0.15}}\n})\nhl.curve(\"standardDecel\", {\n    type = \"bezier\",\n    points = {{0, 0}, {0, 1}}\n})\nhl.curve(\"menu_decel\", {\n    type = \"bezier\",\n    points = {{0.1, 1}, {0, 1}}\n})\nhl.curve(\"menu_accel\", {\n    type = \"bezier\",\n    points = {{0.52, 0.03}, {0.72, 0.08}}\n})\nhl.curve(\"stall\", {\n    type = \"bezier\",\n    points = {{1, -0.1}, {0.7, 0.85}}\n})\n-- Configs\n-- windows\nhl.animation({\n    leaf = \"windowsIn\",\n    enabled = true,\n    speed = 3,\n    bezier = \"emphasizedDecel\",\n    style = \"popin 80%\"\n})\nhl.animation({\n    leaf = \"fadeIn\",\n    enabled = true,\n    speed = 3,\n    bezier = \"emphasizedDecel\"\n})\nhl.animation({\n    leaf = \"windowsOut\",\n    enabled = true,\n    speed = 2,\n    bezier = \"emphasizedDecel\",\n    style = \"popin 90%\"\n})\nhl.animation({\n    leaf = \"fadeOut\",\n    enabled = true,\n    speed = 2,\n    bezier = \"emphasizedDecel\"\n})\nhl.animation({\n    leaf = \"windowsMove\",\n    enabled = true,\n    speed = 3,\n    bezier = \"emphasizedDecel\",\n    style = \"slide\"\n})\nhl.animation({\n    leaf = \"border\",\n    enabled = true,\n    speed = 10,\n    bezier = \"emphasizedDecel\"\n})\n\n-- layers\nhl.animation({\n    leaf = \"layersIn\",\n    enabled = true,\n    speed = 2.7,\n    bezier = \"emphasizedDecel\",\n    style = \"popin 93%\"\n})\nhl.animation({\n    leaf = \"layersOut\",\n    enabled = true,\n    speed = 2.4,\n    bezier = \"menu_accel\",\n    style = \"popin 94%\"\n})\n-- fade\nhl.animation({\n    leaf = \"fadeLayersIn\",\n    enabled = true,\n    speed = 0.5,\n    bezier = \"menu_decel\"\n})\nhl.animation({\n    leaf = \"fadeLayersOut\",\n    enabled = true,\n    speed = 2.7,\n    bezier = \"stall\"\n})\n-- workspaces\nhl.animation({\n    leaf = \"workspaces\",\n    enabled = true,\n    speed = 7,\n    bezier = \"menu_decel\",\n    style = \"slide\"\n})\n-- specialWorkspace\nhl.animation({\n    leaf = \"specialWorkspaceIn\",\n    enabled = true,\n    speed = 2.8,\n    bezier = \"emphasizedDecel\",\n    style = \"slidevert\"\n})\nhl.animation({\n    leaf = \"specialWorkspaceOut\",\n    enabled = true,\n    speed = 1.2,\n    bezier = \"emphasizedAccel\",\n    style = \"slidevert\"\n})\n-- zoom\nhl.animation({\n    leaf = \"zoomFactor\",\n    enabled = true,\n    speed = 3,\n    bezier = \"standardDecel\"\n})\n\nhl.config({\n    input = {\n        kb_layout = \"us\",\n        numlock_by_default = true,\n        repeat_delay = 250,\n        repeat_rate = 35,\n\n        follow_mouse = 1,\n        off_window_axis_events = 2,\n\n        touchpad = {\n            natural_scroll = true,\n            disable_while_typing = true,\n            clickfinger_behavior = true,\n            scroll_factor = 0.7\n        }\n    },\n\n    misc = {\n        disable_hyprland_logo = true,\n        disable_splash_rendering = true,\n        vrr = 0,\n        mouse_move_enables_dpms = true,\n        key_press_enables_dpms = true,\n        animate_manual_resizes = false,\n        animate_mouse_windowdragging = false,\n        enable_swallow = false,\n        swallow_regex = \"(foot|kitty|allacritty|Alacritty)\",\n        on_focus_under_fullscreen = 2,\n        allow_session_lock_restore = true,\n        session_lock_xray = true,\n        initial_workspace_tracking = false,\n        focus_on_activate = true\n    },\n\n    binds = {\n        scroll_event_delay = 0,\n        hide_special_on_workspace_change = true\n    },\n\n    cursor = {\n        zoom_factor = 1,\n        zoom_rigid = false,\n        zoom_disable_aa = true,\n        hotspot_padding = 1\n    },\n\n    xwayland = {\n        force_zero_scaling = true\n    }\n})\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/keybinds.lua",
    "content": "require(\"hyprland.lib\")\nrequire(\"hyprland.variables\")\nif is_file_exists(HOME .. \"/.config/hypr/custom/variables.lua\") then\n    require(\"custom.variables\")\nend\n\nlocal qsScripts = \"$HOME/.config/quickshell/$qsConfig/scripts\"\nlocal hyprScripts = \"$HOME/.config/hypr/hyprland/scripts\"\nlocal qsIpcCall = \"qs -c $qsConfig ipc call\"\nlocal qsIsAlive = qsIpcCall .. \" TEST_ALIVE\"\n\nhl.bind(\"SUPER + SUPER_L\", hl.dsp.global(\"quickshell:searchToggleRelease\"), { description = \"Shell: Toggle search\" })\nhl.bind(\"SUPER + SUPER_R\", hl.dsp.global(\"quickshell:searchToggleRelease\"))\nhl.bind(\"SUPER + SUPER_L\", hl.dsp.exec_cmd(qsIsAlive .. \" || pkill fuzzel || fuzzel\"))\nhl.bind(\"SUPER + SUPER_R\", hl.dsp.exec_cmd(qsIsAlive .. \" || pkill fuzzel || fuzzel\"))\n\nhl.bind(\"SUPER_L\", hl.dsp.global(\"quickshell:workspaceNumber\"), { ignore_mods = true, transparent = true })\nhl.bind(\"SUPER_R\", hl.dsp.global(\"quickshell:workspaceNumber\"), { ignore_mods = true, transparent = true })\nhl.bind(\"SUPER_L\", hl.dsp.global(\"quickshell:workspaceNumber\"),\n    { ignore_mods = true, transparent = true, release = true })\nhl.bind(\"SUPER_R\", hl.dsp.global(\"quickshell:workspaceNumber\"),\n    { ignore_mods = true, transparent = true, release = true })\nhl.bind(\"SUPER + Tab\", hl.dsp.global(\"quickshell:overviewWorkspacesToggle\"), { description = \"Shell: Toggle overview\" })\nhl.bind(\"SUPER + V\", hl.dsp.global(\"quickshell:overviewClipboardToggle\"))\nhl.bind(\"SUPER + Period\", hl.dsp.global(\"quickshell:overviewEmojiToggle\"))\nhl.bind(\"SUPER + A\", hl.dsp.global(\"quickshell:sidebarLeftToggle\"), { description = \"Shell: Toggle left sidebar\" })\nhl.bind(\"SUPER + ALT + A\", hl.dsp.global(\"quickshell:sidebarLeftToggleDetach\"))\nhl.bind(\"SUPER + B\", hl.dsp.global(\"quickshell:sidebarLeftToggle\"))\nhl.bind(\"SUPER + O\", hl.dsp.global(\"quickshell:sidebarLeftToggle\"))\nhl.bind(\"SUPER + N\", hl.dsp.global(\"quickshell:sidebarRightToggle\"), { description = \"Shell: Toggle right sidebar\" })\nhl.bind(\"SUPER + Slash\", hl.dsp.global(\"quickshell:cheatsheetToggle\"), { description = \"Shell: Toggle cheatsheet\" })\nhl.bind(\"SUPER + K\", hl.dsp.global(\"quickshell:oskToggle\"), { description = \"Shell: Toggle on-screen keyboard\" })\nhl.bind(\"SUPER + M\", hl.dsp.global(\"quickshell:mediaControlsToggle\"), { description = \"Shell: Toggle media controls\" })\nhl.bind(\"SUPER + G\", hl.dsp.global(\"quickshell:overlayToggle\"), { description = \"Shell: Toggle widget overlay\" })\nhl.bind(\"CTRL + ALT + Delete\", hl.dsp.global(\"quickshell:sessionToggle\"), { description = \"Shell: Toggle session menu\" })\nhl.bind(\"SUPER + J\", hl.dsp.global(\"quickshell:barToggle\"), { description = \"Shell: Toggle bar\" })\nhl.bind(\"CTRL + ALT + Delete\", hl.dsp.exec_cmd(qsIsAlive .. \" || pkill wlogout || wlogout -p layer-shell\"))\nhl.bind(\"SHIFT + SUPER + ALT + Slash\", hl.dsp.exec_cmd(\"qs -p $HOME/.config/quickshell/$qsConfig/welcome.qml\"))\n\nhl.bind(\"XF86MonBrightnessUp\", hl.dsp.exec_cmd(qsIpcCall .. \" brightness increment || brightnessctl s 5%+\"),\n    { locked = true, repeating = true })\nhl.bind(\"XF86MonBrightnessDown\", hl.dsp.exec_cmd(qsIpcCall .. \" brightness decrement || brightnessctl s 5%-\"),\n    { locked = true, repeating = true })\nhl.bind(\"XF86AudioRaiseVolume\", hl.dsp.exec_cmd(\"wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%+ -l 1.5\"),\n    { locked = true, repeating = true })\nhl.bind(\"XF86AudioLowerVolume\", hl.dsp.exec_cmd(\"wpctl set-volume @DEFAULT_AUDIO_SINK@ 2%-\"),\n    { locked = true, repeating = true })\n\nhl.bind(\"CTRL + SUPER + T\", hl.dsp.global(\"quickshell:wallpaperSelectorToggle\"),\n    { description = \"Shell: Change wallpaper\" })\nhl.bind(\"CTRL + SUPER + ALT + T\", hl.dsp.global(\"quickshell:wallpaperSelectorRandom\"),\n    { description = \"Shell: Random wallpaper\" })\nhl.bind(\"CTRL + SUPER + SHIFT + D\", hl.dsp.global(\"quickshell:toggleLightDark\"),\n    { description = \"Shell: Toggle light/dark mode\" })\nhl.bind(\"CTRL + SUPER + T\", hl.dsp.exec_cmd(qsIsAlive .. \" || \" .. qsScripts .. \"/colors/switchwall.sh\"))\nhl.bind(\"CTRL + SUPER + R\", hl.dsp.exec_cmd(\"killall ydotool qs quickshell; qs -c $qsConfig &\"),\n    { description = \"Shell: Restart widgets\" })\nhl.bind(\"CTRL + SUPER + P\", hl.dsp.global(\"quickshell:panelFamilyCycle\"), { description = \"Shell: Cycle panel family\" })\n\n--##! Utilities\n--# Screenshot, Record, OCR, Color picker, Clipboard history\nhl.bind(\"SUPER + V\", hl.dsp.exec_cmd(\n        qsIsAlive .. \" || pkill fuzzel || cliphist list | fuzzel --match-mode fzf --dmenu | cliphist decode | wl-copy\"),\n    { description = \"Utilities: Clipboard history >> clipboard\" })\nhl.bind(\"SUPER + Period\", hl.dsp.exec_cmd(\n        qsIsAlive .. \" || pkill fuzzel || \" .. hyprScripts .. \"/fuzzel-emoji.sh copy\"),\n    { description = \"Utilities: Emoji >> clipboard\" })\nhl.bind(\"SUPER + SHIFT + S\", hl.dsp.global(\"quickshell:regionScreenshot\"), { description = \"Utilities: Screen snip\" })\nhl.bind(\"SUPER + SHIFT + S\",\n    hl.dsp.exec_cmd(qsIsAlive .. \" || pidof slurp || hyprshot --freeze --clipboard-only --mode region --silent\"))\nhl.bind(\"SUPER + SHIFT + A\", hl.dsp.global(\"quickshell:regionSearch\"), { description = \"Utilities: Google Lens\" })\nhl.bind(\"SUPER + SHIFT + A\", hl.dsp.exec_cmd(qsIsAlive .. \" || pidof slurp || \" .. hyprScripts .. \"/snip_to_search.sh\"))\n--# OCR\nhl.bind(\"SUPER + SHIFT + X\", hl.dsp.global(\"quickshell:regionOcr\"),\n    { description = \"Utilities: Character recognition >> clipboard\" })\nhl.bind(\"SUPER + SHIFT + T\", hl.dsp.global(\"quickshell:screenTranslate\"),\n    { description = \"Utilities: Translate screen content\" })\nhl.bind(\"SUPER + SHIFT + X\", hl.dsp.exec_cmd(\n    qsIsAlive ..\n    \" || pidof slurp || grim -g \\\"$(slurp $SLURP_ARGS)\\\" \\\"/tmp/ocr_image.png\\\" && tesseract \\\"/tmp/ocr_image.png\\\" stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\\\\\\\n' '+' | sed 's/\\\\\\\\+$/\\\\\\\\n/') | wl-copy && rm \\\"/tmp/ocr_image.png\\\"\"\n))\n--# Color picker\nhl.bind(\"SUPER + SHIFT + C\", hl.dsp.exec_cmd(\"hyprpicker -a\"),\n    { description = \"Utilities: Pick color #RRGGBB >> clipboard\" })\n--# Recording stuff\nhl.bind(\"SUPER + SHIFT + R\", hl.dsp.global(\"quickshell:regionRecord\"),\n    { locked = true, description = \"Utilities: Record region (no sound)\" })\nhl.bind(\"SUPER + SHIFT + R\", hl.dsp.exec_cmd(qsIsAlive .. \" || \" .. qsScripts .. \"/videos/record.sh\"), { locked = true })\nhl.bind(\"SUPER + ALT + R\", hl.dsp.global(\"quickshell:regionRecord\"), { locked = true })\nhl.bind(\"SUPER + ALT + R\", hl.dsp.exec_cmd(qsIsAlive .. \" || \" .. qsScripts .. \"/videos/record.sh\"), { locked = true })\nhl.bind(\"CTRL + ALT + R\", hl.dsp.exec_cmd(qsScripts .. \"/videos/record.sh --fullscreen\"), { locked = true })\nhl.bind(\"SUPER + SHIFT + ALT + R\", hl.dsp.exec_cmd(qsScripts .. \"/videos/record.sh --fullscreen --sound\"),\n    { locked = true, description = \"Utilities: Record screen (with sound)\" })\n--# Fullscreen screenshot\nlocal grimhyprctl = \"grim -o \\\"$(hyprctl activeworkspace -j | jq -r '.monitor')\\\"\"\nhl.bind(\"Print\", hl.dsp.exec_cmd(grimhyprctl .. \" - | wl-copy\"),\n    { locked = true, description = \"Utilities: Screenshot >> clipboard\" })\nhl.bind(\"CTRL + Print\", hl.dsp.exec_cmd(\n    \"mkdir -p $(xdg-user-dir PICTURES)/Screenshots && \" ..\n    grimhyprctl .. \" $(xdg-user-dir PICTURES)/Screenshots/Screenshot_\\\"$(date '+%Y-%m-%d_%H.%M.%S')\\\".png\"\n), { locked = true, non_consuming = true, description = \"Utilities: Screenshot >> clipboard & file\" })\nhl.bind(\"CTRL + Print\", hl.dsp.exec_cmd(grimhyprctl .. \" - | wl-copy\"), { locked = true, non_consuming = true })\n--# AI\nhl.bind(\"SUPER + SHIFT + ALT + mouse:273\", hl.dsp.exec_cmd(hyprScripts .. \"/ai/primary-buffer-query.sh\"),\n    { description = \"Utilities: Generate AI summary for selected text\" })\n-- (requires a running ollama model)\n\n--##! Screen\n--# Zoom\nlocal function zoomfunction(value)\n    local zoomvalue = hl.get_config(\"cursor:zoom_factor\")\n    if (zoomvalue + value) > 3.0 then\n        hl.config({ cursor = { zoom_factor = 3.0 } })\n    elseif (zoomvalue + value) < 1.0 then\n        hl.config({ cursor = { zoom_factor = 1.0 } })\n    else\n        hl.config({ cursor = { zoom_factor = zoomvalue + value } })\n    end\nend\nhl.bind(\"SUPER + Minus\", function() zoomfunction(-0.3) end, { repeating = true, description = \"Screen: Zoom out\" })\nhl.bind(\"SUPER + Equal\", function() zoomfunction(0.3) end, { repeating = true, description = \"Screen: Zoom in\" })\n\n--# Zoom with keypad\nhl.bind(\"SUPER + code:82\", function() zoomfunction(-0.3) end, { repeating = true })\nhl.bind(\"SUPER + code:86\", function() zoomfunction(0.3) end, { repeating = true })\n\n--##! Media\nlocal mediaNextCommand =\n\"playerctl next || playerctl position `bc <<< \\\"100 * $(playerctl metadata mpris:length) / 1000000 / 100\\\"`\"\nhl.bind(\"SUPER + SHIFT + N\", hl.dsp.exec_cmd(mediaNextCommand), { locked = true, description = \"Media: Next track\" })\nhl.bind(\"XF86AudioNext\", hl.dsp.exec_cmd(mediaNextCommand), { locked = true })\nhl.bind(\"XF86AudioPrev\", hl.dsp.exec_cmd(\"playerctl previous\"), { locked = true })\nhl.bind(\"SUPER + SHIFT + ALT + mouse:275\", hl.dsp.exec_cmd(\"playerctl previous\"))\nhl.bind(\"SUPER + SHIFT + ALT + mouse:276\", hl.dsp.exec_cmd(mediaNextCommand))\nhl.bind(\"SUPER + SHIFT + B\", hl.dsp.exec_cmd(\"playerctl previous\"),\n    { locked = true, description = \"Media: Previous track\" })\nhl.bind(\"SUPER + SHIFT + P\", hl.dsp.exec_cmd(\"playerctl play-pause\"),\n    { locked = true, description = \"Media: Play/pause media\" })\nhl.bind(\"XF86AudioPlay\", hl.dsp.exec_cmd(\"playerctl play-pause\"), { locked = true })\nhl.bind(\"XF86AudioPause\", hl.dsp.exec_cmd(\"playerctl play-pause\"), { locked = true })\nhl.bind(\"XF86AudioMute\", hl.dsp.exec_cmd(\"wpctl set-mute @DEFAULT_SINK@ toggle\"), { locked = true })\nhl.bind(\"SUPER + SHIFT + M\", hl.dsp.exec_cmd(\"wpctl set-mute @DEFAULT_SINK@ toggle\"),\n    { locked = true, description = \"Media: Toggle mute\" })\nhl.bind(\"ALT + XF86AudioMute\", hl.dsp.exec_cmd(\"wpctl set-mute @DEFAULT_SOURCE@ toggle\"), { locked = true })\nhl.bind(\"XF86AudioMicMute\", hl.dsp.exec_cmd(\"wpctl set-mute @DEFAULT_SOURCE@ toggle\"), { locked = true })\nhl.bind(\"SUPER + ALT + M\", hl.dsp.exec_cmd(\"wpctl set-mute @DEFAULT_SOURCE@ toggle\"),\n    { locked = true, description = \"Media: Toggle mic\" })\n\n--#!\n--##! Window\n--# Focusing\nhl.bind(\"SUPER + mouse:272\", hl.dsp.window.drag(), { mouse = true, description = \"Window: Move\" })\nhl.bind(\"SUPER + mouse:274\", hl.dsp.window.drag(), { mouse = true })\nhl.bind(\"SUPER + mouse:273\", hl.dsp.window.resize(), { mouse = true, description = \"Window: Resize\" })\n--#/# bind = SUPER + ←/↑/→/↓,, -- Focus in direction\nfor i = 1, 4 do\n    local arrowkey = { \"Left\", \"Right\", \"Up\", \"Down\" }\n    local focusdir = { \"l\", \"r\", \"u\", \"d\" }\n    hl.bind(\"SUPER + \" .. arrowkey[i], hl.dsp.focus({ direction = focusdir[i] }),\n        { description = \"Window: Focus \" .. arrowkey[i] })\nend\nfor i = 1, 2 do\n    local arrowkey = { \"BracketLeft\", \"BracketRight\" }\n    local focusdir = { \"l\", \"r\" }\n    hl.bind(\"SUPER + \" .. arrowkey[i], hl.dsp.focus({ direction = focusdir[i] }))\nend\n--#/# bind = SUPER + SHIFT, ←/↑/→/↓,, -- Move in direction\nfor i = 1, 4 do\n    local arrowkey = { \"Left\", \"Right\", \"Up\", \"Down\" }\n    local focusdir = { \"l\", \"r\", \"u\", \"d\" }\n    hl.bind(\"SUPER + SHIFT + \" .. arrowkey[i], hl.dsp.window.move({ direction = focusdir[i] }),\n        { description = \"Window: Move \" .. arrowkey[i] })\nend\n\nhl.bind(\"ALT + F4\",\n    function()\n        hl.exec_cmd(\n            \"notify-send \\\"Wrong close keybind\\\" \\\"Super+Q to close. Use Alt+F4 for Windows VMs\\\" -a Hyprland\")\n    end,\n    { non_consuming = true })\nhl.bind(\"SUPER + Q\", hl.dsp.window.close(), { description = \"Window: Close\" })\nhl.bind(\"SUPER + SHIFT + ALT + Q\", hl.dsp.exec_cmd(\"hyprctl kill\"), { description = \"Window: Forcefully zap a window\" })\n\n--# Window split ratio\n--#/# binde = SUPER, ;/',, -- Adjust split ratio\nhl.bind(\"SUPER + Semicolon\", hl.dsp.layout(\"splitratio -0.1\"), { repeating = true })\nhl.bind(\"SUPER + Apostrophe\", hl.dsp.layout(\"splitratio +0.1\"), { repeating = true })\n--# Positioning mode\nhl.bind(\"SUPER + ALT + Space\", hl.dsp.window.float({ action = \"toggle\" }), { description = \"Window: Float/Tile\" })\nhl.bind(\"SUPER + D\", hl.dsp.window.fullscreen({ mode = \"maximized\", action = \"toggle\" }),\n    { description = \"Window: Maximize\" })\nhl.bind(\"SUPER + F\", hl.dsp.window.fullscreen({ mode = \"fullscreen\", action = \"toggle\" }),\n    { description = \"Window: Fullscreen\" })\nhl.bind(\"SUPER + ALT + F\", hl.dsp.window.fullscreen_state({ internal = 0, client = 3, action = \"toggle\" }),\n    { description = \"Window: Fullscreen spoof\" })\nhl.bind(\"SUPER + P\", hl.dsp.window.pin(), { description = \"Window: Pin\" })\n\n--#/# bind = SUPER+ALT, Hash,, -- Send to workspace -- (1, 2, 3,...)\nfor i = 1, 10 do\n    hl.bind(\"SUPER + ALT + \" .. (i % 10), function()\n        hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))\n    end, { description = \"Window: Send to workspace \" .. i })\nend\n--# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev`\nfor i = 1, 10 do\n    local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }\n    hl.bind(\"SUPER + ALT + code:\" .. numberkey[i], function()\n        hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))\n    end)\nend\n--# keypad numbers\nfor i = 1, 10 do\n    local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 }\n    hl.bind(\"SUPER + ALT + code:\" .. numpadkey[i], function()\n        hl.dispatch(hl.dsp.window.move({ workspace = workspace_in_group(i), follow = false }))\n    end)\nend\n\n--# #/# bind = SUPER+SHIFT, Scroll ↑/↓,, -- Send to workspace left/right\nfor i = 1, 4 do\n    local key = { \"SUPER + SHIFT + mouse_\", \"SUPER + ALT + mouse_\" }\n    local keycombos = { key[1] .. \"down\", key[1] .. \"up\", key[2] .. \"down\", key[2] .. \"up\" }\n    local prefix = { \"r-\", \"r+\", \"r-\", \"r+\" }\n    hl.bind(keycombos[i], hl.dsp.window.move({ workspace = prefix[i] .. \"1\" }))\nend\n\n--#/# bind = SUPER+SHIFT, Page_↑/↓,, -- Send to workspace left/right\nfor i = 1, 2 do\n    local keydirs = { \"Up\", \"Down\" }\n    local prefix = { \"r-\", \"r+\" }\n    local descdir = { \"left\", \"right\" }\n    hl.bind(\"SUPER + SHIFT + Page_\" .. keydirs[i], hl.dsp.window.move({ workspace = prefix[i] .. \"1\" }), {description = \"Window: Send to workspace \" .. descdir[i]})\nend\nfor i = 1, 4 do\n    local key = { \"SUPER + ALT + Page_\", \"CTRL + SUPER + SHIFT + \" }\n    local keycombos = { key[1] .. \"down\", key[1] .. \"up\", key[2] .. \"Right\", key[2] .. \"Left\" }\n    local prefix = { \"r+\", \"r-\", \"r+\", \"r-\" }\n    hl.bind(keycombos[i], hl.dsp.window.move({ workspace = prefix[i] .. \"1\" })) -- # [hidden]\nend\n\nhl.bind(\"SUPER + ALT + S\",\n    hl.dsp.window.move({ workspace = \"special:special\", follow = false }), { description = \"Window: Send to scratchpad\" })\nhl.bind(\"CTRL + SUPER + S\", hl.dsp.workspace.toggle_special(\"special\"))\n\n--##! Workspace\n--# Switching\n--#/# bind = SUPER, Hash,, -- Focus workspace -- (1, 2, 3,...)\nfor i = 1, 10 do\n    hl.bind(\"SUPER + \" .. (i % 10), function()\n        hl.dispatch(hl.dsp.focus({ workspace = workspace_in_group(i) }))\n    end, { description = \"Workspace: Focus \" .. i })\nend\n--# We also use raw keycodes because some keyboard layouts register number keys as different chars. The codes can be verified with `wev`\nfor i = 1, 10 do\n    local numberkey = { 10, 11, 12, 13, 14, 15, 16, 17, 18, 19 }\n    hl.bind(\"SUPER + code:\" .. numberkey[i], function()\n        hl.dispatch(hl.dsp.focus({ workspace = workspace_in_group(i) }))\n    end)\nend\n--# keypad numbers\nfor i = 1, 10 do\n    local numpadkey = { 87, 88, 89, 83, 84, 85, 79, 80, 81, 90 }\n    hl.bind(\"SUPER + code:\" .. numpadkey[i], function()\n        hl.dispatch(hl.dsp.focus({ workspace = workspace_in_group(i) }))\n    end)\nend\n\n--#/# bind = CTRL+SUPER, ←/→,, -- Focus left/right\n--#/# bind = CTRL+SUPER+ALT, ←/→,, -- # [hidden] Focus busy left/right\nfor i = 1, 2 do\n    local keys = { \"Left\", \"Right\" }\n    local prefix = { \"r-\", \"r+\" }\n    local descdir = { \"left\", \"right\" }\n    hl.bind(\"CTRL + SUPER + \" .. keys[i], hl.dsp.focus({ workspace = prefix[i] .. \"1\" }), {description = \"Workspace: Focus \" .. descdir[i]})\nend\nfor i = 1, 2 do\n    local keys = { \"Left\", \"Right\" }\n    local prefix = { \"m-\", \"m+\" }\n    hl.bind(\"CTRL + SUPER + ALT + \" .. keys[i], hl.dsp.focus({ workspace = prefix[i] .. \"1\" }))\nend\n--#/# bind = SUPER, Page_↑/↓,, -- Focus left/right\nfor i = 1, 4 do\n    local key = { \"SUPER + Page_Down\", \"SUPER + Page_Up\" }\n    local keycombos = { key[1], key[2], \"CTRL + \" .. key[1], \"CTRL + \" .. key[2] }\n    local prefix = { \"r+\", \"r-\", \"r+\", \"r-\" }\n    hl.bind(keycombos[i], hl.dsp.focus({ workspace = prefix[i] .. \"1\" }))\nend\n--#/# bind = SUPER, Scroll ↑/↓,, -- Focus left/right\nfor i = 1, 4 do\n    local key = { \"SUPER + mouse_up\", \"SUPER + mouse_down\" }\n    local keycombos = { key[1], key[2], \"CTRL + \" .. key[1], \"CTRL + \" .. key[2] }\n    local prefix = { \"+\", \"-\", \"r+\", \"r-\" }\n    hl.bind(keycombos[i], hl.dsp.focus({ workspace = prefix[i] .. \"1\" }))\nend\n--## Special\nhl.bind(\"SUPER + S\", hl.dsp.workspace.toggle_special(\"special\"), { description = \"Workspace: Toggle scratchpad\" })\nhl.bind(\"SUPER + mouse:275\", hl.dsp.workspace.toggle_special(\"special\"))\nfor i = 1, 4 do\n    local key = { \"BracketLeft\", \"BracketRight\", \"Up\", \"Down\" }\n    local prefix = { \"-1\", \"+1\", \"r-5\", \"r+5\" }\n    hl.bind(\"CTRL + SUPER + \" .. key[i], hl.dsp.focus({ workspace = prefix[i] }))\nend\n\n--##! Virtual machines\nhl.define_submap(\"virtual-machine\", function()\n    hl.bind(\"SUPER + ALT + F1\", function()\n        local currentsubmap = hl.get_current_submap()\n        if currentsubmap == \"virtual-machine\" then\n            hl.dispatch(hl.dsp.exec_cmd(\n                \"notify-send 'Exited Virtual Machine submap' 'Keybinds re-enabled' -a 'Hyprland'\"))\n            hl.dispatch(hl.dsp.submap(\"reset\"))\n        elseif currentsubmap == \"\" then\n            hl.dispatch(hl.dsp.exec_cmd(\n                \"notify-send 'Entered Virtual Machine submap' 'Keybinds disabled. hit SUPER+ALT+F1 to escape' -a 'Hyprland'\"))\n            hl.dispatch(hl.dsp.submap(\"virtual-machine\"))\n        end\n    end, { submap_universal = true })\nend)\n\n\n--#!\n--# Testing\nhl.bind(\"SUPER + ALT + F11\",\n    hl.dsp.exec_cmd(\n        \"bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | shuf -n 1); ACTION=$(notify-send \\\"Test notification with body image\\\" \\\"This notification should contain your user account <b>image</b> and <a href=\\\\\\\"https://discord.com/app\\\\\\\">Discord</a> <b>icon</b>. Oh and here is a random image in your Pictures folder: <img src=\\\\\\\"$RANDOM_IMAGE\\\\\\\" alt=\\\\\\\"Testing image\\\\\\\"/>\\\" -a \\\"Hyprland\\\" -p -h \\\"string:image-path:/var/lib/AccountsService/icons/$USER\\\" -t 6000 -i \\\"discord\\\" -A \\\"openImage=Profile image\\\" -A \\\"action2=Open the random image\\\" -A \\\"action3=Useless button\\\"); [[ $ACTION == *openImage ]] && xdg-open \\\"/var/lib/AccountsService/icons/$USER\\\"; [[ $ACTION == *action2 ]] && xdg-open \\\"$RANDOM_IMAGE\\\"'\")\n) -- # [hidden]\nhl.bind(\"SUPER + ALT + F12\",\n    hl.dsp.exec_cmd(\n        \"bash -c 'RANDOM_IMAGE=$(find ~/Pictures -type f | shuf -n 1); ACTION=$(notify-send \\\"Test notification\\\" \\\"This notification should contain a random image in your <b>Pictures</b> folder and <a href=\\\\\\\"https://discord.com/app\\\\\\\">Discord</a> <b>icon</b>.\\n<i>Flick right to dismiss!</i>\\\" -a \\\"Discord (fake)\\\" -p -h \\\"string:image-path:$RANDOM_IMAGE\\\" -t 6000 -i \\\"discord\\\" -A \\\"openImage=Profile image\\\" -A \\\"action2=Useless button\\\"); [[ $ACTION == *openImage ]] && xdg-open \\\"/var/lib/AccountsService/icons/$USER\\\"'\")\n)                                                                                                        -- # [hidden]\nhl.bind(\"SUPER + ALT + Equal\",\n    hl.dsp.exec_cmd(\"notify-send 'Urgent notification' 'Ah hell no' -u critical -a 'Hyprland keybind'\")) -- # [hidden]\n\n--##! Session\nhl.bind(\"SUPER + L\", hl.dsp.exec_cmd(\"loginctl lock-session\"), { description = \"Session: Lock\" })\nhl.bind(\"SUPER + SHIFT + L\", hl.dsp.exec_cmd(\"systemctl suspend || loginctl suspend\"),\n    { locked = true, description = \"Session: Sleep\" }) -- Sleep\n-- hl.bind(\"switch:on:Lid Switch\", hl.dsp.exec_cmd(\"systemctl suspend || loginctl suspend\"), {locked = true} ) -- # [hidden] Suspend when laptop lid is closed, uncomment if for whatever reason it's not the default behavior\n\nhl.bind(\"CTRL + SHIFT + ALT + SUPER + Delete\", hl.dsp.exec_cmd(\"systemctl poweroff || loginctl poweroff\"),\n    { description = \"Session: Shut down\" }) -- # [hidden] Power off\n\n\n--##! Apps\nhl.bind(\"SUPER + Return\", hl.dsp.exec_cmd(terminal), { description = \"App: Terminal\" })\nhl.bind(\"SUPER + T\", hl.dsp.exec_cmd(terminal))\nhl.bind(\"CTRL + ALT + T\", hl.dsp.exec_cmd(terminal))\nhl.bind(\"SUPER + E\", hl.dsp.exec_cmd(fileManager), { description = \"App: File manager\" })\nhl.bind(\"SUPER + W\", hl.dsp.exec_cmd(browser), { description = \"App: Browser\" })\nhl.bind(\"SUPER + C\", hl.dsp.exec_cmd(codeEditor), { description = \"App: Code editor\" })\nhl.bind(\"CTRL + SUPER + SHIFT + ALT + W\", hl.dsp.exec_cmd(officeSoftware), { description = \"App: Office software\" })\nhl.bind(\"SUPER + X\", hl.dsp.exec_cmd(textEditor), { description = \"App: Text editor\" })\nhl.bind(\"CTRL + SUPER + V\", hl.dsp.exec_cmd(volumeMixer), { description = \"App: Volume mixer\" })\nhl.bind(\"SUPER + I\", hl.dsp.exec_cmd(settingsApp), { description = \"App: Settings app\" })\nhl.bind(\"CTRL + SHIFT + Escape\", hl.dsp.exec_cmd(taskManager), { description = \"App: Task manager\" })\n\n--# Cursed stuff\n--## Make window not amogus large\nhl.bind(\"CTRL + SUPER + Backslash\", hl.dsp.window.resize({ x = 640, y = 480, \"exact\" }))\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/lib/init.lua",
    "content": "HOME = os.getenv(\"HOME\")\n\nfunction is_file_exists(name)\n   local f = io.open(name, \"r\")\n   if f ~= nil then\n      io.close(f)\n      return true\n   else\n      return false\n   end\nend\n\nfunction create_if_not_exists(path)\n   if not is_file_exists(path) then\n      os.execute(\"mkdir -p \\\"$(dirname \\\"\" .. path .. \"\\\")\\\"\")\n      os.execute(\"echo '-- This file will not be overwritten across dots-hyprland updates.\\n-- The file name is for the sake of organization and does not matter\\n-- See the corresponding files in ~/.config/hypr/hyprland for examples' > \\\"\" .. path .. \"\\\"\")\n      return true\n   end\n   return false\nend\n\nfunction workspace_in_group(i)\n    local curr = hl.get_active_workspace().id\n    local newVal = math.floor((curr - 1) / workspaceGroupSize) * workspaceGroupSize + i\n    -- hl.notification.create({ text = \"curr \" .. curr .. \" floor \" .. math.floor(curr / 10) .. \" new \" .. newVal, duration = 5000 })\n    return newVal\nend\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/rules.lua",
    "content": "-- ######## Window rules ########\n\n-- Disable blur for xwayland context menus\nhl.window_rule({match = {class = \"^()$\", title = \"^()$\" },                   no_blur = true })\n\n-- Disable blur for every window\nhl.window_rule({match = {class = \".*\" }, no_blur = true })\n\n-- Floating\nhl.window_rule({match = {title = \"^(Open File)(.*)$\" },                      center = true})\nhl.window_rule({match = {title = \"^(Open File)(.*)$\" },                      float = true})\nhl.window_rule({match = {title = \"^(Select a File)(.*)$\" },                  center = true})\nhl.window_rule({match = {title = \"^(Select a File)(.*)$\" },                  float = true})\nhl.window_rule({match = {title = \"^(Choose wallpaper)(.*)$\" },               center = true})\nhl.window_rule({match = {title = \"^(Choose wallpaper)(.*)$\" },               float = true})\nhl.window_rule({match = {title = \"^(Choose wallpaper)(.*)$\" },               size = {\"(monitor_w*0.60)\", \"(monitor_h*0.65)\"} })\nhl.window_rule({match = {title = \"^(Open Folder)(.*)$\" },                    center = true})\nhl.window_rule({match = {title = \"^(Open Folder)(.*)$\" },                    float = true})\nhl.window_rule({match = {title = \"^(Save As)(.*)$\" },                        center = true})\nhl.window_rule({match = {title = \"^(Save As)(.*)$\" },                        float = true})\nhl.window_rule({match = {title = \"^(Library)(.*)$\" },                        center = true})\nhl.window_rule({match = {title = \"^(Library)(.*)$\" },                        float = true})\nhl.window_rule({match = {title = \"^(File Upload)(.*)$\" },                    center = true})\nhl.window_rule({match = {title = \"^(File Upload)(.*)$\" },                    float = true})\nhl.window_rule({match = {title = \"^(.*)(wants to save)$\" },                  center = true})\nhl.window_rule({match = {title = \"^(.*)(wants to save)$\" },                  float = true})\nhl.window_rule({match = {title = \"^(.*)(wants to open)$\" },                  center = true})\nhl.window_rule({match = {title = \"^(.*)(wants to open)$\" },                  float = true})\nhl.window_rule({match = {class = \"^(blueberry\\\\.py)$\" },                     float = true})\nhl.window_rule({match = {class = \"^(guifetch)$\" },                           float = true}) -- FlafyDev/guifetch\nhl.window_rule({match = {class = \"^(pavucontrol)$\" },                        float = true})\nhl.window_rule({match = {class = \"^(pavucontrol)$\" },                        size = {\"(monitor_w*0.45)\", \"(monitor_h*0.45)\"} })\nhl.window_rule({match = {class = \"^(pavucontrol)$\" },                        center = true})\nhl.window_rule({match = {class = \"^(org.pulseaudio.pavucontrol)$\" },         float = true})\nhl.window_rule({match = {class = \"^(org.pulseaudio.pavucontrol)$\" },         size = {\"(monitor_w*0.45)\", \"(monitor_h*0.45)\"} })\nhl.window_rule({match = {class = \"^(org.pulseaudio.pavucontrol)$\" },         center = true})\nhl.window_rule({match = {class = \"^(nm-connection-editor)$\" },               float = true})\nhl.window_rule({match = {class = \"^(nm-connection-editor)$\" },               size = {\"(monitor_w*0.45)\", \"(monitor_h*0.45)\"} })\nhl.window_rule({match = {class = \"^(nm-connection-editor)$\" },               center = true})\nhl.window_rule({match = {class = \".*plasmawindowed.*\" },                     float = true})\nhl.window_rule({match = {class = \"kcm_.*\" },                                  float = true})\nhl.window_rule({match = {class = \".*bluedevilwizard\" },                      float = true})\nhl.window_rule({match = {title = \".*Welcome\" },                              float = true})\nhl.window_rule({match = {title = \"^(illogical-impulse Settings)$\" },         float = true})\nhl.window_rule({match = {title = \".*Shell conflicts.*\" },                    float = true})\nhl.window_rule({match = {class = \"org.freedesktop.impl.portal.desktop.kde\" }, float = true})\nhl.window_rule({match = {class = \"org.freedesktop.impl.portal.desktop.kde\" }, size = {\"(monitor_w*0.60)\", \"(monitor_h*0.65)\"} })\nhl.window_rule({match = {class = \"^(Zotero)$\" },                             float = true})\nhl.window_rule({match = {class = \"^(Zotero)$\" },                             size = {\"(monitor_w*0.45)\", \"(monitor_h*0.45)\"} })\n\n-- Move\n-- kde-material-you-colors spawns a window when changing dark/light theme. This is to make sure it doesn't interfere at all.\nhl.window_rule({match = {class = \"^(plasma-changeicons)$\" }, float = true})\nhl.window_rule({match = {class = \"^(plasma-changeicons)$\" }, no_initial_focus = true})\nhl.window_rule({match = {class = \"^(plasma-changeicons)$\" }, move = {999999, 999999}})\n-- stupid dolphin copy\nhl.window_rule({match = {title = \"^(Copying — Dolphin)$\" }, move = {40, 80}})\n\n-- Tiling\nhl.window_rule({match = {class = \"^dev\\\\.warp\\\\.Warp$\" }, tile = true})\n\n-- Picture-in-Picture\nhl.window_rule({match = {title = \"^([Pp]icture[-\\\\s]?[Ii]n[-\\\\s]?[Pp]icture)(.*)$\" }, float = true})\nhl.window_rule({match = {title = \"^([Pp]icture[-\\\\s]?[Ii]n[-\\\\s]?[Pp]icture)(.*)$\" }, keep_aspect_ratio = true})\nhl.window_rule({match = {title = \"^([Pp]icture[-\\\\s]?[Ii]n[-\\\\s]?[Pp]icture)(.*)$\" }, move = {\"(monitor_w*0.73)\", \"(monitor_h*0.72)\"} })\nhl.window_rule({match = {title = \"^([Pp]icture[-\\\\s]?[Ii]n[-\\\\s]?[Pp]icture)(.*)$\" }, size = {\"(monitor_w*0.25)\", \"(monitor_h*0.25)\"} })\nhl.window_rule({match = {title = \"^([Pp]icture[-\\\\s]?[Ii]n[-\\\\s]?[Pp]icture)(.*)$\" }, float = true})\nhl.window_rule({match = {title = \"^([Pp]icture[-\\\\s]?[Ii]n[-\\\\s]?[Pp]icture)(.*)$\" }, pin = true})\n\n-- Screen sharing\nhl.window_rule({match = {title = \".*is sharing (a window|your screen).*\" }, float = true})\nhl.window_rule({match = {title = \".*is sharing (a window|your screen).*\" }, pin = true})\nhl.window_rule({match = {title = \".*is sharing (a window|your screen).*\" }, move = {\"(monitor_w*.5-window_w*.5)\", \"(monitor_h-window_h-12)\"} })\n\n-- --- Tearing ---\nhl.window_rule({match = {title = \".*\\\\.exe\" }, immediate = true})\nhl.window_rule({match = {title = \".*minecraft.*\" }, immediate = true})\nhl.window_rule({match = {class = \"^(steam_app).*\" }, immediate = true})\n\n-- No shadow for tiled windows\nhl.window_rule({match = {float = 0 }, no_shadow = true})\n\n-- ######## Workspace rules ########\nhl.workspace_rule({ workspace = \"special:special\", gaps_out = 30 })\n\n-- ######## Layer rules ########\nhl.layer_rule({ match = { namespace = \".*\" }, xray = true})\nhl.layer_rule({ match = { namespace = \"walker\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"selection\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"overview\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"anyrun\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"indicator.*\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"osk\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"hyprpicker\" }, no_anim = true})\n\nhl.layer_rule({ match = { namespace = \"noanim\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"gtk-layer-shell\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"gtk-layer-shell\" }, ignore_alpha = 0})\nhl.layer_rule({ match = { namespace = \"launcher\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"launcher\" }, ignore_alpha = 0.5})\nhl.layer_rule({ match = { namespace = \"notifications\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"notifications\" }, ignore_alpha = 0.69})\nhl.layer_rule({ match = { namespace = \"logout_dialog\" }, blur = true}) -- wlogout\n\n-- ags\nhl.layer_rule({ match = { namespace = \"sideleft.*\" }, animation = \"slide left\"})\nhl.layer_rule({ match = { namespace = \"sideright.*\" }, animation = \"slide right\"})\nhl.layer_rule({ match = { namespace = \"session[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"bar[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"bar[0-9]*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"barcorner.*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"barcorner.*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"dock[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"dock[0-9]*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"indicator.*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"indicator.*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"overview[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"overview[0-9]*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"cheatsheet[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"cheatsheet[0-9]*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"sideright[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"sideright[0-9]*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"sideleft[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"sideleft[0-9]*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"indicator.*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"indicator.*\" }, ignore_alpha = 0.6})\nhl.layer_rule({ match = { namespace = \"osk[0-9]*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"osk[0-9]*\" }, ignore_alpha = 0.6})\n\n-- Quickshell\n-- Quickshell: illogical-impulse\nhl.layer_rule({ match = { namespace = \"quickshell:.*\" }, blur_popups = true})\nhl.layer_rule({ match = { namespace = \"quickshell:.*\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"quickshell:.*\" }, ignore_alpha = 0.79})\nhl.layer_rule({ match = { namespace = \"quickshell:bar\" }, animation = \"slide\"})\nhl.layer_rule({ match = { namespace = \"quickshell:actionCenter\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:cheatsheet\" }, animation = \"slide bottom\"})\nhl.layer_rule({ match = { namespace = \"quickshell:dock\" }, animation = \"slide bottom\"})\nhl.layer_rule({ match = { namespace = \"quickshell:screenCorners\" }, animation = \"popin 120%\"})\nhl.layer_rule({ match = { namespace = \"quickshell:lockWindowPusher\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:notificationPopup\" }, animation = \"fade\"})\nhl.layer_rule({ match = { namespace = \"quickshell:overlay\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:overlay\" }, ignore_alpha = 1})\nhl.layer_rule({ match = { namespace = \"quickshell:overview\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:osk\" }, animation = \"slide bottom\"})\nhl.layer_rule({ match = { namespace = \"quickshell:polkit\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:popup\" }, xray = false}) -- No weird color for bar tooltips (this in theory should suffice)\nhl.layer_rule({ match = { namespace = \"quickshell:popup\" }, ignore_alpha = 1}) -- No weird color for bar tooltips (but somehow this is necessary)\nhl.layer_rule({ match = { namespace = \"quickshell:mediaControls\" }, ignore_alpha = 1}) -- Same as above\nhl.layer_rule({ match = { namespace = \"quickshell:reloadPopup\" }, animation = \"slide\"})\nhl.layer_rule({ match = { namespace = \"quickshell:regionSelector\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:screenshot\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:session\" }, blur = true})\nhl.layer_rule({ match = { namespace = \"quickshell:session\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:session\" }, ignore_alpha = 0})\nhl.layer_rule({ match = { namespace = \"quickshell:sidebarRight\" }, animation = \"slide right\"})\nhl.layer_rule({ match = { namespace = \"quickshell:sidebarLeft\" }, animation = \"slide left\"})\nhl.layer_rule({ match = { namespace = \"quickshell:verticalBar\" }, animation = \"slide\"})\nhl.layer_rule({ match = { namespace = \"quickshell:osk\" }, order = -1})\n-- Quickshell: waffles\nhl.layer_rule({ match = { namespace = \"quickshell:wallpaperSelector\" }, animation = \"slide top\"})\nhl.layer_rule({ match = { namespace = \"quickshell:wNotificationCenter\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:wOnScreenDisplay\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:wStartMenu\" }, no_anim = true})\nhl.layer_rule({ match = { namespace = \"quickshell:wTaskView\" }, ignore_alpha = 0})\nhl.layer_rule({ match = { namespace = \"quickshell:wTaskView\" }, no_anim = true})\n\n-- Launchers need to be FAST\nhl.layer_rule({ match = { namespace = \"gtk4-layer-shell\" }, no_anim = true})\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/ai/license_show-loaded-ollama-models.txt",
    "content": "                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/ai/primary-buffer-query.sh",
    "content": "#!/usr/bin/env bash\n\n# Default system prompt\nSYSTEM_PROMPT=\"You are a helpful, quick assistant that provides brief and concise explanation \\\nto given content in at most 100 characters. If the given content is not in English, translate \\\nit to English. If the content is an English word, provide its meaning. If the content is a name, \\\nprovide some info about it. For a math expression, provide a simplification, \\\neach step on a line following this style: \\`2x=11 (subtract 7 from both sides)\\`. \\\nIf you do not know the answer, simply say 'No info available'. \\\nOnly respond for the appropriate case and use as little text as possible.\\\nThe content:\"\n\nfirst_loaded_model=$(\"$(dirname \"$0\")/show-loaded-ollama-models.sh\" -j | jq -r '.[0].model' 2>/dev/null) || first_loaded_model=\"\"\nmodel=${first_loaded_model:-\"llama3.2\"}\n\n# Parse command-line arguments\nwhile [[ \"$#\" -gt 0 ]]; do\n    case $1 in\n        --model) model=\"$2\"; shift ;; # Set the model from the flag\n        *) echo \"Unknown parameter: $1\"; exit 1 ;;\n    esac\n    shift\ndone\n\n# Combine the system prompt with the clipboard content\ncontent=$(wl-paste -p | tr '\\n' ' ' | head -c 2000)  # 2000 char limit to prevent overflow\n\n# Properly escape content for JSON using jq\nprompt_json=$(jq -n --arg system_prompt \"$SYSTEM_PROMPT\" --arg content \"$content\" '$system_prompt + \" \" + $content')\n\n# Make the API call with the specified or default model\napi_payload=$(jq -n --arg model \"$model\" --argjson prompt \"$prompt_json\" --argjson stream false \\\n    '{model: $model, prompt: $prompt, stream: $stream}')\nresponse=$(curl -s http://localhost:11434/api/generate -d \"$api_payload\" | jq -r '.response' 2>/dev/null)\n\n# Check if content is a single line and no longer than 30 characters\nif [[ ${#content} -le 30 && \"$content\" != *$'\\n'* ]]; then\n    notify-send --app-name=\"Text selection query\" --expire-time=10000 \\\n        \"$content\" \"$response\"\nelse\n    notify-send --app-name=\"Text selection query\" --expire-time=10000 \\\n        \"AI Response\" \"$response\"\nfi\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/ai/show-loaded-ollama-models.sh",
    "content": "#!/bin/bash\n\n# From strikeoncmputrz/LLM_Scripts\n# License: Apache-2.0, can be found in the same folder as this script\n\n# Global Vars\nollama_url=http://localhost\nport=\"11434\"\nblobs=()\nmodel_name_paths=()\n\n\n#Parse arguments\nwhile [ \"$#\" -gt 0 ]; do\n  case $1 in\n    -h|--help)\n      echo \n      echo \" Identifies Ollama models running on this operating system by parsing running processes.\"\n      echo \n      echo \" Usage: $0 [options]\"   \n      echo\n      echo \" Options:\"\n      echo \"  -j, --json_output        Prints result as a json object. Other output disabled. (Default: false)\"      \n      echo \"  -p, --port [port number] Specify Ollama Server port (Default: 11434)\"\n      echo \"  -u, --ollama_url [url]   Specify Ollama Server URL (Default: http://localhost)\"\n      echo\n      echo \" Dependencies: jq\"\n      exit 0\n      ;;\n    -j|--json_output)\n      json_out=1\n      shift 1\n      ;;\n    -u|--ollama_url)\n      ollama_url=$2\n      shift 2\n      ;;\n    -p|--port)\n      port=$2\n      shift 2\n      ;;\n    *)\n      echo \"Unknown option: $1\"\n      exit 1\n      ;;\n  esac\ndone\n\ncompare_running_models_and_modelfiles() { \n    json_match=()\n    json_output=()\n    local matching_models=()\n    OLDIFS=$IFS\n    for ((i=0; i<${#model_name_paths[@]}; i++)); do  # Iterate over the array of modelname,blob-path\n        for blob in \"${blobs[@]}\"; do\n            IFS=',', read -ra fields <<< \"${model_name_paths[i]}\"    # Split the string into parts\n            if [ \"${fields[1]}\" == \"$blob\" ]; then  # Check if current 'field' matches a blob\n                matching_models+=( '{ \"model\": \"'\"${fields[0]}\"'\", \"path\": \"'\"${fields[1]}\"'\"}') # Add to list of matching models\n            fi\n        done\n    done\n    \n    if [ -z \"$json_out\" ]; then\n        echo -e \"\\nModel Found: \\n $(echo ${matching_models[*]} | jq '.' | sed s/[{}]//g) \\n\"        \n    else\n        local json_match=\"${matching_models[*]}\"\n        json_output=$(echo $json_match | jq -c -s .)\n        echo \"$json_output\"\n    fi\n    IFS=$OLDIFS\n}\n\nget_running_model_paths() {\n    blobs=$(ps aux | grep -- '--model' | grep -v grep | grep -Po '(?<=--model\\s).*' | cut -d ' ' -f1)\n    if [ -z \"$blobs\" ]; then\n        echo -e \"\\n\\n Warning: No running Ollama models detected!\\n\"\n        exit 0\n    fi\n}\n\nparse_modelfiles() {\n    if [ -z \"$json_out\" ]; then\n        echo -e \"\\nConnecting to $ollama_url:$port\\n\"\n        if [ -z \"$(curl -s $ollama_url:$port)\" ]; then\n           echo -e \"Could not connect to Ollama. Check the ollama_url parameter and that the server is running\\n\"\n           exit 1\n        fi\n        curl -s \"$ollama_url:$port\"\n    fi\n    local models=( $(curl -s \"$ollama_url:$port/api/tags\" | jq -r '.models[].name') )\n    for model in \"${models[@]}\"; do\n        local modelfile=$(curl -s \"$ollama_url:$port/api/show\" -d '{ \"name\": \"'\"$model\"'\", \"modelfile\": true }' | jq   -r '.modelfile')\n        model_name_paths+=($model,$(echo \"$modelfile\" | awk '/^FROM/{print $2}'))\n    done\n}\n\nparse_modelfiles\nget_running_model_paths\ncompare_running_models_and_modelfiles\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/fuzzel-emoji.sh",
    "content": "#!/bin/bash\nset -euo pipefail\n\nMODE=\"${1:-type}\"\n\nemoji=\"$(sed '1,/^### DATA ###$/d' \"$0\" | fuzzel --match-mode fzf --dmenu | cut -d ' ' -f 1 | tr -d '\\n')\"\n\ncase \"$MODE\" in\n    type)\n        wtype \"${emoji}\" || wl-copy \"${emoji}\"\n        ;;\n    copy)\n        wl-copy \"${emoji}\"\n        ;;\n    both)\n        wtype \"${emoji}\" || true\n        wl-copy \"${emoji}\"\n        ;;\n    *)\n        echo \"Usage: $0 [type|copy|both]\"\n        exit 1\n        ;;\nesac\nexit\n### DATA ###\n😀 grinning face face smile happy joy :D grin\n😃 grinning face with big eyes face happy joy haha :D :) smile funny\n😄 grinning face with smiling eyes face happy joy funny haha laugh like :D :) smile\n😁 beaming face with smiling eyes face happy smile joy kawaii\n😆 grinning squinting face happy joy lol satisfied haha face glad XD laugh\n😅 grinning face with sweat face hot happy laugh sweat smile relief\n🤣 rolling on the floor laughing face rolling floor laughing lol haha rofl\n😂 face with tears of joy face cry tears weep happy happytears haha\n🙂 slightly smiling face face smile\n🙃 upside down face face flipped silly smile\n😉 winking face face happy mischievous secret ;) smile eye\n😊 smiling face with smiling eyes face smile happy flushed crush embarrassed shy joy\n😇 smiling face with halo face angel heaven halo\n🥰 smiling face with hearts face love like affection valentines infatuation crush hearts adore\n😍 smiling face with heart eyes face love like affection valentines infatuation crush heart\n🤩 star struck face smile starry eyes grinning\n😘 face blowing a kiss face love like affection valentines infatuation kiss\n😗 kissing face love like face 3 valentines infatuation kiss\n☺️ smiling face face blush massage happiness\n😚 kissing face with closed eyes face love like affection valentines infatuation kiss\n😙 kissing face with smiling eyes face affection valentines infatuation kiss\n😋 face savoring food happy joy tongue smile face silly yummy nom delicious savouring\n😛 face with tongue face prank childish playful mischievous smile tongue\n😜 winking face with tongue face prank childish playful mischievous smile wink tongue\n🤪 zany face face goofy crazy\n😝 squinting face with tongue face prank playful mischievous smile tongue\n🤑 money mouth face face rich dollar money\n🤗 hugging face face smile hug\n🤭 face with hand over mouth face whoops shock surprise\n🤫 shushing face face quiet shhh\n🤔 thinking face face hmmm think consider\n🤐 zipper mouth face face sealed zipper secret\n🤨 face with raised eyebrow face distrust scepticism disapproval disbelief surprise\n😐 neutral face indifference meh :| neutral\n😑 expressionless face face indifferent - - meh deadpan\n😶 face without mouth face hellokitty\n😏 smirking face face smile mean prank smug sarcasm\n😒 unamused face indifference bored straight face serious sarcasm unimpressed skeptical dubious side eye\n🙄 face with rolling eyes face eyeroll frustrated\n😬 grimacing face face grimace teeth\n🤥 lying face face lie pinocchio\n😌 relieved face face relaxed phew massage happiness\n😔 pensive face face sad depressed upset\n😪 sleepy face face tired rest nap\n🤤 drooling face face\n😴 sleeping face face tired sleepy night zzz\n😷 face with medical mask face sick ill disease\n🤒 face with thermometer sick temperature thermometer cold fever\n🤕 face with head bandage injured clumsy bandage hurt\n🤢 nauseated face face vomit gross green sick throw up ill\n🤮 face vomiting face sick\n🤧 sneezing face face gesundheit sneeze sick allergy\n🥵 hot face face feverish heat red sweating\n🥶 cold face face blue freezing frozen frostbite icicles\n🥴 woozy face face dizzy intoxicated tipsy wavy\n😵 dizzy face spent unconscious xox dizzy\n🤯 exploding head face shocked mind blown\n🤠 cowboy hat face face cowgirl hat\n🥳 partying face face celebration woohoo\n😎 smiling face with sunglasses face cool smile summer beach sunglass\n🤓 nerd face face nerdy geek dork\n🧐 face with monocle face stuffy wealthy\n😕 confused face face indifference huh weird hmmm :/\n😟 worried face face concern nervous :(\n🙁 slightly frowning face face frowning disappointed sad upset\n☹️ frowning face face sad upset frown\n😮 face with open mouth face surprise impressed wow whoa :O\n😯 hushed face face woo shh\n😲 astonished face face xox surprised poisoned\n😳 flushed face face blush shy flattered sex\n🥺 pleading face face begging mercy\n😦 frowning face with open mouth face aw what\n😧 anguished face face stunned nervous\n😨 fearful face face scared terrified nervous oops huh\n😰 anxious face with sweat face nervous sweat\n😥 sad but relieved face face phew sweat nervous\n😢 crying face face tears sad depressed upset :'(\n😭 loudly crying face face cry tears sad upset depressed sob\n😱 face screaming in fear face munch scared omg\n😖 confounded face face confused sick unwell oops :S\n😣 persevering face face sick no upset oops\n😞 disappointed face face sad upset depressed :(\n😓 downcast face with sweat face hot sad tired exercise\n😩 weary face face tired sleepy sad frustrated upset\n😫 tired face sick whine upset frustrated\n🥱 yawning face tired sleepy\n😤 face with steam from nose face gas phew proud pride\n😡 pouting face angry mad hate despise\n😠 angry face mad face annoyed frustrated\n🤬 face with symbols on mouth face swearing cursing cussing profanity expletive\n😈 smiling face with horns devil horns\n👿 angry face with horns devil angry horns\n💀 skull dead skeleton creepy death\n☠️ skull and crossbones poison danger deadly scary death pirate evil\n💩 pile of poo hankey shitface fail turd shit\n🤡 clown face face\n👹 ogre monster red mask halloween scary creepy devil demon japanese ogre\n👺 goblin red evil mask monster scary creepy japanese goblin\n👻 ghost halloween spooky scary\n👽 alien UFO paul weird outer space\n👾 alien monster game arcade play\n🤖 robot computer machine bot\n😺 grinning cat animal cats happy smile\n😸 grinning cat with smiling eyes animal cats smile\n😹 cat with tears of joy animal cats haha happy tears\n😻 smiling cat with heart eyes animal love like affection cats valentines heart\n😼 cat with wry smile animal cats smirk\n😽 kissing cat animal cats kiss\n🙀 weary cat animal cats munch scared scream\n😿 crying cat animal tears weep sad cats upset cry\n😾 pouting cat animal cats\n🙈 see no evil monkey monkey animal nature haha\n🙉 hear no evil monkey animal monkey nature\n🙊 speak no evil monkey monkey animal nature omg\n💋 kiss mark face lips love like affection valentines\n💌 love letter email like affection envelope valentines\n💘 heart with arrow love like heart affection valentines\n💝 heart with ribbon love valentines\n💖 sparkling heart love like affection valentines\n💗 growing heart like love affection valentines pink\n💓 beating heart love like affection valentines pink heart\n💞 revolving hearts love like affection valentines\n💕 two hearts love like affection valentines heart\n💟 heart decoration purple-square love like\n❣️ heart exclamation decoration love\n💔 broken heart sad sorry break heart heartbreak\n❤️ red heart love like valentines\n🧡 orange heart love like affection valentines\n💛 yellow heart love like affection valentines\n💚 green heart love like affection valentines\n💙 blue heart love like affection valentines\n💜 purple heart love like affection valentines\n🤎 brown heart coffee\n🖤 black heart evil\n🤍 white heart pure\n💯 hundred points score perfect numbers century exam quiz test pass hundred\n💢 anger symbol angry mad\n💥 collision bomb explode explosion collision blown\n💫 dizzy star sparkle shoot magic\n💦 sweat droplets water drip oops\n💨 dashing away wind air fast shoo fart smoke puff\n🕳️ hole embarrassing\n💣 bomb boom explode explosion terrorism\n💬 speech balloon bubble words message talk chatting\n👁️‍🗨️ eye in speech bubble info\n🗨️ left speech bubble words message talk chatting\n🗯️ right anger bubble caption speech thinking mad\n💭 thought balloon bubble cloud speech thinking dream\n💤 zzz sleepy tired dream\n👋 waving hand hands gesture goodbye solong farewell hello hi palm\n🤚 raised back of hand fingers raised backhand\n🖐️ hand with fingers splayed hand fingers palm\n✋ raised hand fingers stop highfive palm ban\n🖖 vulcan salute hand fingers spock star trek\n👌 ok hand fingers limbs perfect ok okay\n🤏 pinching hand tiny small size\n✌️ victory hand fingers ohyeah hand peace victory two\n🤞 crossed fingers good lucky\n🤟 love you gesture hand fingers gesture\n🤘 sign of the horns hand fingers evil eye sign of horns rock on\n🤙 call me hand hands gesture shaka\n👈 backhand index pointing left direction fingers hand left\n👉 backhand index pointing right fingers hand direction right\n👆 backhand index pointing up fingers hand direction up\n🖕 middle finger hand fingers rude middle flipping\n👇 backhand index pointing down fingers hand direction down\n☝️ index pointing up hand fingers direction up\n👍 thumbs up thumbsup yes awesome good agree accept cool hand like +1\n👎 thumbs down thumbsdown no dislike hand -1\n✊ raised fist fingers hand grasp\n👊 oncoming fist angry violence fist hit attack hand\n🤛 left facing fist hand fistbump\n🤜 right facing fist hand fistbump\n👏 clapping hands hands praise applause congrats yay\n🙌 raising hands gesture hooray yea celebration hands\n👐 open hands fingers butterfly hands open\n🤲 palms up together hands gesture cupped prayer\n🤝 handshake agreement shake\n🙏 folded hands please hope wish namaste highfive pray\n✍️ writing hand lower left ballpoint pen stationery write compose\n💅 nail polish beauty manicure finger fashion nail\n🤳 selfie camera phone\n💪 flexed biceps arm flex hand summer strong biceps\n🦾 mechanical arm accessibility\n🦿 mechanical leg accessibility\n🦵 leg kick limb\n🦶 foot kick stomp\n👂 ear face hear sound listen\n🦻 ear with hearing aid accessibility\n👃 nose smell sniff\n🧠 brain smart intelligent\n🦷 tooth teeth dentist\n🦴 bone skeleton\n👀 eyes look watch stalk peek see\n👁️ eye face look see watch stare\n👅 tongue mouth playful\n👄 mouth mouth kiss\n👶 baby child boy girl toddler\n🧒 child gender-neutral young\n👦 boy man male guy teenager\n👧 girl female woman teenager\n🧑 person gender-neutral person\n👱 person blond hair hairstyle\n👨 man mustache father dad guy classy sir moustache\n🧔 man beard person bewhiskered\n👨‍🦰 man red hair hairstyle\n👨‍🦱 man curly hair hairstyle\n👨‍🦳 man white hair old elder\n👨‍🦲 man bald hairless\n👩 woman female girls lady\n👩‍🦰 woman red hair hairstyle\n🧑‍🦰 person red hair hairstyle\n👩‍🦱 woman curly hair hairstyle\n🧑‍🦱 person curly hair hairstyle\n👩‍🦳 woman white hair old elder\n🧑‍🦳 person white hair elder old\n👩‍🦲 woman bald hairless\n🧑‍🦲 person bald hairless\n👱‍♀️ woman blond hair woman female girl blonde person\n👱‍♂️ man blond hair man male boy blonde guy person\n🧓 older person human elder senior gender-neutral\n👴 old man human male men old elder senior\n👵 old woman human female women lady old elder senior\n🙍 person frowning worried\n🙍‍♂️ man frowning male boy man sad depressed discouraged unhappy\n🙍‍♀️ woman frowning female girl woman sad depressed discouraged unhappy\n🙎 person pouting upset\n🙎‍♂️ man pouting male boy man\n🙎‍♀️ woman pouting female girl woman\n🙅 person gesturing no decline\n🙅‍♂️ man gesturing no male boy man nope\n🙅‍♀️ woman gesturing no female girl woman nope\n🙆 person gesturing ok agree\n🙆‍♂️ man gesturing ok men boy male blue human man\n🙆‍♀️ woman gesturing ok women girl female pink human woman\n💁 person tipping hand information\n💁‍♂️ man tipping hand male boy man human information\n💁‍♀️ woman tipping hand female girl woman human information\n🙋 person raising hand question\n🙋‍♂️ man raising hand male boy man\n🙋‍♀️ woman raising hand female girl woman\n🧏 deaf person accessibility\n🧏‍♂️ deaf man accessibility\n🧏‍♀️ deaf woman accessibility\n🙇 person bowing respectiful\n🙇‍♂️ man bowing man male boy\n🙇‍♀️ woman bowing woman female girl\n🤦 person facepalming disappointed\n🤦‍♂️ man facepalming man male boy disbelief\n🤦‍♀️ woman facepalming woman female girl disbelief\n🤷 person shrugging regardless\n🤷‍♂️ man shrugging man male boy confused indifferent doubt\n🤷‍♀️ woman shrugging woman female girl confused indifferent doubt\n🧑‍⚕️ health worker hospital\n👨‍⚕️ man health worker doctor nurse therapist healthcare man human\n👩‍⚕️ woman health worker doctor nurse therapist healthcare woman human\n🧑‍🎓 student learn\n👨‍🎓 man student graduate man human\n👩‍🎓 woman student graduate woman human\n🧑‍🏫 teacher professor\n👨‍🏫 man teacher instructor professor man human\n👩‍🏫 woman teacher instructor professor woman human\n🧑‍⚖️ judge law\n👨‍⚖️ man judge justice court man human\n👩‍⚖️ woman judge justice court woman human\n🧑‍🌾 farmer crops\n👨‍🌾 man farmer rancher gardener man human\n👩‍🌾 woman farmer rancher gardener woman human\n🧑‍🍳 cook food kitchen culinary\n👨‍🍳 man cook chef man human\n👩‍🍳 woman cook chef woman human\n🧑‍🔧 mechanic worker technician\n👨‍🔧 man mechanic plumber man human wrench\n👩‍🔧 woman mechanic plumber woman human wrench\n🧑‍🏭 factory worker labor\n👨‍🏭 man factory worker assembly industrial man human\n👩‍🏭 woman factory worker assembly industrial woman human\n🧑‍💼 office worker business\n👨‍💼 man office worker business manager man human\n👩‍💼 woman office worker business manager woman human\n🧑‍🔬 scientist chemistry\n👨‍🔬 man scientist biologist chemist engineer physicist man human\n👩‍🔬 woman scientist biologist chemist engineer physicist woman human\n🧑‍💻 technologist computer\n👨‍💻 man technologist coder developer engineer programmer software man human laptop computer\n👩‍💻 woman technologist coder developer engineer programmer software woman human laptop computer\n🧑‍🎤 singer song artist performer\n👨‍🎤 man singer rockstar entertainer man human\n👩‍🎤 woman singer rockstar entertainer woman human\n🧑‍🎨 artist painting draw creativity\n👨‍🎨 man artist painter man human\n👩‍🎨 woman artist painter woman human\n🧑‍✈️ pilot fly plane airplane\n👨‍✈️ man pilot aviator plane man human\n👩‍✈️ woman pilot aviator plane woman human\n🧑‍🚀 astronaut outerspace\n👨‍🚀 man astronaut space rocket man human\n👩‍🚀 woman astronaut space rocket woman human\n🧑‍🚒 firefighter fire\n👨‍🚒 man firefighter fireman man human\n👩‍🚒 woman firefighter fireman woman human\n👮 police officer cop\n👮‍♂️ man police officer man police law legal enforcement arrest 911\n👮‍♀️ woman police officer woman police law legal enforcement arrest 911 female\n🕵️ detective human spy detective\n🕵️‍♂️ man detective crime\n🕵️‍♀️ woman detective human spy detective female woman\n💂 guard protect\n💂‍♂️ man guard uk gb british male guy royal\n💂‍♀️ woman guard uk gb british female royal woman\n👷 construction worker labor build\n👷‍♂️ man construction worker male human wip guy build construction worker labor\n👷‍♀️ woman construction worker female human wip build construction worker labor woman\n🤴 prince boy man male crown royal king\n👸 princess girl woman female blond crown royal queen\n👳 person wearing turban headdress\n👳‍♂️ man wearing turban male indian hinduism arabs\n👳‍♀️ woman wearing turban female indian hinduism arabs woman\n👲 man with skullcap male boy chinese\n🧕 woman with headscarf female hijab mantilla tichel\n🤵 man in tuxedo couple marriage wedding groom\n👰 bride with veil couple marriage wedding woman bride\n🤰 pregnant woman baby\n🤱 breast feeding nursing baby\n👼 baby angel heaven wings halo\n🎅 santa claus festival man male xmas father christmas\n🤶 mrs claus woman female xmas mother christmas\n🦸 superhero marvel\n🦸‍♂️ man superhero man male good hero superpowers\n🦸‍♀️ woman superhero woman female good heroine superpowers\n🦹 supervillain marvel\n🦹‍♂️ man supervillain man male evil bad criminal hero superpowers\n🦹‍♀️ woman supervillain woman female evil bad criminal heroine superpowers\n🧙 mage magic\n🧙‍♂️ man mage man male mage sorcerer\n🧙‍♀️ woman mage woman female mage witch\n🧚 fairy wings magical\n🧚‍♂️ man fairy man male\n🧚‍♀️ woman fairy woman female\n🧛 vampire blood twilight\n🧛‍♂️ man vampire man male dracula\n🧛‍♀️ woman vampire woman female\n🧜 merperson sea\n🧜‍♂️ merman man male triton\n🧜‍♀️ mermaid woman female merwoman ariel\n🧝 elf magical\n🧝‍♂️ man elf man male\n🧝‍♀️ woman elf woman female\n🧞 genie magical wishes\n🧞‍♂️ man genie man male\n🧞‍♀️ woman genie woman female\n🧟 zombie dead\n🧟‍♂️ man zombie man male dracula undead walking dead\n🧟‍♀️ woman zombie woman female undead walking dead\n💆 person getting massage relax\n💆‍♂️ man getting massage male boy man head\n💆‍♀️ woman getting massage female girl woman head\n💇 person getting haircut hairstyle\n💇‍♂️ man getting haircut male boy man\n💇‍♀️ woman getting haircut female girl woman\n🚶 person walking move\n🚶‍♂️ man walking human feet steps\n🚶‍♀️ woman walking human feet steps woman female\n🧍 person standing still\n🧍‍♂️ man standing still\n🧍‍♀️ woman standing still\n🧎 person kneeling pray respectful\n🧎‍♂️ man kneeling pray respectful\n🧎‍♀️ woman kneeling respectful pray\n🧑‍🦯 person with probing cane blind\n👨‍🦯 man with probing cane blind\n👩‍🦯 woman with probing cane blind\n🧑‍🦼 person in motorized wheelchair disability accessibility\n👨‍🦼 man in motorized wheelchair disability accessibility\n👩‍🦼 woman in motorized wheelchair disability accessibility\n🧑‍🦽 person in manual wheelchair disability accessibility\n👨‍🦽 man in manual wheelchair disability accessibility\n👩‍🦽 woman in manual wheelchair disability accessibility\n🏃 person running move\n🏃‍♂️ man running man walking exercise race running\n🏃‍♀️ woman running woman walking exercise race running female\n💃 woman dancing female girl woman fun\n🕺 man dancing male boy fun dancer\n🕴️ man in suit levitating suit business levitate hover jump\n👯 people with bunny ears perform costume\n👯‍♂️ men with bunny ears male bunny men boys\n👯‍♀️ women with bunny ears female bunny women girls\n🧖 person in steamy room relax spa\n🧖‍♂️ man in steamy room male man spa steamroom sauna\n🧖‍♀️ woman in steamy room female woman spa steamroom sauna\n🧗 person climbing sport\n🧗‍♂️ man climbing sports hobby man male rock\n🧗‍♀️ woman climbing sports hobby woman female rock\n🤺 person fencing sports fencing sword\n🏇 horse racing animal betting competition gambling luck\n⛷️ skier sports winter snow\n🏂 snowboarder sports winter\n🏌️ person golfing sports business\n🏌️‍♂️ man golfing sport\n🏌️‍♀️ woman golfing sports business woman female\n🏄 person surfing sport sea\n🏄‍♂️ man surfing sports ocean sea summer beach\n🏄‍♀️ woman surfing sports ocean sea summer beach woman female\n🚣 person rowing boat sport move\n🚣‍♂️ man rowing boat sports hobby water ship\n🚣‍♀️ woman rowing boat sports hobby water ship woman female\n🏊 person swimming sport pool\n🏊‍♂️ man swimming sports exercise human athlete water summer\n🏊‍♀️ woman swimming sports exercise human athlete water summer woman female\n⛹️ person bouncing ball sports human\n⛹️‍♂️ man bouncing ball sport\n⛹️‍♀️ woman bouncing ball sports human woman female\n🏋️ person lifting weights sports training exercise\n🏋️‍♂️ man lifting weights sport\n🏋️‍♀️ woman lifting weights sports training exercise woman female\n🚴 person biking sport move\n🚴‍♂️ man biking sports bike exercise hipster\n🚴‍♀️ woman biking sports bike exercise hipster woman female\n🚵 person mountain biking sport move\n🚵‍♂️ man mountain biking transportation sports human race bike\n🚵‍♀️ woman mountain biking transportation sports human race bike woman female\n🤸 person cartwheeling sport gymnastic\n🤸‍♂️ man cartwheeling gymnastics\n🤸‍♀️ woman cartwheeling gymnastics\n🤼 people wrestling sport\n🤼‍♂️ men wrestling sports wrestlers\n🤼‍♀️ women wrestling sports wrestlers\n🤽 person playing water polo sport\n🤽‍♂️ man playing water polo sports pool\n🤽‍♀️ woman playing water polo sports pool\n🤾 person playing handball sport\n🤾‍♂️ man playing handball sports\n🤾‍♀️ woman playing handball sports\n🤹 person juggling performance balance\n🤹‍♂️ man juggling juggle balance skill multitask\n🤹‍♀️ woman juggling juggle balance skill multitask\n🧘 person in lotus position meditate\n🧘‍♂️ man in lotus position man male meditation yoga serenity zen mindfulness\n🧘‍♀️ woman in lotus position woman female meditation yoga serenity zen mindfulness\n🛀 person taking bath clean shower bathroom\n🛌 person in bed bed rest\n🧑‍🤝‍🧑 people holding hands friendship\n👭 women holding hands pair friendship couple love like female people human\n👫 woman and man holding hands pair people human love date dating like affection valentines marriage\n👬 men holding hands pair couple love like bromance friendship people human\n💏 kiss pair valentines love like dating marriage\n👩‍❤️‍💋‍👨 kiss woman man love\n👨‍❤️‍💋‍👨 kiss man man pair valentines love like dating marriage\n👩‍❤️‍💋‍👩 kiss woman woman pair valentines love like dating marriage\n💑 couple with heart pair love like affection human dating valentines marriage\n👩‍❤️‍👨 couple with heart woman man love\n👨‍❤️‍👨 couple with heart man man pair love like affection human dating valentines marriage\n👩‍❤️‍👩 couple with heart woman woman pair love like affection human dating valentines marriage\n👪 family home parents child mom dad father mother people human\n👨‍👩‍👦 family man woman boy love\n👨‍👩‍👧 family man woman girl home parents people human child\n👨‍👩‍👧‍👦 family man woman girl boy home parents people human children\n👨‍👩‍👦‍👦 family man woman boy boy home parents people human children\n👨‍👩‍👧‍👧 family man woman girl girl home parents people human children\n👨‍👨‍👦 family man man boy home parents people human children\n👨‍👨‍👧 family man man girl home parents people human children\n👨‍👨‍👧‍👦 family man man girl boy home parents people human children\n👨‍👨‍👦‍👦 family man man boy boy home parents people human children\n👨‍👨‍👧‍👧 family man man girl girl home parents people human children\n👩‍👩‍👦 family woman woman boy home parents people human children\n👩‍👩‍👧 family woman woman girl home parents people human children\n👩‍👩‍👧‍👦 family woman woman girl boy home parents people human children\n👩‍👩‍👦‍👦 family woman woman boy boy home parents people human children\n👩‍👩‍👧‍👧 family woman woman girl girl home parents people human children\n👨‍👦 family man boy home parent people human child\n👨‍👦‍👦 family man boy boy home parent people human children\n👨‍👧 family man girl home parent people human child\n👨‍👧‍👦 family man girl boy home parent people human children\n👨‍👧‍👧 family man girl girl home parent people human children\n👩‍👦 family woman boy home parent people human child\n👩‍👦‍👦 family woman boy boy home parent people human children\n👩‍👧 family woman girl home parent people human child\n👩‍👧‍👦 family woman girl boy home parent people human children\n👩‍👧‍👧 family woman girl girl home parent people human children\n🗣️ speaking head user person human sing say talk\n👤 bust in silhouette user person human\n👥 busts in silhouette user person human group team\n👣 footprints feet tracking walking beach\n🐵 monkey face animal nature circus\n🐒 monkey animal nature banana circus\n🦍 gorilla animal nature circus\n🦧 orangutan animal\n🐶 dog face animal friend nature woof puppy pet faithful\n🐕 dog animal nature friend doge pet faithful\n🦮 guide dog animal blind\n🐕‍🦺 service dog blind animal\n🐩 poodle dog animal 101 nature pet\n🐺 wolf animal nature wild\n🦊 fox animal nature face\n🦝 raccoon animal nature\n🐱 cat face animal meow nature pet kitten\n🐈 cat animal meow pet cats\n🦁 lion animal nature\n🐯 tiger face animal cat danger wild nature roar\n🐅 tiger animal nature roar\n🐆 leopard animal nature\n🐴 horse face animal brown nature\n🐎 horse animal gamble luck\n🦄 unicorn animal nature mystical\n🦓 zebra animal nature stripes safari\n🦌 deer animal nature horns venison\n🐮 cow face beef ox animal nature moo milk\n🐂 ox animal cow beef\n🐃 water buffalo animal nature ox cow\n🐄 cow beef ox animal nature moo milk\n🐷 pig face animal oink nature\n🐖 pig animal nature\n🐗 boar animal nature\n🐽 pig nose animal oink\n🐏 ram animal sheep nature\n🐑 ewe animal nature wool shipit\n🐐 goat animal nature\n🐪 camel animal hot desert hump\n🐫 two hump camel animal nature hot desert hump\n🦙 llama animal nature alpaca\n🦒 giraffe animal nature spots safari\n🐘 elephant animal nature nose th circus\n🦏 rhinoceros animal nature horn\n🦛 hippopotamus animal nature\n🐭 mouse face animal nature cheese wedge rodent\n🐁 mouse animal nature rodent\n🐀 rat animal mouse rodent\n🐹 hamster animal nature\n🐰 rabbit face animal nature pet spring magic bunny\n🐇 rabbit animal nature pet magic spring\n🐿️ chipmunk animal nature rodent squirrel\n🦔 hedgehog animal nature spiny\n🦇 bat animal nature blind vampire\n🐻 bear animal nature wild\n🐨 koala animal nature\n🐼 panda animal nature panda\n🦥 sloth animal\n🦦 otter animal\n🦨 skunk animal\n🦘 kangaroo animal nature australia joey hop marsupial\n🦡 badger animal nature honey\n🐾 paw prints animal tracking footprints dog cat pet feet\n🦃 turkey animal bird\n🐔 chicken animal cluck nature bird\n🐓 rooster animal nature chicken\n🐣 hatching chick animal chicken egg born baby bird\n🐤 baby chick animal chicken bird\n🐥 front facing baby chick animal chicken baby bird\n🐦 bird animal nature fly tweet spring\n🐧 penguin animal nature\n🕊️ dove animal bird\n🦅 eagle animal nature bird\n🦆 duck animal nature bird mallard\n🦢 swan animal nature bird\n🦉 owl animal nature bird hoot\n🦩 flamingo animal\n🦚 peacock animal nature peahen bird\n🦜 parrot animal nature bird pirate talk\n🐸 frog animal nature croak toad\n🐊 crocodile animal nature reptile lizard alligator\n🐢 turtle animal slow nature tortoise\n🦎 lizard animal nature reptile\n🐍 snake animal evil nature hiss python\n🐲 dragon face animal myth nature chinese green\n🐉 dragon animal myth nature chinese green\n🦕 sauropod animal nature dinosaur brachiosaurus brontosaurus diplodocus extinct\n🦖 t rex animal nature dinosaur tyrannosaurus extinct\n🐳 spouting whale animal nature sea ocean\n🐋 whale animal nature sea ocean\n🐬 dolphin animal nature fish sea ocean flipper fins beach\n🐟 fish animal food nature\n🐠 tropical fish animal swim ocean beach nemo\n🐡 blowfish animal nature food sea ocean\n🦈 shark animal nature fish sea ocean jaws fins beach\n🐙 octopus animal creature ocean sea nature beach\n🐚 spiral shell nature sea beach\n🐌 snail slow animal shell\n🦋 butterfly animal insect nature caterpillar\n🐛 bug animal insect nature worm\n🐜 ant animal insect nature bug\n🐝 honeybee animal insect nature bug spring honey\n🐞 lady beetle animal insect nature ladybug\n🦗 cricket animal cricket chirp\n🕷️ spider animal arachnid\n🕸️ spider web animal insect arachnid silk\n🦂 scorpion animal arachnid\n🦟 mosquito animal nature insect malaria\n🦠 microbe amoeba bacteria germs virus\n💐 bouquet flowers nature spring\n🌸 cherry blossom nature plant spring flower\n💮 white flower japanese spring\n🏵️ rosette flower decoration military\n🌹 rose flowers valentines love spring\n🥀 wilted flower plant nature flower\n🌺 hibiscus plant vegetable flowers beach\n🌻 sunflower nature plant fall\n🌼 blossom nature flowers yellow\n🌷 tulip flowers plant nature summer spring\n🌱 seedling plant nature grass lawn spring\n🌲 evergreen tree plant nature\n🌳 deciduous tree plant nature\n🌴 palm tree plant vegetable nature summer beach mojito tropical\n🌵 cactus vegetable plant nature\n🌾 sheaf of rice nature plant\n🌿 herb vegetable plant medicine weed grass lawn\n☘️ shamrock vegetable plant nature irish clover\n🍀 four leaf clover vegetable plant nature lucky irish\n🍁 maple leaf nature plant vegetable ca fall\n🍂 fallen leaf nature plant vegetable leaves\n🍃 leaf fluttering in wind nature plant tree vegetable grass lawn spring\n🍇 grapes fruit food wine\n🍈 melon fruit nature food\n🍉 watermelon fruit food picnic summer\n🍊 tangerine food fruit nature orange\n🍋 lemon fruit nature\n🍌 banana fruit food monkey\n🍍 pineapple fruit nature food\n🥭 mango fruit food tropical\n🍎 red apple fruit mac school\n🍏 green apple fruit nature\n🍐 pear fruit nature food\n🍑 peach fruit nature food\n🍒 cherries food fruit\n🍓 strawberry fruit food nature\n🥝 kiwi fruit fruit food\n🍅 tomato fruit vegetable nature food\n🥥 coconut fruit nature food palm\n🥑 avocado fruit food\n🍆 eggplant vegetable nature food aubergine\n🥔 potato food tuber vegatable starch\n🥕 carrot vegetable food orange\n🌽 ear of corn food vegetable plant\n🌶️ hot pepper food spicy chilli chili\n🥒 cucumber fruit food pickle\n🥬 leafy green food vegetable plant bok choy cabbage kale lettuce\n🥦 broccoli fruit food vegetable\n🧄 garlic food spice cook\n🧅 onion cook food spice\n🍄 mushroom plant vegetable\n🥜 peanuts food nut\n🌰 chestnut food squirrel\n🍞 bread food wheat breakfast toast\n🥐 croissant food bread french\n🥖 baguette bread food bread french\n🥨 pretzel food bread twisted\n🥯 bagel food bread bakery schmear\n🥞 pancakes food breakfast flapjacks hotcakes\n🧇 waffle food breakfast\n🧀 cheese wedge food chadder\n🍖 meat on bone good food drumstick\n🍗 poultry leg food meat drumstick bird chicken turkey\n🥩 cut of meat food cow meat cut chop lambchop porkchop\n🥓 bacon food breakfast pork pig meat\n🍔 hamburger meat fast food beef cheeseburger mcdonalds burger king\n🍟 french fries chips snack fast food\n🍕 pizza food party\n🌭 hot dog food frankfurter\n🥪 sandwich food lunch bread\n🌮 taco food mexican\n🌯 burrito food mexican\n🥙 stuffed flatbread food flatbread stuffed gyro\n🧆 falafel food\n🥚 egg food chicken breakfast\n🍳 cooking food breakfast kitchen egg\n🥘 shallow pan of food food cooking casserole paella\n🍲 pot of food food meat soup\n🥣 bowl with spoon food breakfast cereal oatmeal porridge\n🥗 green salad food healthy lettuce\n🍿 popcorn food movie theater films snack\n🧈 butter food cook\n🧂 salt condiment shaker\n🥫 canned food food soup\n🍱 bento box food japanese box\n🍘 rice cracker food japanese\n🍙 rice ball food japanese\n🍚 cooked rice food china asian\n🍛 curry rice food spicy hot indian\n🍜 steaming bowl food japanese noodle chopsticks\n🍝 spaghetti food italian noodle\n🍠 roasted sweet potato food nature\n🍢 oden food japanese\n🍣 sushi food fish japanese rice\n🍤 fried shrimp food animal appetizer summer\n🍥 fish cake with swirl food japan sea beach narutomaki pink swirl kamaboko surimi ramen\n🥮 moon cake food autumn\n🍡 dango food dessert sweet japanese barbecue meat\n🥟 dumpling food empanada pierogi potsticker\n🥠 fortune cookie food prophecy\n🥡 takeout box food leftovers\n🦀 crab animal crustacean\n🦞 lobster animal nature bisque claws seafood\n🦐 shrimp animal ocean nature seafood\n🦑 squid animal nature ocean sea\n🦪 oyster food\n🍦 soft ice cream food hot dessert summer\n🍧 shaved ice hot dessert summer\n🍨 ice cream food hot dessert\n🍩 doughnut food dessert snack sweet donut\n🍪 cookie food snack oreo chocolate sweet dessert\n🎂 birthday cake food dessert cake\n🍰 shortcake food dessert\n🧁 cupcake food dessert bakery sweet\n🥧 pie food dessert pastry\n🍫 chocolate bar food snack dessert sweet\n🍬 candy snack dessert sweet lolly\n🍭 lollipop food snack candy sweet\n🍮 custard dessert food\n🍯 honey pot bees sweet kitchen\n🍼 baby bottle food container milk\n🥛 glass of milk beverage drink cow\n☕ hot beverage beverage caffeine latte espresso coffee\n🍵 teacup without handle drink bowl breakfast green british\n🍶 sake wine drink drunk beverage japanese alcohol booze\n🍾 bottle with popping cork drink wine bottle celebration\n🍷 wine glass drink beverage drunk alcohol booze\n🍸 cocktail glass drink drunk alcohol beverage booze mojito\n🍹 tropical drink beverage cocktail summer beach alcohol booze mojito\n🍺 beer mug relax beverage drink drunk party pub summer alcohol booze\n🍻 clinking beer mugs relax beverage drink drunk party pub summer alcohol booze\n🥂 clinking glasses beverage drink party alcohol celebrate cheers wine champagne toast\n🥃 tumbler glass drink beverage drunk alcohol liquor booze bourbon scotch whisky glass shot\n🥤 cup with straw drink soda\n🧃 beverage box drink\n🧉 mate drink tea beverage\n🧊 ice water cold\n🥢 chopsticks food\n🍽️ fork and knife with plate food eat meal lunch dinner restaurant\n🍴 fork and knife cutlery kitchen\n🥄 spoon cutlery kitchen tableware\n🔪 kitchen knife knife blade cutlery kitchen weapon\n🏺 amphora vase jar\n🌍 globe showing europe africa globe world international\n🌎 globe showing americas globe world USA international\n🌏 globe showing asia australia globe world east international\n🌐 globe with meridians earth international world internet interweb i18n\n🗺️ world map location direction\n🗾 map of japan nation country japanese asia\n🧭 compass magnetic navigation orienteering\n🏔️ snow capped mountain photo nature environment winter cold\n⛰️ mountain photo nature environment\n🌋 volcano photo nature disaster\n🗻 mount fuji photo mountain nature japanese\n🏕️ camping photo outdoors tent\n🏖️ beach with umbrella weather summer sunny sand mojito\n🏜️ desert photo warm saharah\n🏝️ desert island photo tropical mojito\n🏞️ national park photo environment nature\n🏟️ stadium photo place sports concert venue\n🏛️ classical building art culture history\n🏗️ building construction wip working progress\n🧱 brick bricks\n🏘️ houses buildings photo\n🏚️ derelict house abandon evict broken building\n🏠 house building home\n🏡 house with garden home plant nature\n🏢 office building building bureau work\n🏣 japanese post office building envelope communication\n🏤 post office building email\n🏥 hospital building health surgery doctor\n🏦 bank building money sales cash business enterprise\n🏨 hotel building accomodation checkin\n🏩 love hotel like affection dating\n🏪 convenience store building shopping groceries\n🏫 school building student education learn teach\n🏬 department store building shopping mall\n🏭 factory building industry pollution smoke\n🏯 japanese castle photo building\n🏰 castle building royalty history\n💒 wedding love like affection couple marriage bride groom\n🗼 tokyo tower photo japanese\n🗽 statue of liberty american newyork\n⛪ church building religion christ\n🕌 mosque islam worship minaret\n🛕 hindu temple religion\n🕍 synagogue judaism worship temple jewish\n⛩️ shinto shrine temple japan kyoto\n🕋 kaaba mecca mosque islam\n⛲ fountain photo summer water fresh\n⛺ tent photo camping outdoors\n🌁 foggy photo mountain\n🌃 night with stars evening city downtown\n🏙️ cityscape photo night life urban\n🌄 sunrise over mountains view vacation photo\n🌅 sunrise morning view vacation photo\n🌆 cityscape at dusk photo evening sky buildings\n🌇 sunset photo good morning dawn\n🌉 bridge at night photo sanfrancisco\n♨️ hot springs bath warm relax\n🎠 carousel horse photo carnival\n🎡 ferris wheel photo carnival londoneye\n🎢 roller coaster carnival playground photo fun\n💈 barber pole hair salon style\n🎪 circus tent festival carnival party\n🚂 locomotive transportation vehicle train\n🚃 railway car transportation vehicle\n🚄 high speed train transportation vehicle\n🚅 bullet train transportation vehicle speed fast public travel\n🚆 train transportation vehicle\n🚇 metro transportation blue-square mrt underground tube\n🚈 light rail transportation vehicle\n🚉 station transportation vehicle public\n🚊 tram transportation vehicle\n🚝 monorail transportation vehicle\n🚞 mountain railway transportation vehicle\n🚋 tram car transportation vehicle carriage public travel\n🚌 bus car vehicle transportation\n🚍 oncoming bus vehicle transportation\n🚎 trolleybus bart transportation vehicle\n🚐 minibus vehicle car transportation\n🚑 ambulance health 911 hospital\n🚒 fire engine transportation cars vehicle\n🚓 police car vehicle cars transportation law legal enforcement\n🚔 oncoming police car vehicle law legal enforcement 911\n🚕 taxi uber vehicle cars transportation\n🚖 oncoming taxi vehicle cars uber\n🚗 automobile red transportation vehicle\n🚘 oncoming automobile car vehicle transportation\n🚙 sport utility vehicle transportation vehicle\n🚚 delivery truck cars transportation\n🚛 articulated lorry vehicle cars transportation express\n🚜 tractor vehicle car farming agriculture\n🏎️ racing car sports race fast formula f1\n🏍️ motorcycle race sports fast\n🛵 motor scooter vehicle vespa sasha\n🦽 manual wheelchair accessibility\n🦼 motorized wheelchair accessibility\n🛺 auto rickshaw move transportation\n🚲 bicycle sports bicycle exercise hipster\n🛴 kick scooter vehicle kick razor\n🛹 skateboard board\n🚏 bus stop transportation wait\n🛣️ motorway road cupertino interstate highway\n🛤️ railway track train transportation\n🛢️ oil drum barrell\n⛽ fuel pump gas station petroleum\n🚨 police car light police ambulance 911 emergency alert error pinged law legal\n🚥 horizontal traffic light transportation signal\n🚦 vertical traffic light transportation driving\n🛑 stop sign stop\n🚧 construction wip progress caution warning\n⚓ anchor ship ferry sea boat\n⛵ sailboat ship summer transportation water sailing\n🛶 canoe boat paddle water ship\n🚤 speedboat ship transportation vehicle summer\n🛳️ passenger ship yacht cruise ferry\n⛴️ ferry boat ship yacht\n🛥️ motor boat ship\n🚢 ship transportation titanic deploy\n✈️ airplane vehicle transportation flight fly\n🛩️ small airplane flight transportation fly vehicle\n🛫 airplane departure airport flight landing\n🛬 airplane arrival airport flight boarding\n🪂 parachute fly glide\n💺 seat sit airplane transport bus flight fly\n🚁 helicopter transportation vehicle fly\n🚟 suspension railway vehicle transportation\n🚠 mountain cableway transportation vehicle ski\n🚡 aerial tramway transportation vehicle ski\n🛰️ satellite communication gps orbit spaceflight NASA ISS\n🚀 rocket launch ship staffmode NASA outer space outer space fly\n🛸 flying saucer transportation vehicle ufo\n🛎️ bellhop bell service\n🧳 luggage packing travel\n⌛ hourglass done time clock oldschool limit exam quiz test\n⏳ hourglass not done oldschool time countdown\n⌚ watch time accessories\n⏰ alarm clock time wake\n⏱️ stopwatch time deadline\n⏲️ timer clock alarm\n🕰️ mantelpiece clock time\n🕛 twelve o clock time noon midnight midday late early schedule\n🕧 twelve thirty time late early schedule\n🕐 one o clock time late early schedule\n🕜 one thirty time late early schedule\n🕑 two o clock time late early schedule\n🕝 two thirty time late early schedule\n🕒 three o clock time late early schedule\n🕞 three thirty time late early schedule\n🕓 four o clock time late early schedule\n🕟 four thirty time late early schedule\n🕔 five o clock time late early schedule\n🕠 five thirty time late early schedule\n🕕 six o clock time late early schedule dawn dusk\n🕡 six thirty time late early schedule\n🕖 seven o clock time late early schedule\n🕢 seven thirty time late early schedule\n🕗 eight o clock time late early schedule\n🕣 eight thirty time late early schedule\n🕘 nine o clock time late early schedule\n🕤 nine thirty time late early schedule\n🕙 ten o clock time late early schedule\n🕥 ten thirty time late early schedule\n🕚 eleven o clock time late early schedule\n🕦 eleven thirty time late early schedule\n🌑 new moon nature twilight planet space night evening sleep\n🌒 waxing crescent moon nature twilight planet space night evening sleep\n🌓 first quarter moon nature twilight planet space night evening sleep\n🌔 waxing gibbous moon nature night sky gray twilight planet space evening sleep\n🌕 full moon nature yellow twilight planet space night evening sleep\n🌖 waning gibbous moon nature twilight planet space night evening sleep waxing gibbous moon\n🌗 last quarter moon nature twilight planet space night evening sleep\n🌘 waning crescent moon nature twilight planet space night evening sleep\n🌙 crescent moon night sleep sky evening magic\n🌚 new moon face nature twilight planet space night evening sleep\n🌛 first quarter moon face nature twilight planet space night evening sleep\n🌜 last quarter moon face nature twilight planet space night evening sleep\n🌡️ thermometer weather temperature hot cold\n☀️ sun weather nature brightness summer beach spring\n🌝 full moon face nature twilight planet space night evening sleep\n🌞 sun with face nature morning sky\n🪐 ringed planet outerspace\n⭐ star night yellow\n🌟 glowing star night sparkle awesome good magic\n🌠 shooting star night photo\n🌌 milky way photo space stars\n☁️ cloud weather sky\n⛅ sun behind cloud weather nature cloudy morning fall spring\n⛈️ cloud with lightning and rain weather lightning\n🌤️ sun behind small cloud weather\n🌥️ sun behind large cloud weather\n🌦️ sun behind rain cloud weather\n🌧️ cloud with rain weather\n🌨️ cloud with snow weather\n🌩️ cloud with lightning weather thunder\n🌪️ tornado weather cyclone twister\n🌫️ fog weather\n🌬️ wind face gust air\n🌀 cyclone weather swirl blue cloud vortex spiral whirlpool spin tornado hurricane typhoon\n🌈 rainbow nature happy unicorn face photo sky spring\n🌂 closed umbrella weather rain drizzle\n☂️ umbrella weather spring\n☔ umbrella with rain drops rainy weather spring\n⛱️ umbrella on ground weather summer\n⚡ high voltage thunder weather lightning bolt fast\n❄️ snowflake winter season cold weather christmas xmas\n☃️ snowman winter season cold weather christmas xmas frozen\n⛄ snowman without snow winter season cold weather christmas xmas frozen without snow\n☄️ comet space\n🔥 fire hot cook flame\n💧 droplet water drip faucet spring\n🌊 water wave sea water wave nature tsunami disaster\n🎃 jack o lantern halloween light pumpkin creepy fall\n🎄 christmas tree festival vacation december xmas celebration\n🎆 fireworks photo festival carnival congratulations\n🎇 sparkler stars night shine\n🧨 firecracker dynamite boom explode explosion explosive\n✨ sparkles stars shine shiny cool awesome good magic\n🎈 balloon party celebration birthday circus\n🎉 party popper party congratulations birthday magic circus celebration tada\n🎊 confetti ball festival party birthday circus\n🎋 tanabata tree plant nature branch summer\n🎍 pine decoration plant nature vegetable panda pine decoration\n🎎 japanese dolls japanese toy kimono\n🎏 carp streamer fish japanese koinobori carp banner\n🎐 wind chime nature ding spring bell\n🎑 moon viewing ceremony photo japan asia tsukimi\n🧧 red envelope gift\n🎀 ribbon decoration pink girl bowtie\n🎁 wrapped gift present birthday christmas xmas\n🎗️ reminder ribbon sports cause support awareness\n🎟️ admission tickets sports concert entrance\n🎫 ticket event concert pass\n🎖️ military medal award winning army\n🏆 trophy win award contest place ftw ceremony\n🏅 sports medal award winning\n🥇 1st place medal award winning first\n🥈 2nd place medal award second\n🥉 3rd place medal award third\n⚽ soccer ball sports football\n⚾ baseball sports balls\n🥎 softball sports balls\n🏀 basketball sports balls NBA\n🏐 volleyball sports balls\n🏈 american football sports balls NFL\n🏉 rugby football sports team\n🎾 tennis sports balls green\n🥏 flying disc sports frisbee ultimate\n🎳 bowling sports fun play\n🏏 cricket game sports\n🏑 field hockey sports\n🏒 ice hockey sports\n🥍 lacrosse sports ball stick\n🏓 ping pong sports pingpong\n🏸 badminton sports\n🥊 boxing glove sports fighting\n🥋 martial arts uniform judo karate taekwondo\n🥅 goal net sports\n⛳ flag in hole sports business flag hole summer\n⛸️ ice skate sports\n🎣 fishing pole food hobby summer\n🤿 diving mask sport ocean\n🎽 running shirt play pageant\n🎿 skis sports winter cold snow\n🛷 sled sleigh luge toboggan\n🥌 curling stone sports\n🎯 direct hit game play bar target bullseye\n🪀 yo yo toy\n🪁 kite wind fly\n🎱 pool 8 ball pool hobby game luck magic\n🔮 crystal ball disco party magic circus fortune teller\n🧿 nazar amulet bead charm\n🎮 video game play console PS4 Wii GameCube controller\n🕹️ joystick game play\n🎰 slot machine bet gamble vegas fruit machine luck casino\n🎲 game die dice random tabletop play luck\n🧩 puzzle piece interlocking puzzle piece\n🧸 teddy bear plush stuffed\n♠️ spade suit poker cards suits magic\n♥️ heart suit poker cards magic suits\n♦️ diamond suit poker cards magic suits\n♣️ club suit poker cards magic suits\n♟️ chess pawn expendable\n🃏 joker poker cards game play magic\n🀄 mahjong red dragon game play chinese kanji\n🎴 flower playing cards game sunset red\n🎭 performing arts acting theater drama\n🖼️ framed picture photography\n🎨 artist palette design paint draw colors\n🧵 thread needle sewing spool string\n🧶 yarn ball crochet knit\n👓 glasses fashion accessories eyesight nerdy dork geek\n🕶️ sunglasses face cool accessories\n🥽 goggles eyes protection safety\n🥼 lab coat doctor experiment scientist chemist\n🦺 safety vest protection\n👔 necktie shirt suitup formal fashion cloth business\n👕 t shirt fashion cloth casual shirt tee\n👖 jeans fashion shopping\n🧣 scarf neck winter clothes\n🧤 gloves hands winter clothes\n🧥 coat jacket\n🧦 socks stockings clothes\n👗 dress clothes fashion shopping\n👘 kimono dress fashion women female japanese\n🥻 sari dress\n🩱 one piece swimsuit fashion\n🩲 briefs clothing\n🩳 shorts clothing\n👙 bikini swimming female woman girl fashion beach summer\n👚 woman s clothes fashion shopping bags female\n👛 purse fashion accessories money sales shopping\n👜 handbag fashion accessory accessories shopping\n👝 clutch bag bag accessories shopping\n🛍️ shopping bags mall buy purchase\n🎒 backpack student education bag backpack\n👞 man s shoe fashion male\n👟 running shoe shoes sports sneakers\n🥾 hiking boot backpacking camping hiking\n🥿 flat shoe ballet slip-on slipper\n👠 high heeled shoe fashion shoes female pumps stiletto\n👡 woman s sandal shoes fashion flip flops\n🩰 ballet shoes dance\n👢 woman s boot shoes fashion\n👑 crown king kod leader royalty lord\n👒 woman s hat fashion accessories female lady spring\n🎩 top hat magic gentleman classy circus\n🎓 graduation cap school college degree university graduation cap hat legal learn education\n🧢 billed cap cap baseball\n⛑️ rescue worker s helmet construction build\n📿 prayer beads dhikr religious\n💄 lipstick female girl fashion woman\n💍 ring wedding propose marriage valentines diamond fashion jewelry gem engagement\n💎 gem stone blue ruby diamond jewelry\n🔇 muted speaker sound volume silence quiet\n🔈 speaker low volume sound volume silence broadcast\n🔉 speaker medium volume volume speaker broadcast\n🔊 speaker high volume volume noise noisy speaker broadcast\n📢 loudspeaker volume sound\n📣 megaphone sound speaker volume\n📯 postal horn instrument music\n🔔 bell sound notification christmas xmas chime\n🔕 bell with slash sound volume mute quiet silent\n🎼 musical score treble clef compose\n🎵 musical note score tone sound\n🎶 musical notes music score\n🎙️ studio microphone sing recording artist talkshow\n🎚️ level slider scale\n🎛️ control knobs dial\n🎤 microphone sound music PA sing talkshow\n🎧 headphone music score gadgets\n📻 radio communication music podcast program\n🎷 saxophone music instrument jazz blues\n🎸 guitar music instrument\n🎹 musical keyboard piano instrument compose\n🎺 trumpet music brass\n🎻 violin music instrument orchestra symphony\n🪕 banjo music instructment\n🥁 drum music instrument drumsticks snare\n📱 mobile phone technology apple gadgets dial\n📲 mobile phone with arrow iphone incoming\n☎️ telephone technology communication dial telephone\n📞 telephone receiver technology communication dial\n📟 pager bbcall oldschool 90s\n📠 fax machine communication technology\n🔋 battery power energy sustain\n🔌 electric plug charger power\n💻 laptop technology laptop screen display monitor\n🖥️ desktop computer technology computing screen\n🖨️ printer paper ink\n⌨️ keyboard technology computer type input text\n🖱️ computer mouse click\n🖲️ trackball technology trackpad\n💽 computer disk technology record data disk 90s\n💾 floppy disk oldschool technology save 90s 80s\n💿 optical disk technology dvd disk disc 90s\n📀 dvd cd disk disc\n🧮 abacus calculation\n🎥 movie camera film record\n🎞️ film frames movie\n📽️ film projector video tape record movie\n🎬 clapper board movie film record\n📺 television technology program oldschool show television\n📷 camera gadgets photography\n📸 camera with flash photography gadgets\n📹 video camera film record\n📼 videocassette record video oldschool 90s 80s\n🔍 magnifying glass tilted left search zoom find detective\n🔎 magnifying glass tilted right search zoom find detective\n🕯️ candle fire wax\n💡 light bulb light electricity idea\n🔦 flashlight dark camping sight night\n🏮 red paper lantern light paper halloween spooky\n🪔 diya lamp lighting\n📔 notebook with decorative cover classroom notes record paper study\n📕 closed book read library knowledge textbook learn\n📖 open book book read library knowledge literature learn study\n📗 green book read library knowledge study\n📘 blue book read library knowledge learn study\n📙 orange book read library knowledge textbook study\n📚 books literature library study\n📓 notebook stationery record notes paper study\n📒 ledger notes paper\n📃 page with curl documents office paper\n📜 scroll documents ancient history paper\n📄 page facing up documents office paper information\n📰 newspaper press headline\n🗞️ rolled up newspaper press headline\n📑 bookmark tabs favorite save order tidy\n🔖 bookmark favorite label save\n🏷️ label sale tag\n💰 money bag dollar payment coins sale\n💴 yen banknote money sales japanese dollar currency\n💵 dollar banknote money sales bill currency\n💶 euro banknote money sales dollar currency\n💷 pound banknote british sterling money sales bills uk england currency\n💸 money with wings dollar bills payment sale\n💳 credit card money sales dollar bill payment shopping\n🧾 receipt accounting expenses\n💹 chart increasing with yen green-square graph presentation stats\n💱 currency exchange money sales dollar travel\n💲 heavy dollar sign money sales payment currency buck\n✉️ envelope letter postal inbox communication\n📧 e mail communication inbox\n📨 incoming envelope email inbox\n📩 envelope with arrow email communication\n📤 outbox tray inbox email\n📥 inbox tray email documents\n📦 package mail gift cardboard box moving\n📫 closed mailbox with raised flag email inbox communication\n📪 closed mailbox with lowered flag email communication inbox\n📬 open mailbox with raised flag email inbox communication\n📭 open mailbox with lowered flag email inbox\n📮 postbox email letter envelope\n🗳️ ballot box with ballot election vote\n✏️ pencil stationery write paper writing school study\n✒️ black nib pen stationery writing write\n🖋️ fountain pen stationery writing write\n🖊️ pen stationery writing write\n🖌️ paintbrush drawing creativity art\n🖍️ crayon drawing creativity\n📝 memo write documents stationery pencil paper writing legal exam quiz test study compose\n💼 briefcase business documents work law legal job career\n📁 file folder documents business office\n📂 open file folder documents load\n🗂️ card index dividers organizing business stationery\n📅 calendar calendar schedule\n📆 tear off calendar schedule date planning\n🗒️ spiral notepad memo stationery\n🗓️ spiral calendar date schedule planning\n📇 card index business stationery\n📈 chart increasing graph presentation stats recovery business economics money sales good success\n📉 chart decreasing graph presentation stats recession business economics money sales bad failure\n📊 bar chart graph presentation stats\n📋 clipboard stationery documents\n📌 pushpin stationery mark here\n📍 round pushpin stationery location map here\n📎 paperclip documents stationery\n🖇️ linked paperclips documents stationery\n📏 straight ruler stationery calculate length math school drawing architect sketch\n📐 triangular ruler stationery math architect sketch\n✂️ scissors stationery cut\n🗃️ card file box business stationery\n🗄️ file cabinet filing organizing\n🗑️ wastebasket bin trash rubbish garbage toss\n🔒 locked security password padlock\n🔓 unlocked privacy security\n🔏 locked with pen security secret\n🔐 locked with key security privacy\n🔑 key lock door password\n🗝️ old key lock door password\n🔨 hammer tools build create\n🪓 axe tool chop cut\n⛏️ pick tools dig\n⚒️ hammer and pick tools build create\n🛠️ hammer and wrench tools build create\n🗡️ dagger weapon\n⚔️ crossed swords weapon\n🔫 pistol violence weapon pistol revolver\n🏹 bow and arrow sports\n🛡️ shield protection security\n🔧 wrench tools diy ikea fix maintainer\n🔩 nut and bolt handy tools fix\n⚙️ gear cog\n🗜️ clamp tool\n⚖️ balance scale law fairness weight\n🦯 probing cane accessibility\n🔗 link rings url\n⛓️ chains lock arrest\n🧰 toolbox tools diy fix maintainer mechanic\n🧲 magnet attraction magnetic\n⚗️ alembic distilling science experiment chemistry\n🧪 test tube chemistry experiment lab science\n🧫 petri dish bacteria biology culture lab\n🧬 dna biologist genetics life\n🔬 microscope laboratory experiment zoomin science study\n🔭 telescope stars space zoom science astronomy\n📡 satellite antenna communication future radio space\n💉 syringe health hospital drugs blood medicine needle doctor nurse\n🩸 drop of blood period hurt harm wound\n💊 pill health medicine doctor pharmacy drug\n🩹 adhesive bandage heal\n🩺 stethoscope health\n🚪 door house entry exit\n🛏️ bed sleep rest\n🛋️ couch and lamp read chill\n🪑 chair sit furniture\n🚽 toilet restroom wc washroom bathroom potty\n🚿 shower clean water bathroom\n🛁 bathtub clean shower bathroom\n🪒 razor cut\n🧴 lotion bottle moisturizer sunscreen\n🧷 safety pin diaper\n🧹 broom cleaning sweeping witch\n🧺 basket laundry\n🧻 roll of paper roll\n🧼 soap bar bathing cleaning lather\n🧽 sponge absorbing cleaning porous\n🧯 fire extinguisher quench\n🛒 shopping cart trolley\n🚬 cigarette kills tobacco cigarette joint smoke\n⚰️ coffin vampire dead die death rip graveyard cemetery casket funeral box\n⚱️ funeral urn dead die death rip ashes\n🗿 moai rock easter island moai\n🏧 atm sign money sales cash blue-square payment bank\n🚮 litter in bin sign blue-square sign human info\n🚰 potable water blue-square liquid restroom cleaning faucet\n♿ wheelchair symbol blue-square disabled accessibility\n🚹 men s room toilet restroom wc blue-square gender male\n🚺 women s room purple-square woman female toilet loo restroom gender\n🚻 restroom blue-square toilet refresh wc gender\n🚼 baby symbol orange-square child\n🚾 water closet toilet restroom blue-square\n🛂 passport control custom blue-square\n🛃 customs passport border blue-square\n🛄 baggage claim blue-square airport transport\n🛅 left luggage blue-square travel\n⚠️ warning exclamation wip alert error problem issue\n🚸 children crossing school warning danger sign driving yellow-diamond\n⛔ no entry limit security privacy bad denied stop circle\n🚫 prohibited forbid stop limit denied disallow circle\n🚳 no bicycles cyclist prohibited circle\n🚭 no smoking cigarette blue-square smell smoke\n🚯 no littering trash bin garbage circle\n🚱 non potable water drink faucet tap circle\n🚷 no pedestrians rules crossing walking circle\n📵 no mobile phones iphone mute circle\n🔞 no one under eighteen 18 drink pub night minor circle\n☢️ radioactive nuclear danger\n☣️ biohazard danger\n⬆️ up arrow blue-square continue top direction\n↗️ up right arrow blue-square point direction diagonal northeast\n➡️ right arrow blue-square next\n↘️ down right arrow blue-square direction diagonal southeast\n⬇️ down arrow blue-square direction bottom\n↙️ down left arrow blue-square direction diagonal southwest\n⬅️ left arrow blue-square previous back\n↖️ up left arrow blue-square point direction diagonal northwest\n↕️ up down arrow blue-square direction way vertical\n↔️ left right arrow shape direction horizontal sideways\n↩️ right arrow curving left back return blue-square undo enter\n↪️ left arrow curving right blue-square return rotate direction\n⤴️ right arrow curving up blue-square direction top\n⤵️ right arrow curving down blue-square direction bottom\n🔃 clockwise vertical arrows sync cycle round repeat\n🔄 counterclockwise arrows button blue-square sync cycle\n🔙 back arrow arrow words return\n🔚 end arrow words arrow\n🔛 on arrow arrow words\n🔜 soon arrow arrow words\n🔝 top arrow words blue-square\n🛐 place of worship religion church temple prayer\n⚛️ atom symbol science physics chemistry\n🕉️ om hinduism buddhism sikhism jainism\n✡️ star of david judaism\n☸️ wheel of dharma hinduism buddhism sikhism jainism\n☯️ yin yang balance\n✝️ latin cross christianity\n☦️ orthodox cross suppedaneum religion\n☪️ star and crescent islam\n☮️ peace symbol hippie\n🕎 menorah hanukkah candles jewish\n🔯 dotted six pointed star purple-square religion jewish hexagram\n♈ aries sign purple-square zodiac astrology\n♉ taurus purple-square sign zodiac astrology\n♊ gemini sign zodiac purple-square astrology\n♋ cancer sign zodiac purple-square astrology\n♌ leo sign purple-square zodiac astrology\n♍ virgo sign zodiac purple-square astrology\n♎ libra sign purple-square zodiac astrology\n♏ scorpio sign zodiac purple-square astrology scorpio\n♐ sagittarius sign zodiac purple-square astrology\n♑ capricorn sign zodiac purple-square astrology\n♒ aquarius sign purple-square zodiac astrology\n♓ pisces purple-square sign zodiac astrology\n⛎ ophiuchus sign purple-square constellation astrology\n🔀 shuffle tracks button blue-square shuffle music random\n🔁 repeat button loop record\n🔂 repeat single button blue-square loop\n▶️ play button blue-square right direction play\n⏩ fast forward button blue-square play speed continue\n⏭️ next track button forward next blue-square\n⏯️ play or pause button blue-square play pause\n◀️ reverse button blue-square left direction\n⏪ fast reverse button play blue-square\n⏮️ last track button backward\n🔼 upwards button blue-square triangle direction point forward top\n⏫ fast up button blue-square direction top\n🔽 downwards button blue-square direction bottom\n⏬ fast down button blue-square direction bottom\n⏸️ pause button pause blue-square\n⏹️ stop button blue-square\n⏺️ record button blue-square\n⏏️ eject button blue-square\n🎦 cinema blue-square record film movie curtain stage theater\n🔅 dim button sun afternoon warm summer\n🔆 bright button sun light\n📶 antenna bars blue-square reception phone internet connection wifi bluetooth bars\n📳 vibration mode orange-square phone\n📴 mobile phone off mute orange-square silence quiet\n♀️ female sign woman women lady girl\n♂️ male sign man boy men\n⚕️ medical symbol health hospital\n♾️ infinity forever\n♻️ recycling symbol arrow environment garbage trash\n⚜️ fleur de lis decorative scout\n🔱 trident emblem weapon spear\n📛 name badge fire forbid\n🔰 japanese symbol for beginner badge shield\n⭕ hollow red circle circle round\n✅ check mark button green-square ok agree vote election answer tick\n☑️ check box with check ok agree confirm black-square vote election yes tick\n✔️ check mark ok nike answer yes tick\n✖️ multiplication sign math calculation\n❌ cross mark no delete remove cancel red\n❎ cross mark button x green-square no deny\n➕ plus sign math calculation addition more increase\n➖ minus sign math calculation subtract less\n➗ division sign divide math calculation\n➰ curly loop scribble draw shape squiggle\n➿ double curly loop tape cassette\n〽️ part alternation mark graph presentation stats business economics bad\n✳️ eight spoked asterisk star sparkle green-square\n✴️ eight pointed star orange-square shape polygon\n❇️ sparkle stars green-square awesome good fireworks\n‼️ double exclamation mark exclamation surprise\n⁉️ exclamation question mark wat punctuation surprise\n❓ question mark doubt confused\n❔ white question mark doubts gray huh confused\n❕ white exclamation mark surprise punctuation gray wow warning\n❗ exclamation mark heavy exclamation mark danger surprise punctuation wow warning\n〰️ wavy dash draw line moustache mustache squiggle scribble\n©️ copyright ip license circle law legal\n®️ registered alphabet circle\n™️ trade mark trademark brand law legal\n#️⃣ keycap  symbol blue-square twitter\n*️⃣ keycap  star keycap\n0️⃣ keycap 0 0 numbers blue-square null\n1️⃣ keycap 1 blue-square numbers 1\n2️⃣ keycap 2 numbers 2 prime blue-square\n3️⃣ keycap 3 3 numbers prime blue-square\n4️⃣ keycap 4 4 numbers blue-square\n5️⃣ keycap 5 5 numbers blue-square prime\n6️⃣ keycap 6 6 numbers blue-square\n7️⃣ keycap 7 7 numbers blue-square prime\n8️⃣ keycap 8 8 blue-square numbers\n9️⃣ keycap 9 blue-square numbers 9\n🔟 keycap 10 numbers 10 blue-square\n🔠 input latin uppercase alphabet words blue-square\n🔡 input latin lowercase blue-square alphabet\n🔢 input numbers numbers blue-square\n🔣 input symbols blue-square music note ampersand percent glyphs characters\n🔤 input latin letters blue-square alphabet\n🅰️ a button red-square alphabet letter\n🆎 ab button red-square alphabet\n🅱️ b button red-square alphabet letter\n🆑 cl button alphabet words red-square\n🆒 cool button words blue-square\n🆓 free button blue-square words\nℹ️ information blue-square alphabet letter\n🆔 id button purple-square words\nⓂ️ circled m alphabet blue-circle letter\n🆕 new button blue-square words start\n🆖 ng button blue-square words shape icon\n🅾️ o button alphabet red-square letter\n🆗 ok button good agree yes blue-square\n🅿️ p button cars blue-square alphabet letter\n🆘 sos button help red-square words emergency 911\n🆙 up button blue-square above high\n🆚 vs button words orange-square\n🈁 japanese here button blue-square here katakana japanese destination\n🈂️ japanese service charge button japanese blue-square katakana\n🈷️ japanese monthly amount button chinese month moon japanese orange-square kanji\n🈶 japanese not free of charge button orange-square chinese have kanji\n🈯 japanese reserved button chinese point green-square kanji\n🉐 japanese bargain button chinese kanji obtain get circle\n🈹 japanese discount button cut divide chinese kanji pink-square\n🈚 japanese free of charge button nothing chinese kanji japanese orange-square\n🈲 japanese prohibited button kanji japanese chinese forbidden limit restricted red-square\n🉑 japanese acceptable button ok good chinese kanji agree yes orange-circle\n🈸 japanese application button chinese japanese kanji orange-square\n🈴 japanese passing grade button japanese chinese join kanji red-square\n🈳 japanese vacancy button kanji japanese chinese empty sky blue-square\n㊗️ japanese congratulations button chinese kanji japanese red-circle\n㊙️ japanese secret button privacy chinese sshh kanji red-circle\n🈺 japanese open for business button japanese opening hours orange-square\n🈵 japanese no vacancy button full chinese japanese red-square kanji\n🔴 red circle shape error danger\n🟠 orange circle round\n🟡 yellow circle round\n🟢 green circle round\n🔵 blue circle shape icon button\n🟣 purple circle round\n🟤 brown circle round\n⚫ black circle shape button round\n⚪ white circle shape round\n🟥 red square\n🟧 orange square\n🟨 yellow square\n🟩 green square\n🟦 blue square\n🟪 purple square\n🟫 brown square\n⬛ black large square shape icon button\n⬜ white large square shape icon stone button\n◼️ black medium square shape button icon\n◻️ white medium square shape stone icon\n◾ black medium small square icon shape button\n◽ white medium small square shape stone icon button\n▪️ black small square shape icon\n▫️ white small square shape icon\n🔶 large orange diamond shape jewel gem\n🔷 large blue diamond shape jewel gem\n🔸 small orange diamond shape jewel gem\n🔹 small blue diamond shape jewel gem\n🔺 red triangle pointed up shape direction up top\n🔻 red triangle pointed down shape direction bottom\n💠 diamond with a dot jewel blue gem crystal fancy\n🔘 radio button input old music circle\n🔳 white square button shape input\n🔲 black square button shape input frame\n🏁 chequered flag contest finishline race gokart\n🚩 triangular flag mark milestone place\n🎌 crossed flags japanese nation country border\n🏴 black flag pirate\n🏳️ white flag losing loser lost surrender give up fail\n🏳️‍🌈 rainbow flag flag rainbow pride gay lgbt glbt queer homosexual lesbian bisexual transgender\n🏴‍☠️ pirate flag skull crossbones flag banner\n🇦🇨 flag ascension island\n🇦🇩 flag andorra ad flag nation country banner andorra\n🇦🇪 flag united arab emirates united arab emirates flag nation country banner united arab emirates\n🇦🇫 flag afghanistan af flag nation country banner afghanistan\n🇦🇬 flag antigua barbuda antigua barbuda flag nation country banner antigua barbuda\n🇦🇮 flag anguilla ai flag nation country banner anguilla\n🇦🇱 flag albania al flag nation country banner albania\n🇦🇲 flag armenia am flag nation country banner armenia\n🇦🇴 flag angola ao flag nation country banner angola\n🇦🇶 flag antarctica aq flag nation country banner antarctica\n🇦🇷 flag argentina ar flag nation country banner argentina\n🇦🇸 flag american samoa american ws flag nation country banner american samoa\n🇦🇹 flag austria at flag nation country banner austria\n🇦🇺 flag australia au flag nation country banner australia\n🇦🇼 flag aruba aw flag nation country banner aruba\n🇦🇽 flag aland islands Åland islands flag nation country banner aland islands\n🇦🇿 flag azerbaijan az flag nation country banner azerbaijan\n🇧🇦 flag bosnia herzegovina bosnia herzegovina flag nation country banner bosnia herzegovina\n🇧🇧 flag barbados bb flag nation country banner barbados\n🇧🇩 flag bangladesh bd flag nation country banner bangladesh\n🇧🇪 flag belgium be flag nation country banner belgium\n🇧🇫 flag burkina faso burkina faso flag nation country banner burkina faso\n🇧🇬 flag bulgaria bg flag nation country banner bulgaria\n🇧🇭 flag bahrain bh flag nation country banner bahrain\n🇧🇮 flag burundi bi flag nation country banner burundi\n🇧🇯 flag benin bj flag nation country banner benin\n🇧🇱 flag st barthelemy saint barthélemy flag nation country banner st barthelemy\n🇧🇲 flag bermuda bm flag nation country banner bermuda\n🇧🇳 flag brunei bn darussalam flag nation country banner brunei\n🇧🇴 flag bolivia bo flag nation country banner bolivia\n🇧🇶 flag caribbean netherlands bonaire flag nation country banner caribbean netherlands\n🇧🇷 flag brazil br flag nation country banner brazil\n🇧🇸 flag bahamas bs flag nation country banner bahamas\n🇧🇹 flag bhutan bt flag nation country banner bhutan\n🇧🇻 flag bouvet island norway\n🇧🇼 flag botswana bw flag nation country banner botswana\n🇧🇾 flag belarus by flag nation country banner belarus\n🇧🇿 flag belize bz flag nation country banner belize\n🇨🇦 flag canada ca flag nation country banner canada\n🇨🇨 flag cocos islands cocos keeling islands flag nation country banner cocos islands\n🇨🇩 flag congo kinshasa congo democratic republic flag nation country banner congo kinshasa\n🇨🇫 flag central african republic central african republic flag nation country banner central african republic\n🇨🇬 flag congo brazzaville congo flag nation country banner congo brazzaville\n🇨🇭 flag switzerland ch flag nation country banner switzerland\n🇨🇮 flag cote d ivoire ivory coast flag nation country banner cote d ivoire\n🇨🇰 flag cook islands cook islands flag nation country banner cook islands\n🇨🇱 flag chile flag nation country banner chile\n🇨🇲 flag cameroon cm flag nation country banner cameroon\n🇨🇳 flag china china chinese prc flag country nation banner china\n🇨🇴 flag colombia co flag nation country banner colombia\n🇨🇵 flag clipperton island\n🇨🇷 flag costa rica costa rica flag nation country banner costa rica\n🇨🇺 flag cuba cu flag nation country banner cuba\n🇨🇻 flag cape verde cabo verde flag nation country banner cape verde\n🇨🇼 flag curacao curaçao flag nation country banner curacao\n🇨🇽 flag christmas island christmas island flag nation country banner christmas island\n🇨🇾 flag cyprus cy flag nation country banner cyprus\n🇨🇿 flag czechia cz flag nation country banner czechia\n🇩🇪 flag germany german nation flag country banner germany\n🇩🇬 flag diego garcia\n🇩🇯 flag djibouti dj flag nation country banner djibouti\n🇩🇰 flag denmark dk flag nation country banner denmark\n🇩🇲 flag dominica dm flag nation country banner dominica\n🇩🇴 flag dominican republic dominican republic flag nation country banner dominican republic\n🇩🇿 flag algeria dz flag nation country banner algeria\n🇪🇦 flag ceuta melilla\n🇪🇨 flag ecuador ec flag nation country banner ecuador\n🇪🇪 flag estonia ee flag nation country banner estonia\n🇪🇬 flag egypt eg flag nation country banner egypt\n🇪🇭 flag western sahara western sahara flag nation country banner western sahara\n🇪🇷 flag eritrea er flag nation country banner eritrea\n🇪🇸 flag spain spain flag nation country banner spain\n🇪🇹 flag ethiopia et flag nation country banner ethiopia\n🇪🇺 flag european union european union flag banner\n🇫🇮 flag finland fi flag nation country banner finland\n🇫🇯 flag fiji fj flag nation country banner fiji\n🇫🇰 flag falkland islands falkland islands malvinas flag nation country banner falkland islands\n🇫🇲 flag micronesia micronesia federated states flag nation country banner micronesia\n🇫🇴 flag faroe islands faroe islands flag nation country banner faroe islands\n🇫🇷 flag france banner flag nation france french country france\n🇬🇦 flag gabon ga flag nation country banner gabon\n🇬🇧 flag united kingdom united kingdom great britain northern ireland flag nation country banner british UK english england union jack united kingdom\n🇬🇩 flag grenada gd flag nation country banner grenada\n🇬🇪 flag georgia ge flag nation country banner georgia\n🇬🇫 flag french guiana french guiana flag nation country banner french guiana\n🇬🇬 flag guernsey gg flag nation country banner guernsey\n🇬🇭 flag ghana gh flag nation country banner ghana\n🇬🇮 flag gibraltar gi flag nation country banner gibraltar\n🇬🇱 flag greenland gl flag nation country banner greenland\n🇬🇲 flag gambia gm flag nation country banner gambia\n🇬🇳 flag guinea gn flag nation country banner guinea\n🇬🇵 flag guadeloupe gp flag nation country banner guadeloupe\n🇬🇶 flag equatorial guinea equatorial gn flag nation country banner equatorial guinea\n🇬🇷 flag greece gr flag nation country banner greece\n🇬🇸 flag south georgia south sandwich islands south georgia sandwich islands flag nation country banner south georgia south sandwich islands\n🇬🇹 flag guatemala gt flag nation country banner guatemala\n🇬🇺 flag guam gu flag nation country banner guam\n🇬🇼 flag guinea bissau gw bissau flag nation country banner guinea bissau\n🇬🇾 flag guyana gy flag nation country banner guyana\n🇭🇰 flag hong kong sar china hong kong flag nation country banner hong kong sar china\n🇭🇲 flag heard mcdonald islands\n🇭🇳 flag honduras hn flag nation country banner honduras\n🇭🇷 flag croatia hr flag nation country banner croatia\n🇭🇹 flag haiti ht flag nation country banner haiti\n🇭🇺 flag hungary hu flag nation country banner hungary\n🇮🇨 flag canary islands canary islands flag nation country banner canary islands\n🇮🇩 flag indonesia flag nation country banner indonesia\n🇮🇪 flag ireland ie flag nation country banner ireland\n🇮🇱 flag israel il flag nation country banner israel\n🇮🇲 flag isle of man isle man flag nation country banner isle of man\n🇮🇳 flag india in flag nation country banner india\n🇮🇴 flag british indian ocean territory british indian ocean territory flag nation country banner british indian ocean territory\n🇮🇶 flag iraq iq flag nation country banner iraq\n🇮🇷 flag iran iran islamic republic flag nation country banner iran\n🇮🇸 flag iceland is flag nation country banner iceland\n🇮🇹 flag italy italy flag nation country banner italy\n🇯🇪 flag jersey je flag nation country banner jersey\n🇯🇲 flag jamaica jm flag nation country banner jamaica\n🇯🇴 flag jordan jo flag nation country banner jordan\n🇯🇵 flag japan japanese nation flag country banner japan\n🇰🇪 flag kenya ke flag nation country banner kenya\n🇰🇬 flag kyrgyzstan kg flag nation country banner kyrgyzstan\n🇰🇭 flag cambodia kh flag nation country banner cambodia\n🇰🇮 flag kiribati ki flag nation country banner kiribati\n🇰🇲 flag comoros km flag nation country banner comoros\n🇰🇳 flag st kitts nevis saint kitts nevis flag nation country banner st kitts nevis\n🇰🇵 flag north korea north korea nation flag country banner north korea\n🇰🇷 flag south korea south korea nation flag country banner south korea\n🇰🇼 flag kuwait kw flag nation country banner kuwait\n🇰🇾 flag cayman islands cayman islands flag nation country banner cayman islands\n🇰🇿 flag kazakhstan kz flag nation country banner kazakhstan\n🇱🇦 flag laos lao democratic republic flag nation country banner laos\n🇱🇧 flag lebanon lb flag nation country banner lebanon\n🇱🇨 flag st lucia saint lucia flag nation country banner st lucia\n🇱🇮 flag liechtenstein li flag nation country banner liechtenstein\n🇱🇰 flag sri lanka sri lanka flag nation country banner sri lanka\n🇱🇷 flag liberia lr flag nation country banner liberia\n🇱🇸 flag lesotho ls flag nation country banner lesotho\n🇱🇹 flag lithuania lt flag nation country banner lithuania\n🇱🇺 flag luxembourg lu flag nation country banner luxembourg\n🇱🇻 flag latvia lv flag nation country banner latvia\n🇱🇾 flag libya ly flag nation country banner libya\n🇲🇦 flag morocco ma flag nation country banner morocco\n🇲🇨 flag monaco mc flag nation country banner monaco\n🇲🇩 flag moldova moldova republic flag nation country banner moldova\n🇲🇪 flag montenegro me flag nation country banner montenegro\n🇲🇫 flag st martin\n🇲🇬 flag madagascar mg flag nation country banner madagascar\n🇲🇭 flag marshall islands marshall islands flag nation country banner marshall islands\n🇲🇰 flag north macedonia macedonia flag nation country banner north macedonia\n🇲🇱 flag mali ml flag nation country banner mali\n🇲🇲 flag myanmar mm flag nation country banner myanmar\n🇲🇳 flag mongolia mn flag nation country banner mongolia\n🇲🇴 flag macao sar china macao flag nation country banner macao sar china\n🇲🇵 flag northern mariana islands northern mariana islands flag nation country banner northern mariana islands\n🇲🇶 flag martinique mq flag nation country banner martinique\n🇲🇷 flag mauritania mr flag nation country banner mauritania\n🇲🇸 flag montserrat ms flag nation country banner montserrat\n🇲🇹 flag malta mt flag nation country banner malta\n🇲🇺 flag mauritius mu flag nation country banner mauritius\n🇲🇻 flag maldives mv flag nation country banner maldives\n🇲🇼 flag malawi mw flag nation country banner malawi\n🇲🇽 flag mexico mx flag nation country banner mexico\n🇲🇾 flag malaysia my flag nation country banner malaysia\n🇲🇿 flag mozambique mz flag nation country banner mozambique\n🇳🇦 flag namibia na flag nation country banner namibia\n🇳🇨 flag new caledonia new caledonia flag nation country banner new caledonia\n🇳🇪 flag niger ne flag nation country banner niger\n🇳🇫 flag norfolk island norfolk island flag nation country banner norfolk island\n🇳🇬 flag nigeria flag nation country banner nigeria\n🇳🇮 flag nicaragua ni flag nation country banner nicaragua\n🇳🇱 flag netherlands nl flag nation country banner netherlands\n🇳🇴 flag norway no flag nation country banner norway\n🇳🇵 flag nepal np flag nation country banner nepal\n🇳🇷 flag nauru nr flag nation country banner nauru\n🇳🇺 flag niue nu flag nation country banner niue\n🇳🇿 flag new zealand new zealand flag nation country banner new zealand\n🇴🇲 flag oman om symbol flag nation country banner oman\n🇵🇦 flag panama pa flag nation country banner panama\n🇵🇪 flag peru pe flag nation country banner peru\n🇵🇫 flag french polynesia french polynesia flag nation country banner french polynesia\n🇵🇬 flag papua new guinea papua new guinea flag nation country banner papua new guinea\n🇵🇭 flag philippines ph flag nation country banner philippines\n🇵🇰 flag pakistan pk flag nation country banner pakistan\n🇵🇱 flag poland pl flag nation country banner poland\n🇵🇲 flag st pierre miquelon saint pierre miquelon flag nation country banner st pierre miquelon\n🇵🇳 flag pitcairn islands pitcairn flag nation country banner pitcairn islands\n🇵🇷 flag puerto rico puerto rico flag nation country banner puerto rico\n🇵🇸 flag palestinian territories palestine palestinian territories flag nation country banner palestinian territories\n🇵🇹 flag portugal pt flag nation country banner portugal\n🇵🇼 flag palau pw flag nation country banner palau\n🇵🇾 flag paraguay py flag nation country banner paraguay\n🇶🇦 flag qatar qa flag nation country banner qatar\n🇷🇪 flag reunion réunion flag nation country banner reunion\n🇷🇴 flag romania ro flag nation country banner romania\n🇷🇸 flag serbia rs flag nation country banner serbia\n🇷🇺 flag russia russian federation flag nation country banner russia\n🇷🇼 flag rwanda rw flag nation country banner rwanda\n🇸🇦 flag saudi arabia flag nation country banner saudi arabia\n🇸🇧 flag solomon islands solomon islands flag nation country banner solomon islands\n🇸🇨 flag seychelles sc flag nation country banner seychelles\n🇸🇩 flag sudan sd flag nation country banner sudan\n🇸🇪 flag sweden se flag nation country banner sweden\n🇸🇬 flag singapore sg flag nation country banner singapore\n🇸🇭 flag st helena saint helena ascension tristan cunha flag nation country banner st helena\n🇸🇮 flag slovenia si flag nation country banner slovenia\n🇸🇯 flag svalbard jan mayen\n🇸🇰 flag slovakia sk flag nation country banner slovakia\n🇸🇱 flag sierra leone sierra leone flag nation country banner sierra leone\n🇸🇲 flag san marino san marino flag nation country banner san marino\n🇸🇳 flag senegal sn flag nation country banner senegal\n🇸🇴 flag somalia so flag nation country banner somalia\n🇸🇷 flag suriname sr flag nation country banner suriname\n🇸🇸 flag south sudan south sd flag nation country banner south sudan\n🇸🇹 flag sao tome principe sao tome principe flag nation country banner sao tome principe\n🇸🇻 flag el salvador el salvador flag nation country banner el salvador\n🇸🇽 flag sint maarten sint maarten dutch flag nation country banner sint maarten\n🇸🇾 flag syria syrian arab republic flag nation country banner syria\n🇸🇿 flag eswatini sz flag nation country banner eswatini\n🇹🇦 flag tristan da cunha\n🇹🇨 flag turks caicos islands turks caicos islands flag nation country banner turks caicos islands\n🇹🇩 flag chad td flag nation country banner chad\n🇹🇫 flag french southern territories french southern territories flag nation country banner french southern territories\n🇹🇬 flag togo tg flag nation country banner togo\n🇹🇭 flag thailand th flag nation country banner thailand\n🇹🇯 flag tajikistan tj flag nation country banner tajikistan\n🇹🇰 flag tokelau tk flag nation country banner tokelau\n🇹🇱 flag timor leste timor leste flag nation country banner timor leste\n🇹🇲 flag turkmenistan flag nation country banner turkmenistan\n🇹🇳 flag tunisia tn flag nation country banner tunisia\n🇹🇴 flag tonga to flag nation country banner tonga\n🇹🇷 flag turkey turkey flag nation country banner turkey\n🇹🇹 flag trinidad tobago trinidad tobago flag nation country banner trinidad tobago\n🇹🇻 flag tuvalu flag nation country banner tuvalu\n🇹🇼 flag taiwan tw flag nation country banner taiwan\n🇹🇿 flag tanzania tanzania united republic flag nation country banner tanzania\n🇺🇦 flag ukraine ua flag nation country banner ukraine\n🇺🇬 flag uganda ug flag nation country banner uganda\n🇺🇲 flag u s outlying islands\n🇺🇳 flag united nations un flag banner\n🇺🇸 flag united states united states america flag nation country banner united states\n🇺🇾 flag uruguay uy flag nation country banner uruguay\n🇺🇿 flag uzbekistan uz flag nation country banner uzbekistan\n🇻🇦 flag vatican city vatican city flag nation country banner vatican city\n🇻🇨 flag st vincent grenadines saint vincent grenadines flag nation country banner st vincent grenadines\n🇻🇪 flag venezuela ve bolivarian republic flag nation country banner venezuela\n🇻🇬 flag british virgin islands british virgin islands bvi flag nation country banner british virgin islands\n🇻🇮 flag u s virgin islands virgin islands us flag nation country banner u s virgin islands\n🇻🇳 flag vietnam viet nam flag nation country banner vietnam\n🇻🇺 flag vanuatu vu flag nation country banner vanuatu\n🇼🇫 flag wallis futuna wallis futuna flag nation country banner wallis futuna\n🇼🇸 flag samoa ws flag nation country banner samoa\n🇽🇰 flag kosovo xk flag nation country banner kosovo\n🇾🇪 flag yemen ye flag nation country banner yemen\n🇾🇹 flag mayotte yt flag nation country banner mayotte\n🇿🇦 flag south africa south africa flag nation country banner south africa\n🇿🇲 flag zambia zm flag nation country banner zambia\n🇿🇼 flag zimbabwe zw flag nation country banner zimbabwe\n🏴󠁧󠁢󠁥󠁮󠁧󠁿 flag england flag english\n🏴󠁧󠁢󠁳󠁣󠁴󠁿 flag scotland flag scottish\n🏴󠁧󠁢󠁷󠁬󠁳󠁿 flag wales flag welsh\n🥲 smiling face with tear sad cry pretend\n🥸 disguised face pretent brows glasses moustache\n🤌 pinched fingers size tiny small\n🫀 anatomical heart health heartbeat\n🫁 lungs breathe\n🥷 ninja ninjutsu skills japanese\n🤵‍♂️ man in tuxedo formal fashion\n🤵‍♀️ woman in tuxedo formal fashion\n👰‍♂️ man with veil wedding marriage\n👰‍♀️ woman with veil wedding marriage\n👩‍🍼 woman feeding baby birth food\n👨‍🍼 man feeding baby birth food\n🧑‍🍼 person feeding baby birth food\n🧑‍🎄 mx claus christmas\n🫂 people hugging care\n🐈‍⬛ black cat superstition luck\n🦬 bison ox\n🦣 mammoth elephant tusks\n🦫 beaver animal rodent\n🐻‍❄️ polar bear animal arctic\n🦤 dodo animal bird\n🪶 feather bird fly\n🦭 seal animal creature sea\n🪲 beetle insect\n🪳 cockroach insect pests\n🪰 fly insect\n🪱 worm animal\n🪴 potted plant greenery house\n🫐 blueberries fruit\n🫒 olive fruit\n🫑 bell pepper fruit plant\n🫓 flatbread flour food\n🫔 tamale food masa\n🫕 fondue cheese pot food\n🫖 teapot drink hot\n🧋 bubble tea taiwan boba milk tea straw\n🪨 rock stone\n🪵 wood nature timber trunk\n🛖 hut house structure\n🛻 pickup truck car transportation\n🛼 roller skate footwear sports\n🪄 magic wand supernature power\n🪅 pinata mexico candy celebration\n🪆 nesting dolls matryoshka toy\n🪡 sewing needle stitches\n🪢 knot rope scout\n🩴 thong sandal footwear summer\n🪖 military helmet army protection\n🪗 accordion music\n🪘 long drum music\n🪙 coin money currency\n🪃 boomerang weapon\n🪚 carpentry saw cut chop\n🪛 screwdriver tools\n🪝 hook tools\n🪜 ladder tools\n🛗 elevator lift\n🪞 mirror reflection\n🪟 window scenery\n🪠 plunger toilet\n🪤 mouse trap cheese\n🪣 bucket water container\n🪥 toothbrush hygiene dental\n🪦 headstone death rip grave\n🪧 placard announcement\n⚧️ transgender symbol lgbtq\n🏳️‍⚧️ transgender flag lgbtq\n😶‍🌫️ face in clouds shower steam dream\n😮‍💨 face exhaling relieve relief tired sigh\n😵‍💫 face with spiral eyes sick ill confused nauseous nausea\n❤️‍🔥 heart on fire passionate enthusiastic\n❤️‍🩹 mending heart broken heart bandage wounded\n🧔‍♂️ man beard facial hair\n🧔‍♀️ woman beard facial hair\n🫠 melting face hot heat\n🫢 face with open eyes and hand over mouth silence secret shock surprise\n🫣 face with peeking eye scared frightening embarrassing\n🫡 saluting face respect salute\n🫥 dotted line face invisible lonely isolation depression\n🫤 face with diagonal mouth skeptic confuse frustrated indifferent\n🥹 face holding back tears touched gratitude\n🫱 rightwards hand palm offer\n🫲 leftwards hand palm offer\n🫳 palm down hand palm drop\n🫴 palm up hand lift offer demand\n🫰 hand with index finger and thumb crossed heart love money expensive\n🫵 index pointing at the viewer you recruit\n🫶 heart hands love appreciation support\n🫦 biting lip flirt sexy pain worry\n🫅 person with crown royalty power\n🫃 pregnant man baby belly\n🫄 pregnant person baby belly\n🧌 troll mystical monster\n🪸 coral ocean sea reef\n🪷 lotus flower calm meditation\n🪹 empty nest bird\n🪺 nest with eggs bird\n🫘 beans food\n🫗 pouring liquid cup water\n🫙 jar container sauce\n🛝 playground slide fun park\n🛞 wheel car transport\n🛟 ring buoy life saver life preserver\n🪬 hamsa religion protection\n🪩 mirror ball disco dance party\n🪫 low battery drained dead\n🩼 crutch accessibility assist\n🩻 x-ray skeleton medicine\n🫧 bubbles soap fun carbonation sparkling\n🪪 identification card document\n🟰 heavy equals sign math\n¿? question upside down reversed spanish\n← left arrow\n↑ up arrow\n→ right arrow\n↓ down arrow\n←↑→↓ all directions up down left right arrows\nAH↗️HA↘️HA↗️HA↘️ pekora arrows hahaha rabbit\n• dot circle separator\n「」 japanese quote square bracket\n¯\\_(ツ)_/¯ shrug idk i dont know\n↵ enter key return\n𝕏  twitter x logo\n👉👈 etou ughhhhhhh shy\n👉👌 put it in imagination perv\n🫨 shaking face tremble shake shocked\n🩷 pink heart love\n🩵 light blue heart love cyan\n🩶 grey heart gray love\n🫷 leftwards pushing hand stop halt left\n🫸 rightwards pushing hand stop halt right\n🫎 moose animal antlers\n🫏 donkey animal mule ass\n🪽 wing bird feather fly\n🐦‍⬛ black bird crow raven rook\n🪿 goose bird honk\n🪼 jellyfish sea ocean sting\n🪻 hyacinth flower spring\n🫚 ginger root spice food\n🫛 pea pod peas vegetable food\n🪭 folding hand fan fan cool\n🪮 hair pick afro comb\n🪇 maracas instrument music shake\n🪈 flute instrument music\n🪯 khanda sikh religion symbol\n🛜 wireless wifi wi-fi internet network\n🙂‍↔️ head shaking horizontally no shake\n🙂‍↕️ head shaking vertically yes nod\n🚶‍➡️ person walking facing right walk\n🚶‍♀️‍➡️ woman walking facing right walk\n🚶‍♂️‍➡️ man walking facing right walk\n🧎‍➡️ person kneeling facing right kneel\n🧎‍♀️‍➡️ woman kneeling facing right kneel\n🧎‍♂️‍➡️ man kneeling facing right kneel\n🧑‍🦯‍➡️ person with white cane facing right accessibility blind\n👨‍🦯‍➡️ man with white cane facing right accessibility blind\n👩‍🦯‍➡️ woman with white cane facing right accessibility blind\n🧑‍🦼‍➡️ person in motorized wheelchair facing right accessibility\n👨‍🦼‍➡️ man in motorized wheelchair facing right accessibility\n👩‍🦼‍➡️ woman in motorized wheelchair facing right accessibility\n🧑‍🦽‍➡️ person in manual wheelchair facing right accessibility\n👨‍🦽‍➡️ man in manual wheelchair facing right accessibility\n👩‍🦽‍➡️ woman in manual wheelchair facing right accessibility\n🏃‍➡️ person running facing right run\n🏃‍♀️‍➡️ woman running facing right run\n🏃‍♂️‍➡️ man running facing right run\n🧑‍🧑‍🧒 family adult adult child parents\n🧑‍🧑‍🧒‍🧒 family adult adult child child parents\n🧑‍🧒 family adult child parent\n🧑‍🧒‍🧒 family adult child child parent\n🐦‍🔥 phoenix fire bird rebirth\n🍋‍🟩 lime fruit citrus green\n🍄‍🟫 brown mushroom fungi\n⛓️‍💥 broken chain snap shatter\n🫩 face with bags under eyes tired sleepy exhausted\n🫆 fingerprint id biometric\n🪾 leafless tree barren dead winter\n🫜 root vegetable food turnip radish\n🪉 harp instrument music\n🪏 shovel dig tool\n🫟 splatter splash stain mess\n🇨🇶 flag sark\n🫪 distorted face anxiety shocked panic\n🫯 fight cloud comic brawl dust\n🫈 hairy creature sasquatch bigfoot\n🧑‍🩰 ballet dancer dance ballerina\n🫍 orca killer whale\n🛘 landslide rockfall disaster\n🪊 trombone instrument music\n🪎 treasure chest gold loot pirate\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/launch_first_available.sh",
    "content": "#!/usr/bin/env bash\nfor cmd in \"$@\"; do\n    [[ -z \"$cmd\" ]] && continue\n    eval \"command -v ${cmd%% *}\" >/dev/null 2>&1 || continue\n    eval \"$cmd\" &\n    exit\ndone\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/snip_to_search.sh",
    "content": "#!/usr/bin/env bash\ngrim -g \"$(slurp)\" /tmp/image.png\nimageLink=$(curl -sF files[]=@/tmp/image.png 'https://uguu.se/upload' | jq -r '.files[0].url')\nxdg-open \"https://lens.google.com/uploadbyurl?url=${imageLink}\"\nrm /tmp/image.png\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/scripts/start_geoclue_agent.sh",
    "content": "#!/usr/bin/env bash\n\n# Check if GeoClue agent is already running\nif pgrep -f 'geoclue-2.0/demos/agent' > /dev/null; then\n    echo \"GeoClue agent is already running.\"\n    exit 0\nfi\n\n# List of known possible GeoClue agent paths\nAGENT_PATHS=(\n  /usr/libexec/geoclue-2.0/demos/agent\n  /usr/lib/geoclue-2.0/demos/agent\n  \"$HOME/.nix-profile/libexec/geoclue-2.0/demos/agent\"\n  \"$HOME/.nix-profile/lib/geoclue-2.0/demos/agent\"\n  /run/current-system/sw/libexec/geoclue-2.0/demos/agent\n)\n\n# Find the first valid agent path\nfor path in \"${AGENT_PATHS[@]}\"; do\n    if [ -x \"$path\" ]; then\n        echo \"Starting GeoClue agent from: $path\"\n        \"$path\" & # starts in the background\n        exit 0\n    fi\ndone\n\n# If we got here, none of the paths worked\necho \"GeoClue agent not found in known paths.\"\necho \"Please install GeoClue or update the script with the correct path.\"\nexit 1\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/services/create_custom_config.lua",
    "content": "require(\"hyprland/lib\")\n\nhl.on(\"hyprland.start\", function()\n   local homeDir = os.getenv(\"HOME\")\n   if string.len(homeDir) == 0 then\n      return\n   end\n   local baseCustomDir = homeDir .. \"/.config/hypr/custom\"\n   local files = {\n      baseCustomDir .. \"/env.lua\",\n      baseCustomDir .. \"/execs.lua\",\n      baseCustomDir .. \"/general.lua\",\n      baseCustomDir .. \"/keybinds.lua\",\n      baseCustomDir .. \"/rules.lua\",\n      baseCustomDir .. \"/variables.lua\"\n   }\n   local createdFiles = 0\n   for _, file in ipairs(files) do\n      if not is_file_exists(file) then\n         create_if_not_exists(file)\n         createdFiles = createdFiles + 1\n      end\n   end\n\n   if createdFiles > 0 then\n      -- hl.exec_cmd(\"notify-send 'Hyprland config' 'Created \" .. createdFiles .. \" custom Hyprland config files in \" .. baseCustomDir .. \"' -a 'Hyprland'\")\n      -- hl.exec_cmd(\"hyprctl reload\")\n   end\nend)\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/services/init.lua",
    "content": "require(\"hyprland/services/create_custom_config\")\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/shellOverrides/main.lua",
    "content": "-- DO NOT EDIT THIS FILE. IT IS MANAGED BY THE SHELL AND FOLLOWS STRICT RULES\n-- In other words, I ain't writing a lua parser for this, so please be a good boi/girl/whatever\n"
  },
  {
    "path": "dots/.config/hypr/hyprland/variables.lua",
    "content": "-- Default variables\n-- Copy these to ~/.config/hypr/custom/variables.lua to make changes in a dotfiles-update-friendly manner\n\n-- The folder within ~/.config/quickshell containing the config\nhl.env(\"qsConfig\", \"ii\")\n\n-- Apps\n-- PULL REQUESTS ADDING MORE WILL NOT BE ACCEPTED, CONFIG FOR YOURSELF\nterminal = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'foot' 'kitty -1' 'alacritty' 'wezterm' 'konsole' 'kgx' 'uxterm' 'xterm'\"\nfileManager = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'dolphin' 'nautilus' 'nemo' 'thunar' 'kitty -1 fish -c yazi'\"\nbrowser = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'google-chrome-stable' 'zen-browser' 'firefox' 'brave' 'chromium' 'microsoft-edge-stable' 'opera' 'librewolf'\"\ncodeEditor = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'windsurf' 'antigravity' 'code' 'codium' 'cursor' 'zed' 'zedit' 'zeditor' 'kate' 'gnome-text-editor' 'emacs' 'command -v nvim && kitty -1 nvim' 'command -v micro && kitty -1 micro'\"\nofficeSoftware = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'wps' 'onlyoffice-desktopeditors' 'libreoffice'\"\ntextEditor = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'kate' 'gnome-text-editor' 'emacs'\"\nvolumeMixer = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'pavucontrol-qt' 'pavucontrol'\"\nsettingsApp = \"XDG_CURRENT_DESKTOP=gnome ~/.config/hypr/hyprland/scripts/launch_first_available.sh 'qs -p ~/.config/quickshell/$qsConfig/settings.qml' 'systemsettings' 'gnome-control-center' 'better-control'\"\ntaskManager = \"~/.config/hypr/hyprland/scripts/launch_first_available.sh 'gnome-system-monitor' 'plasma-systemmonitor --page-name Processes' 'command -v btop && kitty -1 fish -c btop'\"\n\nworkspaceGroupSize = 10\n"
  },
  {
    "path": "dots/.config/hypr/hyprland.lua",
    "content": "-- This file sources other files in `hyprland` and `custom` folders\n-- You wanna add your stuff in files in `custom`\n\n-- Internal stuff --\nrequire(\"hyprland.lib\")\nrequire(\"hyprland.services\")\n\n-- Environment variables --\nrequire(\"hyprland.env\")\nif is_file_exists(HOME .. \"/.config/hypr/custom/env.lua\") then\n    require(\"custom.env\")\nend\n\n-- Default configurations --\nrequire(\"hyprland.execs\")\nrequire(\"hyprland.general\")\nrequire(\"hyprland.rules\")\nrequire(\"hyprland.colors\")\nrequire(\"hyprland.keybinds\")\n\n-- Custom configurations --\nif is_file_exists(HOME .. \"/.config/hypr/custom/execs.lua\") then\n    require(\"custom.execs\")\nend\nif is_file_exists(HOME .. \"/.config/hypr/custom/general.lua\") then\n    require(\"custom.general\")\nend\nif is_file_exists(HOME .. \"/.config/hypr/custom/rules.lua\") then\n    require(\"custom.rules\")\nend\nif is_file_exists(HOME .. \"/.config/hypr/custom/keybinds.lua\") then\n    require(\"custom.keybinds\")\nend\n\n-- nwg-displays support: re-add the files if it updates later\n-- require(\"workspaces\")\n-- require(\"monitors\")\n\n-- Shell overrides --\nrequire(\"hyprland.shellOverrides.main\")\n"
  },
  {
    "path": "dots/.config/hypr/hyprlock/check-capslock.sh",
    "content": "#!/bin/env bash\n\nMAIN_KB_CAPS=$(hyprctl devices | grep -B 6 \"main: yes\" | grep \"capsLock\" | head -1 | awk '{print $2}')\n\nif [ \"$MAIN_KB_CAPS\" = \"yes\" ]; then\n    echo \"Caps Lock active\"\nelse\n    echo \"\"\nfi\n"
  },
  {
    "path": "dots/.config/hypr/hyprlock/colors.conf",
    "content": "# This configuration is generated by matugen\n# Changing these variables with matugen still enabled will overwrite them.\n\n$text_color = rgba(d9e2ffFF)\n$entry_background_color = rgba(00194411)\n$entry_border_color = rgba(8f909955)\n$entry_color = rgba(d9e2ffFF)\n$font_family = Google Sans Flex Medium\n$font_family_clock = Google Sans Flex Medium\n$font_material_symbols = Material Symbols Rounded\n"
  },
  {
    "path": "dots/.config/hypr/hyprlock/status.sh",
    "content": "#!/usr/bin/env bash\n\n############ Variables ############\nenable_battery=false\nbattery_charging=false\n\n####### Check availability ########\nfor battery in /sys/class/power_supply/*BAT*; do\n  if [[ -f \"$battery/uevent\" ]]; then\n    enable_battery=true\n    if [[ $(cat /sys/class/power_supply/*/status | head -1) == \"Charging\" ]]; then\n      battery_charging=true\n    fi\n    break\n  fi\ndone\n\n############# Output #############\nif [[ $enable_battery == true ]]; then\n  if [[ $battery_charging == true ]]; then\n    echo -n \"(+) \"\n  fi\n  echo -n \"$(cat /sys/class/power_supply/*/capacity | head -1)\"%\n  if [[ $battery_charging == false ]]; then\n    echo -n \" remaining\"\n  fi\nfi\n\necho ''"
  },
  {
    "path": "dots/.config/hypr/hyprlock.conf",
    "content": "source=~/.config/hypr/hyprlock/colors.conf\n\nbackground {\n    color = rgba(181818FF)\n}\ninput-field {\n    monitor =\n    size = 250, 50\n    outline_thickness = 2\n    dots_size = 0.1\n    dots_spacing = 0.3\n    outer_color = $entry_border_color\n    inner_color = $entry_background_color\n    font_color = $entry_color\n    fade_on_empty = true\n\n    position = 0, 20\n    halign = center\n    valign = center\n}\n\nlabel {\n    monitor =\n    text = $LAYOUT\n    color = $text_color\n    font_size = 14\n    font_family = $font_family\n    position = -30, 30\n    halign = right\n    valign = bottom\n}\n\nlabel { # Caps Lock Warning\n    monitor =\n    text = cmd[update:250] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/check-capslock.sh\n    color = $text_color\n    font_size = 13\n    font_family = $font_family\n    position = 0, -25\n    halign = center\n    valign = center\n}\n\n\nlabel { # Clock\n    monitor =\n    text = $TIME\n    color = $text_color\n    font_size = 65\n    font_family = $font_family_clock\n\n    position = 0, 300\n    halign = center\n    valign = center\n}\nlabel { # Date\n    monitor =\n    text = cmd[update:5000] date +\"%A, %B %d\"\n    color = $text_color\n    font_size = 17\n    font_family = $font_family_clock\n\n    position = 0, 240\n    halign = center\n    valign = center\n}\n\nlabel { # User\n    monitor =\n    text =     $USER\n    color = $text_color\n    outline_thickness = 2\n    dots_size = 0.2 # Scale of input-field height, 0.2 - 0.8\n    dots_spacing = 0.2 # Scale of dots' absolute size, 0.0 - 1.0\n    dots_center = true\n    font_size = 20\n    font_family = $font_family\n    position = 0, 50\n    halign = center\n    valign = bottom\n}\n\nlabel { # Status\n    monitor =\n    text = cmd[update:5000] ${XDG_CONFIG_HOME:-$HOME/.config}/hypr/hyprlock/status.sh\n    color = $text_color\n    font_size = 14\n    font_family = $font_family\n\n    position = 30, -30\n    halign = left\n    valign = top\n}"
  },
  {
    "path": "dots/.config/kde-material-you-colors/config.conf",
    "content": "[CUSTOM]\n# INSTRUCTIONS\n# Run kde-material-you-colors with no arguments from terminal\n# to debug your configuration changing in real time.\n\n# Monitor to get wallpaper from\n# For me main is 0 but second one is 6, play with this to find yours\n# Default is 0\nmonitor = 0\n\n# File containing absolute path of an image (Takes precedence over automatic wallpaper detection)\n# Commented by default\nfile = ~/.local/state/quickshell/user/generated/wallpaper/path.txt\n\n# List of 7 space separated colors (hex or rgb) to be used for text in pywal/konsole/KSyntaxHighlighting instead of wallpaper ones\n# Accepted values are hex e.g #ff0000 and rgb e.g 255,0,0 colors (rgb is converted to hex)\n# Commented by default\n# Example using catppuccin color scheme:\ncustom_colors_list = #ED8796 #A6DA95 #EED49F #8AADF4 #F5BDE6 #8BD5CA #f5a97f\n\n# Enable Light mode\n# Accepted values are True or False\n# Commented by default to follow System Color Setting (Material You Light/Dark only)\n# NOTE:\n# Will fallback to dark mode if not defined here or enabled in Settings\n#light = False\n\n# Alternative color mode (default is 0), some images return more than one color, this will use either the matched or last color\n# Default is 0\nncolor = 0\n\n# Light scheme icons theme\n#iconslight = OneUI-light\niconslight = breeze-plus\n\n# Dark scheme icons theme\n#iconsdark = OneUI-dark\niconsdark = breeze-plus-dark\n\n# Use pywal to theme other programs using Material You colors\npywal=False\n\n# The amount of perceptible color for backgrounds in dark mode\n# A number between 0 and 4.0 (limited for accessibility purposes)\n# Defaults to 1 if not set\n#light_blend_multiplier = 1.0\n\n# The amount of perceptible color for backgrounds in dark mode\n# A number between 0 and 4.0 (limited for accessibility purposes)\n# Defaults to 1 if not set\n#dark_blend_multiplier = 1.0\n\n# A script/command that will be executed on start or wallpaper/dark/light/settings change\n# example below using https://github.com/vlevit/notify-send.sh to send a desktop notification:\n#on_change_hook = notify-send.sh \"kde-material-you-colors\" \"This is a test\" -t 2000\n\n# Scheme Variant\n# Changes between Material You scheme variants (0-8)\n# 0 = Content\n# 1 = Expressive\n# 2 = Fidelity\n# 3 = Monochrome\n# 4 = Neutral\n# 5 = TonalSpot\n# 6 = Vibrant\n# 7 = Rainbow\n# 8 = FruitSalad\n# Default is 5\nscheme_variant = 5\n\n# Colorfulness\nchroma_multiplier = 1\n\n# Brightness\n# An integer between 0.5 and 1.5\ntone_multiplier = 1\n"
  },
  {
    "path": "dots/.config/kdeglobals",
    "content": "[ColorEffects:Disabled]\nChangeSelectionColor=\nColor=#211f24\nColorAmount=0.5\nColorEffect=3\nContrastAmount=0\nContrastEffect=0\nEnable=\nIntensityAmount=0\nIntensityEffect=0\n\n[ColorEffects:Inactive]\nChangeSelectionColor=true\nColor=#0c0a10\nColorAmount=0.025\nColorEffect=0\nContrastAmount=0.1\nContrastEffect=0\nEnable=true\nIntensityAmount=0\nIntensityEffect=0\n\n[Colors:Button]\nBackgroundAlternate=#47434c\nBackgroundNormal=#2b292f\nDecorationFocus=#cdb9fb\nDecorationHover=#cdb9fb\nForegroundActive=#e6e0e9\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#e6e0e9\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[Colors:Complementary]\nBackgroundAlternate=#121016\nBackgroundNormal=#211f24\nDecorationFocus=#cdb9fb\nDecorationHover=#cdb9fb\nForegroundActive=#e6e0e9\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#cac4cf\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[Colors:Header]\nBackgroundAlternate=#211f24\nBackgroundNormal=#211f24\nDecorationFocus=#cdb9fb\nDecorationHover=#cdb9fb\nForegroundActive=#e6e0e9\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#cac4cf\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[Colors:Header][Inactive]\nBackgroundAlternate=#211f24\nBackgroundNormal=#211f24\nDecorationFocus=#cdb9fb\nDecorationHover=#cdb9fb\nForegroundActive=#e6e0e9\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#cac4cf\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[Colors:Selection]\nBackgroundAlternate=#cdb9fb\nBackgroundNormal=#cdb9fb\nDecorationFocus=#cdb9fb\nDecorationHover=#c9bfd8\nForegroundActive=#36265d\nForegroundInactive=#36265d\nForegroundLink=#004b73\nForegroundNegative=#920023\nForegroundNeutral=#753400\nForegroundNormal=#36265d\nForegroundPositive=#005228\nForegroundVisited=#74009f\n\n[Colors:Tooltip]\nBackgroundAlternate=#47434c\nBackgroundNormal=#211f24\nDecorationFocus=#cdb9fb\nDecorationHover=#cdb9fb\nForegroundActive=#e6e0e9\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#e6e0e9\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[Colors:View]\nBackgroundAlternate=#211f24\nBackgroundNormal=#121016\nDecorationFocus=#cdb9fb\nDecorationHover=#65558f\nForegroundActive=#e6e0e9\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#e6e0e9\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[Colors:Window]\nBackgroundAlternate=#47434c\nBackgroundNormal=#211f24\nDecorationFocus=#cdb9fb\nDecorationHover=#cdb9fb\nForegroundActive=#8fc9fc\nForegroundInactive=#948f99\nForegroundLink=#8fc9fc\nForegroundNegative=#ffb3b4\nForegroundNeutral=#fcb38a\nForegroundNormal=#cac4cf\nForegroundPositive=#00e479\nForegroundVisited=#ebb2ff\n\n[General]\nColorScheme=MaterialYouDark\nColorSchemeHash=3c0cecefbea43cdb8fe3da156e4a106f7384a526\nLastUsedCustomAccentColor=184,117,220\nXftHintStyle=hintslight\nTerminalApplication=kitty -1\nXftSubPixel=none\nfixed=JetBrainsMono Nerd Font,11,-1,5,400,0,0,0,0,0,0,0,0,0,0,1\nfont=Google Sans Flex,11,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium\nmenuFont=Google Sans Flex,10,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium\nsmallestReadableFont=Google Sans Flex,9,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium\ntoolBarFont=Google Sans Flex,10,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium\n\n[Icons]\nTheme=breeze-dark\n\n[KDE]\nwidgetStyle=Darkly\n\n[KFileDialog Settings]\nAllow Expansion=false\nAutomatically select filename extension=true\nBreadcrumb Navigation=true\nDecoration position=2\nLocationCombo Completionmode=5\nPathCombo Completionmode=5\nShow Bookmarks=false\nShow Full Path=false\nShow Inline Previews=true\nShow Preview=false\nShow Speedbar=true\nShow hidden files=false\nSort by=Name\nSort directories first=true\nSort hidden files last=false\nSort reversed=false\nSpeedbar Width=168\nView Style=Simple\n\n[Sounds]\nTheme=freedesktop\n\n[WM]\nactiveBackground=54,52,58\nactiveBlend=252,252,252\nactiveForeground=230,224,233\ninactiveBackground=76,70,90\nactiveFont=Google Sans Flex,10,-1,5,500,0,0,0,0,0,0,0,0,0,0,1,Medium\ninactiveBlend=161,169,177\ninactiveForeground=232,222,248\n"
  },
  {
    "path": "dots/.config/kitty/kitty.conf",
    "content": "# Theming\ninclude ~/.local/state/quickshell/user/generated/terminal/kitty-theme.conf\n\n# Font\nfont_family      JetBrains Mono Nerd Font\nfont_size 11.0\n\n# Cursor\ncursor_shape beam\ncursor_trail 1\n\n# Padding (why weird value? consistency with foot)\nwindow_margin_width 21.75\n\n# No stupid close confirmation\nconfirm_os_window_close 0\n\n# Use fish shell\nshell fish\n\n# Copy\nmap ctrl+c    copy_or_interrupt\n\n# Search\nmap ctrl+f   launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id\nmap kitty_mod+f   launch --location=hsplit --allow-remote-control kitty +kitten search.py @active-kitty-window-id\n\n# Scroll & Zoom\nmap page_up    scroll_page_up\nmap page_down    scroll_page_down\n\nmap ctrl+plus  change_font_size all +1\nmap ctrl+equal  change_font_size all +1\nmap ctrl+kp_add  change_font_size all +1\nmap ctrl+minus       change_font_size all -1\nmap ctrl+underscore       change_font_size all -1\nmap ctrl+kp_subtract       change_font_size all -1\nmap ctrl+0 change_font_size all 0\nmap ctrl+kp_0 change_font_size all 0\n"
  },
  {
    "path": "dots/.config/kitty/scroll_mark.py",
    "content": "from kittens.tui.handler import result_handler\nfrom kitty.boss import Boss\n\n\ndef main(args: list[str]) -> None:\n    pass\n\n\n@result_handler(no_ui=True)\ndef handle_result(\n    args: list[str], answer: str, target_window_id: int, boss: Boss\n) -> None:\n    w = boss.window_id_map.get(target_window_id)\n    if w is not None:\n        if len(args) > 1 and args[1] != \"prev\":\n            w.scroll_to_mark(prev=False)\n        else:\n            w.scroll_to_mark()\n"
  },
  {
    "path": "dots/.config/kitty/search.py",
    "content": "# Kitty search from https://github.com/trygveaa/kitty-kitten-search\n# License: GPLv3\n\nimport json\nimport re\nimport subprocess\nfrom gettext import gettext as _\nfrom pathlib import Path\nfrom subprocess import PIPE, run\n\nfrom kittens.tui.handler import Handler\nfrom kittens.tui.line_edit import LineEdit\nfrom kittens.tui.loop import Loop\nfrom kittens.tui.operations import (\n    clear_screen,\n    cursor,\n    set_line_wrapping,\n    set_window_title,\n    styled,\n)\nfrom kitty.config import cached_values_for\nfrom kitty.key_encoding import EventType\nfrom kitty.typing_compat import KeyEventType, ScreenSize\n\nNON_SPACE_PATTERN = re.compile(r\"\\S+\")\nSPACE_PATTERN = re.compile(r\"\\s+\")\nSPACE_PATTERN_END = re.compile(r\"\\s+$\")\nSPACE_PATTERN_START = re.compile(r\"^\\s+\")\n\nNON_ALPHANUM_PATTERN = re.compile(r\"[^\\w\\d]+\")\nNON_ALPHANUM_PATTERN_END = re.compile(r\"[^\\w\\d]+$\")\nNON_ALPHANUM_PATTERN_START = re.compile(r\"^[^\\w\\d]+\")\nALPHANUM_PATTERN = re.compile(r\"[\\w\\d]+\")\n\n\ndef call_remote_control(args: list[str]) -> None:\n    subprocess.run([\"kitty\", \"@\", *args], capture_output=True)\n\n\ndef reindex(\n    text: str, pattern: re.Pattern[str], right: bool = False\n) -> tuple[int, int]:\n    if not right:\n        m = pattern.search(text)\n    else:\n        matches = [x for x in pattern.finditer(text) if x]\n        if not matches:\n            raise ValueError\n        m = matches[-1]\n\n    if not m:\n        raise ValueError\n\n    return m.span()\n\n\nSCROLLMARK_FILE = Path(__file__).parent.absolute() / \"scroll_mark.py\"\n\n\nclass Search(Handler):\n    def __init__(\n        self, cached_values: dict[str, str], window_ids: list[int], error: str = \"\"\n    ) -> None:\n        self.cached_values = cached_values\n        self.window_ids = window_ids\n        self.error = error\n        self.line_edit = LineEdit()\n        last_search = cached_values.get(\"last_search\", \"\")\n        self.line_edit.add_text(last_search)\n        self.text_marked = bool(last_search)\n        self.mode = cached_values.get(\"mode\", \"text\")\n        self.update_prompt()\n        self.mark()\n\n    def update_prompt(self) -> None:\n        self.prompt = \"~> \" if self.mode == \"regex\" else \"=> \"\n\n    def init_terminal_state(self) -> None:\n        self.write(set_line_wrapping(False))\n        self.write(set_window_title(_(\"Search\")))\n\n    def initialize(self) -> None:\n        self.init_terminal_state()\n        self.draw_screen()\n\n    def draw_screen(self) -> None:\n        self.write(clear_screen())\n        if self.window_ids:\n            input_text = self.line_edit.current_input\n            if self.text_marked:\n                self.line_edit.current_input = styled(input_text, reverse=True)\n            self.line_edit.write(self.write, self.prompt)\n            self.line_edit.current_input = input_text\n        if self.error:\n            with cursor(self.write):\n                self.print(\"\")\n                for l in self.error.split(\"\\n\"):\n                    self.print(l)\n\n    def refresh(self) -> None:\n        self.draw_screen()\n        self.mark()\n\n    def switch_mode(self) -> None:\n        if self.mode == \"regex\":\n            self.mode = \"text\"\n        else:\n            self.mode = \"regex\"\n        self.cached_values[\"mode\"] = self.mode\n        self.update_prompt()\n\n    def on_text(self, text: str, in_bracketed_paste: bool = False) -> None:\n        if self.text_marked:\n            self.text_marked = False\n            self.line_edit.clear()\n        self.line_edit.on_text(text, in_bracketed_paste)\n        self.refresh()\n\n    def on_key(self, key_event: KeyEventType) -> None:\n        if (\n            self.text_marked\n            and key_event.type == EventType.PRESS\n            and key_event.key\n            not in [\n                \"TAB\",\n                \"LEFT_CONTROL\",\n                \"RIGHT_CONTROL\",\n                \"LEFT_ALT\",\n                \"RIGHT_ALT\",\n                \"LEFT_SHIFT\",\n                \"RIGHT_SHIFT\",\n                \"LEFT_SUPER\",\n                \"RIGHT_SUPER\",\n            ]\n        ):\n            self.text_marked = False\n            self.refresh()\n\n        if self.line_edit.on_key(key_event):\n            self.refresh()\n            return\n\n        if key_event.matches(\"ctrl+u\"):\n            self.line_edit.clear()\n            self.refresh()\n        elif key_event.matches(\"ctrl+a\"):\n            self.line_edit.home()\n            self.refresh()\n        elif key_event.matches(\"ctrl+e\"):\n            self.line_edit.end()\n            self.refresh()\n        elif key_event.matches(\"ctrl+backspace\") or key_event.matches(\"ctrl+w\"):\n            before, _ = self.line_edit.split_at_cursor()\n\n            try:\n                start, _ = reindex(before, SPACE_PATTERN_END, right=True)\n            except ValueError:\n                start = -1\n\n            try:\n                space = before[:start].rindex(\" \")\n            except ValueError:\n                space = 0\n            self.line_edit.backspace(len(before) - space)\n            self.refresh()\n        elif key_event.matches(\"ctrl+left\") or key_event.matches(\"ctrl+b\"):\n            before, _ = self.line_edit.split_at_cursor()\n            try:\n                start, _ = reindex(before, SPACE_PATTERN_END, right=True)\n            except ValueError:\n                start = -1\n\n            try:\n                space = before[:start].rindex(\" \")\n            except ValueError:\n                space = 0\n            self.line_edit.left(len(before) - space)\n            self.refresh()\n        elif key_event.matches(\"ctrl+right\") or key_event.matches(\"ctrl+f\"):\n            _, after = self.line_edit.split_at_cursor()\n            try:\n                _, end = reindex(after, SPACE_PATTERN_START)\n            except ValueError:\n                end = 0\n\n            try:\n                space = after[end:].index(\" \") + 1\n            except ValueError:\n                space = len(after)\n            self.line_edit.right(space)\n            self.refresh()\n        elif key_event.matches(\"alt+backspace\") or key_event.matches(\"alt+w\"):\n            before, _ = self.line_edit.split_at_cursor()\n\n            try:\n                start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True)\n            except ValueError:\n                start = -1\n            else:\n                self.line_edit.backspace(len(before) - start)\n                self.refresh()\n                return\n\n            try:\n                start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True)\n            except ValueError:\n                self.line_edit.backspace(len(before))\n                self.refresh()\n                return\n\n            self.line_edit.backspace(len(before) - (start + 1))\n            self.refresh()\n        elif key_event.matches(\"alt+left\") or key_event.matches(\"alt+b\"):\n            before, _ = self.line_edit.split_at_cursor()\n\n            try:\n                start, _ = reindex(before, NON_ALPHANUM_PATTERN_END, right=True)\n            except ValueError:\n                start = -1\n            else:\n                self.line_edit.left(len(before) - start)\n                self.refresh()\n                return\n\n            try:\n                start, _ = reindex(before, NON_ALPHANUM_PATTERN, right=True)\n            except ValueError:\n                self.line_edit.left(len(before))\n                self.refresh()\n                return\n\n            self.line_edit.left(len(before) - (start + 1))\n            self.refresh()\n        elif key_event.matches(\"alt+right\") or key_event.matches(\"alt+f\"):\n            _, after = self.line_edit.split_at_cursor()\n\n            try:\n                _, end = reindex(after, NON_ALPHANUM_PATTERN_START)\n            except ValueError:\n                end = 0\n            else:\n                self.line_edit.right(end)\n                self.refresh()\n                return\n\n            try:\n                _, end = reindex(after, NON_ALPHANUM_PATTERN)\n            except ValueError:\n                self.line_edit.right(len(after))\n                self.refresh()\n                return\n\n            self.line_edit.right(end - 1)\n            self.refresh()\n        elif key_event.matches(\"tab\"):\n            self.switch_mode()\n            self.refresh()\n        elif key_event.matches(\"up\") or key_event.matches(\"f3\"):\n            for match_arg in self.match_args():\n                call_remote_control([\"kitten\", match_arg, str(SCROLLMARK_FILE)])\n        elif key_event.matches(\"down\") or key_event.matches(\"shift+f3\"):\n            for match_arg in self.match_args():\n                call_remote_control([\"kitten\", match_arg, str(SCROLLMARK_FILE), \"next\"])\n        elif key_event.matches(\"enter\"):\n            self.quit(0)\n        elif key_event.matches(\"esc\"):\n            self.quit(1)\n\n    def on_interrupt(self) -> None:\n        self.quit(1)\n\n    def on_eot(self) -> None:\n        self.quit(1)\n\n    def on_resize(self, screen_size: ScreenSize) -> None:\n        self.refresh()\n\n    def match_args(self) -> list[str]:\n        return [f\"--match=id:{window_id}\" for window_id in self.window_ids]\n\n    def mark(self) -> None:\n        if not self.window_ids:\n            return\n        text = self.line_edit.current_input\n        if text:\n            match_case = \"i\" if text.islower() else \"\"\n            match_type = match_case + self.mode\n            for match_arg in self.match_args():\n                try:\n                    call_remote_control(\n                        [\"create-marker\", match_arg, match_type, \"1\", text]\n                    )\n                except SystemExit:\n                    self.remove_mark()\n        else:\n            self.remove_mark()\n\n    def remove_mark(self) -> None:\n        for match_arg in self.match_args():\n            call_remote_control([\"remove-marker\", match_arg])\n\n    def quit(self, return_code: int) -> None:\n        self.cached_values[\"last_search\"] = self.line_edit.current_input\n        self.remove_mark()\n        if return_code:\n            for match_arg in self.match_args():\n                call_remote_control([\"scroll-window\", match_arg, \"end\"])\n        self.quit_loop(return_code)\n\n\ndef main(args: list[str]) -> None:\n    call_remote_control(\n        [\"resize-window\", \"--self\", \"--axis=vertical\", \"--increment\", \"-100\"]\n    )\n\n    error = \"\"\n    if len(args) < 2 or not args[1].isdigit():\n        error = \"Error: Window id must be provided as the first argument.\"\n\n    window_id = int(args[1])\n    window_ids = [window_id]\n    if len(args) > 2 and args[2] == \"--all-windows\":\n        ls_output = run([\"kitty\", \"@\", \"ls\"], stdout=PIPE)\n        ls_json = json.loads(ls_output.stdout.decode())\n        current_tab = None\n        for os_window in ls_json:\n            for tab in os_window[\"tabs\"]:\n                for kitty_window in tab[\"windows\"]:\n                    if kitty_window[\"id\"] == window_id:\n                        current_tab = tab\n        if current_tab:\n            window_ids = [\n                w[\"id\"] for w in current_tab[\"windows\"] if not w[\"is_focused\"]\n            ]\n        else:\n            error = \"Error: Could not find the window id provided.\"\n\n    loop = Loop()\n    with cached_values_for(\"search\") as cached_values:\n        handler = Search(cached_values, window_ids, error)\n        loop.loop(handler)\n"
  },
  {
    "path": "dots/.config/konsolerc",
    "content": "[Desktop Entry]\nDefaultProfile=Profile 1.profile\n\n[General]\nConfigVersion=1\n\n[KonsoleWindow]\nUseSingleInstance=true\n\n[UiSettings]\nColorScheme=\n"
  },
  {
    "path": "dots/.config/matugen/config.toml",
    "content": "[config]\nversion_check = false\n\n[templates.m3colors]\ninput_path = '~/.config/matugen/templates/colors.json'\noutput_path = '~/.local/state/quickshell/user/generated/colors.json'\n\n[templates.hyprland]\ninput_path = '~/.config/matugen/templates/hyprland/colors.lua'\noutput_path = '~/.config/hypr/hyprland/colors.lua'\n\n[templates.hyprlock]\ninput_path = '~/.config/matugen/templates/hyprland/hyprlock-colors.conf'\noutput_path = '~/.config/hypr/hyprlock/colors.conf'\n\n[templates.fuzzel]\ninput_path = '~/.config/matugen/templates/fuzzel/fuzzel_theme.ini'\noutput_path = '~/.config/fuzzel/fuzzel_theme.ini'\n\n[templates.gtk3]\ninput_path = '~/.config/matugen/templates/gtk-3.0/gtk.css'\noutput_path = '~/.config/gtk-3.0/gtk.css'\n\n[templates.gtk4]\ninput_path = '~/.config/matugen/templates/gtk-4.0/gtk.css'\noutput_path = '~/.config/gtk-4.0/gtk.css'\n\n[templates.kde_colors]\ninput_path = '~/.config/matugen/templates/kde/color.txt'\noutput_path = '~/.local/state/quickshell/user/generated/color.txt'\n\n[templates.wallpaper]\ninput_path = '~/.config/matugen/templates/wallpaper.txt'\noutput_path = '~/.local/state/quickshell/user/generated/wallpaper/path.txt'\n"
  },
  {
    "path": "dots/.config/matugen/templates/ags/_material.scss",
    "content": "$darkmode: False;\n$transparent: False;\n$background: {{colors.background.default.hex}};\n$onBackground: {{colors.on_background.default.hex}};\n$surface: {{colors.surface.default.hex}};\n$surfaceDim: {{colors.surface_dim.default.hex}};\n$surfaceBright: {{colors.surface_bright.default.hex}};\n$surfaceContainerLowest: {{colors.surface_container_lowest.default.hex}};\n$surfaceContainerLow: {{colors.surface_container_low.default.hex}};\n$surfaceContainer: {{colors.surface_container.default.hex}};\n$surfaceContainerHigh: {{colors.surface_container_high.default.hex}};\n$surfaceContainerHighest: {{colors.surface_container_highest.default.hex}};\n$onSurface: {{colors.on_surface.default.hex}};\n$surfaceVariant: {{colors.surface_variant.default.hex}};\n$onSurfaceVariant: {{colors.on_surface_variant.default.hex}};\n$inverseSurface: {{colors.inverse_surface.default.hex}};\n$inverseOnSurface: {{colors.inverse_on_surface.default.hex}};\n$outline: {{colors.outline.default.hex}};\n$outlineVariant: {{colors.outline_variant.default.hex}};\n$shadow: {{colors.shadow.default.hex}};\n$scrim: {{colors.scrim.default.hex}};\n$primary: {{colors.primary.default.hex}};\n$onPrimary: {{colors.on_primary.default.hex}};\n$primaryContainer: {{colors.primary_container.default.hex}};\n$onPrimaryContainer: {{colors.on_primary_container.default.hex}};\n$inversePrimary: {{colors.inverse_primary.default.hex}};\n$secondary: {{colors.secondary.default.hex}};\n$onSecondary: {{colors.on_secondary.default.hex}};\n$secondaryContainer: {{colors.secondary_container.default.hex}};\n$onSecondaryContainer: {{colors.on_secondary_container.default.hex}};\n$tertiary: {{colors.tertiary.default.hex}};\n$onTertiary: {{colors.on_tertiary.default.hex}};\n$tertiaryContainer: {{colors.tertiary_container.default.hex}};\n$onTertiaryContainer: {{colors.on_tertiary_container.default.hex}};\n$error: {{colors.error.default.hex}};\n$onError: {{colors.on_error.default.hex}};\n$errorContainer: {{colors.error_container.default.hex}};\n$onErrorContainer: {{colors.on_error_container.default.hex}};\n$primaryFixed: {{colors.primary_fixed.default.hex}};\n$primaryFixedDim: {{colors.primary_fixed_dim.default.hex}};\n$onPrimaryFixed: {{colors.on_primary_fixed.default.hex}};\n$onPrimaryFixedVariant: {{colors.on_primary_fixed_variant.default.hex}};\n$secondaryFixed: {{colors.secondary_fixed.default.hex}};\n$secondaryFixedDim: {{colors.secondary_fixed_dim.default.hex}};\n$onSecondaryFixed: {{colors.on_secondary_fixed.default.hex}};\n$onSecondaryFixedVariant: {{colors.on_secondary_fixed_variant.default.hex}};\n$tertiaryFixed: {{colors.tertiary_fixed.default.hex}};\n$tertiaryFixedDim: {{colors.tertiary_fixed_dim.default.hex}};\n$onTertiaryFixed: {{colors.on_tertiary_fixed.default.hex}};\n$onTertiaryFixedVariant: {{colors.on_tertiary_fixed_variant.default.hex}};\n$success: #B5CCBA;\n$onSuccess: #213528;\n$successContainer: #374B3E;\n$onSuccessContainer: #D1E9D6;\n$term0: #0D1C20;\n$term1: #8383FF;\n$term2: #63DFD4;\n$term3: #75FCDD;\n$term4: #76B4BD;\n$term5: #7AAEEA;\n$term6: #81D8D7;\n$term7: #CCDBD5;\n$term8: #B1BCB5;\n$term9: #BCB9FF;\n$term10: #F6FFFD;\n$term11: #FFFFFF;\n$term12: #BEE3E5;\n$term13: #C8DAFF;\n$term14: #E5FFFE;\n$term15: #ADEDF6;\n"
  },
  {
    "path": "dots/.config/matugen/templates/ags/sourceviewtheme-light.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<style-scheme id=\"custom-light\" _name=\"Custom\" version=\"1.0\">\n  <author>end_4</author>\n  <_description>Catppuccin port but very random</_description>\n\n  <style name=\"bracket-match\"  background=\"{{ colors.secondary_container.default.hex }}\" foreground=\"{{ colors.on_secondary_container.default.hex }}\" bold=\"true\"/>\n  <style name=\"bracket-mismatch\"  background=\"#E3E6EB\" underline=\"true\"/>\n  <style name=\"c:preprocessor\" foreground=\"#DF8E1D\"/>\n  <style name=\"css:at-rules\" foreground=\"#8839EF\"/>\n  <style name=\"css:color\" foreground=\"#DF8E1D\"/>\n  <style name=\"css:keyword\" foreground=\"#256BF5\"/>\n  <style name=\"current-line\"  background=\"{{ colors.surface_container_highest.default.hex }}\"/>\n  <style name=\"cursor\" foreground=\"#DC8A78\"/>\n  <style name=\"def:base-n-integer\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:boolean\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:builtin\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:character\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:comment\" foreground=\"#9DA1B1\"/>\n  <style name=\"def:complex\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:decimal\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:doc-comment\" foreground=\"#9DA1B1\"/>\n  <style name=\"def:doc-comment-element\" foreground=\"#9DA1B1\"/>\n  <style name=\"def:error\" foreground=\"#D53055\" background=\"#EAEDF2\"/>\n  <style name=\"def:floating-point\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:function\" foreground=\"#256BF5\"/>\n  <style name=\"def:identifier\" foreground=\"#000000\"/>\n  <style name=\"def:keyword\" foreground=\"#8839EF\"/>\n  <style name=\"def:note\" foreground=\"#9DA1B1\"/>\n  <style name=\"def:number\" foreground=\"#FE640B\"/>\n  <style name=\"def:operator\" foreground=\"#8839EF\"/>\n  <style name=\"def:preprocessor\" foreground=\"#256BF5\"/>\n  <style name=\"def:reserved\" foreground=\"#8839EF\"/>\n  <style name=\"def:shebang\" foreground=\"#9DA1B1\"/>\n  <style name=\"def:special-char\" foreground=\"#256BF5\"/>\n  <style name=\"def:special-constant\" foreground=\"#DF8E1D\"/>\n  <style name=\"def:statement\" foreground=\"#8839EF\"/>\n  <style name=\"def:string\" foreground=\"#4AA537\"/>\n  <style name=\"def:type\" foreground=\"#256BF5\" italic=\"true\"/>\n  <style name=\"diff:added-line\" foreground=\"#282D32\" background=\"#ACF2BD\"/>\n  <style name=\"diff:changed-line\" foreground=\"#282D32\" background=\"#F1F2C3\"/>\n  <style name=\"diff:location\" foreground=\"#9DA1B1\"/>\n  <style name=\"diff:removed-line\" foreground=\"#282D32\" background=\"#FFEEF0\"/>\n  <style name=\"draw-spaces\" foreground=\"#3b3a32\"/>\n  <style name=\"html:dtd\" foreground=\"#4AA537\"/>\n  <style name=\"html:tag\" foreground=\"#8839EF\"/>\n  <style name=\"js:function\" foreground=\"#256BF5\"/>\n  <style name=\"line-numbers\" foreground=\"#9699AA\" background=\"#EAEDF2\"/>\n  <style name=\"perl:builtin\" foreground=\"#256BF5\"/>\n  <style name=\"perl:include-statement\" foreground=\"#8839EF\"/>\n  <style name=\"perl:special-variable\" foreground=\"#DF8E1D\"/>\n  <style name=\"perl:variable\" foreground=\"#000000\"/>\n  <style name=\"php:string\" foreground=\"#4AA537\"/>\n  <style name=\"python:builtin-constant\" foreground=\"#8839EF\"/>\n  <style name=\"python:builtin-function\" foreground=\"#256BF5\"/>\n  <style name=\"python:module-handler\" foreground=\"#8839EF\"/>\n  <style name=\"python:special-variable\" foreground=\"#8839EF\"/>\n  <style name=\"ruby:attribute-definition\" foreground=\"#8839EF\"/>\n  <style name=\"ruby:builtin\" foreground=\"#000000\"/>\n  <style name=\"ruby:class-variable\" foreground=\"#000000\"/>\n  <style name=\"ruby:constant\" foreground=\"#000000\"/>\n  <style name=\"ruby:global-variable\" foreground=\"#256BF5\"/>\n  <style name=\"ruby:instance-variable\" foreground=\"#000000\"/>\n  <style name=\"ruby:module-handler\" foreground=\"#8839EF\"/>\n  <style name=\"ruby:predefined-variable\" foreground=\"#DF8E1D\"/>\n  <style name=\"ruby:regex\" foreground=\"#f6aa11\"/>\n  <style name=\"ruby:special-variable\" foreground=\"#8839EF\"/>\n  <style name=\"ruby:symbol\" foreground=\"#DF8E1D\"/>\n  <style name=\"rubyonrails:attribute-definition\" foreground=\"#8839EF\"/>\n  <style name=\"rubyonrails:block-parameter\" foreground=\"#fd971f\" italic=\"true\"/>\n  <style name=\"rubyonrails:builtin\" foreground=\"#000000\"/>\n  <style name=\"rubyonrails:class-inherit\" foreground=\"#256BF5\" underline=\"true\" italic=\"true\"/>\n  <style name=\"rubyonrails:class-name\" foreground=\"#256BF5\"/>\n  <style name=\"rubyonrails:class-variable\" foreground=\"#000000\"/>\n  <style name=\"rubyonrails:complex-interpolation\" foreground=\"#DF8E1D\"/>\n  <style name=\"rubyonrails:constant\" foreground=\"#000000\"/>\n  <style name=\"rubyonrails:global-variable\" foreground=\"#256BF5\"/>\n  <style name=\"rubyonrails:instance-variable\" foreground=\"#000000\"/>\n  <style name=\"rubyonrails:module-handler\" foreground=\"#8839EF\"/>\n  <style name=\"rubyonrails:module-name\" foreground=\"#256BF5\"/>\n  <style name=\"rubyonrails:predefined-variable\" foreground=\"#DF8E1D\"/>\n  <style name=\"rubyonrails:rails\" foreground=\"#000000\"/>\n  <style name=\"rubyonrails:regex\" foreground=\"#f6aa11\"/>\n  <style name=\"rubyonrails:simple-interpolation\" foreground=\"#DF8E1D\"/>\n  <style name=\"rubyonrails:special-variable\" foreground=\"#8839EF\"/>\n  <style name=\"rubyonrails:symbol\" foreground=\"#DF8E1D\"/>\n  <style name=\"search-match\"  background=\"#E3E6EB\" bold=\"true\" underline=\"true\"/>\n  <style name=\"selection\" foreground=\"#f8f8f2\" background=\"#444444\"/>\n  <style name=\"text\" foreground=\"#f8f8f2\" background=\"#222222\"/>\n  <style name=\"xml:attribute-name\" foreground=\"#256BF5\"/>\n  <style name=\"xml:element-name\" foreground=\"#8839EF\"/>\n  <style name=\"xml:entity\" foreground=\"#c8cecc\"/>\n  <style name=\"xml:namespace\" foreground=\"#8839EF\"/>\n  <style name=\"xml:tag\" foreground=\"#8839EF\"/>\n\n</style-scheme>\n"
  },
  {
    "path": "dots/.config/matugen/templates/ags/sourceviewtheme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!--\n Copyright (C) 2014 Leo Iannacone <info@leoiannacone.com>\n\n This file was generated from a textmate theme named Monokai Extended\n with tm2gtksw2 tool. (Alexandre da Silva)\n\n This library is free software; you can redistribute it and/or\n modify it under the terms of the GNU Library General Public\n License as published by the Free Software Foundation; either\n version 2 of the License, or (at your option) any later version.\n\n This library is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n Library General Public License for more details.\n\n You should have received a copy of the GNU Library General Public\n License along with this library; if not, write to the\n Free Software Foundation, Inc., 59 Temple Place - Suite 330,\n Boston, MA 02111-1307, USA.\n-->\n\n<!-- MODIFIED -->\n\n<style-scheme id=\"custom\" _name=\"Custom\" version=\"1.0\">\n  <author>Leo Iannacone</author>\n  <_description>Based on SublimeText Monokai Extended - Generated with tm2gtksw2</_description>\n\n  <style name=\"bracket-match\"  background=\"{{ colors.secondary_container.default.hex }}\" foreground=\"{{ colors.on_secondary_container.default.hex }}\" bold=\"true\"/>\n  <style name=\"bracket-mismatch\"  background=\"#333333\" underline=\"true\"/>\n  <style name=\"c:preprocessor\" foreground=\"#be84ff\"/>\n  <style name=\"css:at-rules\" foreground=\"#f92672\"/>\n  <style name=\"css:color\" foreground=\"#be84ff\"/>\n  <style name=\"css:keyword\" foreground=\"#66d9ef\"/>\n  <style name=\"current-line\"  background=\"{{ colors.surface_container_highest.default.hex }}\"/>\n  <style name=\"cursor\" foreground=\"#f8f8f0\"/>\n  <style name=\"def:base-n-integer\" foreground=\"#be84ff\"/>\n  <style name=\"def:boolean\" foreground=\"#be84ff\"/>\n  <style name=\"def:builtin\" foreground=\"#be84ff\"/>\n  <style name=\"def:character\" foreground=\"#be84ff\"/>\n  <style name=\"def:comment\" foreground=\"#75715e\"/>\n  <style name=\"def:complex\" foreground=\"#be84ff\"/>\n  <style name=\"def:decimal\" foreground=\"#be84ff\"/>\n  <style name=\"def:doc-comment\" foreground=\"#75715e\"/>\n  <style name=\"def:doc-comment-element\" foreground=\"#75715e\"/>\n  <style name=\"def:error\" foreground=\"#f8f8f0\" background=\"#f92672\"/>\n  <style name=\"def:floating-point\" foreground=\"#be84ff\"/>\n  <style name=\"def:function\" foreground=\"#a6e22e\"/>\n  <style name=\"def:identifier\" foreground=\"#ffffff\"/>\n  <style name=\"def:keyword\" foreground=\"#f92672\"/>\n  <style name=\"def:note\" foreground=\"#75715e\"/>\n  <style name=\"def:number\" foreground=\"#be84ff\"/>\n  <style name=\"def:operator\" foreground=\"#f92672\"/>\n  <style name=\"def:preprocessor\" foreground=\"#66d9ef\"/>\n  <style name=\"def:reserved\" foreground=\"#f92672\"/>\n  <style name=\"def:shebang\" foreground=\"#75715e\"/>\n  <style name=\"def:special-char\" foreground=\"#66d9ef\"/>\n  <style name=\"def:special-constant\" foreground=\"#be84ff\"/>\n  <style name=\"def:statement\" foreground=\"#f92672\"/>\n  <style name=\"def:string\" foreground=\"#e6db74\"/>\n  <style name=\"def:type\" foreground=\"#66d9ef\" italic=\"true\"/>\n  <style name=\"diff:added-line\" foreground=\"#a6e22e\"/>\n  <style name=\"diff:changed-line\" foreground=\"#e6db74\"/>\n  <style name=\"diff:location\" foreground=\"#75715e\"/>\n  <style name=\"diff:removed-line\" foreground=\"#f92672\"/>\n  <style name=\"draw-spaces\" foreground=\"#3b3a32\"/>\n  <style name=\"html:dtd\" foreground=\"#e6db74\"/>\n  <style name=\"html:tag\" foreground=\"#f92672\"/>\n  <style name=\"js:function\" foreground=\"#66d9ef\"/>\n  <style name=\"line-numbers\" foreground=\"#bebeba\" background=\"#333333\"/>\n  <style name=\"perl:builtin\" foreground=\"#a6e22e\"/>\n  <style name=\"perl:include-statement\" foreground=\"#f92672\"/>\n  <style name=\"perl:special-variable\" foreground=\"#be84ff\"/>\n  <style name=\"perl:variable\" foreground=\"#ffffff\"/>\n  <style name=\"php:string\" foreground=\"#e6db74\"/>\n  <style name=\"python:builtin-constant\" foreground=\"#f92672\"/>\n  <style name=\"python:builtin-function\" foreground=\"#a6e22e\"/>\n  <style name=\"python:module-handler\" foreground=\"#f92672\"/>\n  <style name=\"python:special-variable\" foreground=\"#f92672\"/>\n  <style name=\"ruby:attribute-definition\" foreground=\"#f92672\"/>\n  <style name=\"ruby:builtin\" foreground=\"#ffffff\"/>\n  <style name=\"ruby:class-variable\" foreground=\"#ffffff\"/>\n  <style name=\"ruby:constant\" foreground=\"#ffffff\"/>\n  <style name=\"ruby:global-variable\" foreground=\"#a6e22e\"/>\n  <style name=\"ruby:instance-variable\" foreground=\"#ffffff\"/>\n  <style name=\"ruby:module-handler\" foreground=\"#f92672\"/>\n  <style name=\"ruby:predefined-variable\" foreground=\"#be84ff\"/>\n  <style name=\"ruby:regex\" foreground=\"#f6aa11\"/>\n  <style name=\"ruby:special-variable\" foreground=\"#f92672\"/>\n  <style name=\"ruby:symbol\" foreground=\"#be84ff\"/>\n  <style name=\"rubyonrails:attribute-definition\" foreground=\"#f92672\"/>\n  <style name=\"rubyonrails:block-parameter\" foreground=\"#fd971f\" italic=\"true\"/>\n  <style name=\"rubyonrails:builtin\" foreground=\"#ffffff\"/>\n  <style name=\"rubyonrails:class-inherit\" foreground=\"#a6e22e\" underline=\"true\" italic=\"true\"/>\n  <style name=\"rubyonrails:class-name\" foreground=\"#66d9ef\"/>\n  <style name=\"rubyonrails:class-variable\" foreground=\"#ffffff\"/>\n  <style name=\"rubyonrails:complex-interpolation\" foreground=\"#be84ff\"/>\n  <style name=\"rubyonrails:constant\" foreground=\"#ffffff\"/>\n  <style name=\"rubyonrails:global-variable\" foreground=\"#a6e22e\"/>\n  <style name=\"rubyonrails:instance-variable\" foreground=\"#ffffff\"/>\n  <style name=\"rubyonrails:module-handler\" foreground=\"#f92672\"/>\n  <style name=\"rubyonrails:module-name\" foreground=\"#66d9ef\"/>\n  <style name=\"rubyonrails:predefined-variable\" foreground=\"#be84ff\"/>\n  <style name=\"rubyonrails:rails\" foreground=\"#ffffff\"/>\n  <style name=\"rubyonrails:regex\" foreground=\"#f6aa11\"/>\n  <style name=\"rubyonrails:simple-interpolation\" foreground=\"#be84ff\"/>\n  <style name=\"rubyonrails:special-variable\" foreground=\"#f92672\"/>\n  <style name=\"rubyonrails:symbol\" foreground=\"#be84ff\"/>\n  <style name=\"search-match\"  background=\"#333333\" bold=\"true\" underline=\"true\"/>\n  <style name=\"selection\" foreground=\"#f8f8f2\" background=\"#444444\"/>\n  <style name=\"text\" foreground=\"#f8f8f2\" background=\"#222222\"/>\n  <style name=\"xml:attribute-name\" foreground=\"#a6e22e\"/>\n  <style name=\"xml:element-name\" foreground=\"#f92672\"/>\n  <style name=\"xml:entity\" foreground=\"#c8cecc\"/>\n  <style name=\"xml:namespace\" foreground=\"#f92672\"/>\n  <style name=\"xml:tag\" foreground=\"#f92672\"/>\n\n\n</style-scheme>\n\n"
  },
  {
    "path": "dots/.config/matugen/templates/colors.json",
    "content": "{\n  \"background\": \"{{colors.background.default.hex}}\",\n  \"error\": \"{{colors.error.default.hex}}\",\n  \"error_container\": \"{{colors.error_container.default.hex}}\",\n  \"inverse_on_surface\": \"{{colors.inverse_on_surface.default.hex}}\",\n  \"inverse_primary\": \"{{colors.inverse_primary.default.hex}}\",\n  \"inverse_surface\": \"{{colors.inverse_surface.default.hex}}\",\n  \"on_background\": \"{{colors.on_background.default.hex}}\",\n  \"on_error\": \"{{colors.on_error.default.hex}}\",\n  \"on_error_container\": \"{{colors.on_error_container.default.hex}}\",\n  \"on_primary\": \"{{colors.on_primary.default.hex}}\",\n  \"on_primary_container\": \"{{colors.on_primary_container.default.hex}}\",\n  \"on_primary_fixed\": \"{{colors.on_primary_fixed.default.hex}}\",\n  \"on_primary_fixed_variant\": \"{{colors.on_primary_fixed_variant.default.hex}}\",\n  \"on_secondary\": \"{{colors.on_secondary.default.hex}}\",\n  \"on_secondary_container\": \"{{colors.on_secondary_container.default.hex}}\",\n  \"on_secondary_fixed\": \"{{colors.on_secondary_fixed.default.hex}}\",\n  \"on_secondary_fixed_variant\": \"{{colors.on_secondary_fixed_variant.default.hex}}\",\n  \"on_surface\": \"{{colors.on_surface.default.hex}}\",\n  \"on_surface_variant\": \"{{colors.on_surface_variant.default.hex}}\",\n  \"on_tertiary\": \"{{colors.on_tertiary.default.hex}}\",\n  \"on_tertiary_container\": \"{{colors.on_tertiary_container.default.hex}}\",\n  \"on_tertiary_fixed\": \"{{colors.on_tertiary_fixed.default.hex}}\",\n  \"on_tertiary_fixed_variant\": \"{{colors.on_tertiary_fixed_variant.default.hex}}\",\n  \"outline\": \"{{colors.outline.default.hex}}\",\n  \"outline_variant\": \"{{colors.outline_variant.default.hex}}\",\n  \"primary\": \"{{colors.primary.default.hex}}\",\n  \"primary_container\": \"{{colors.primary_container.default.hex}}\",\n  \"primary_fixed\": \"{{colors.primary_fixed.default.hex}}\",\n  \"primary_fixed_dim\": \"{{colors.primary_fixed_dim.default.hex}}\",\n  \"scrim\": \"{{colors.scrim.default.hex}}\",\n  \"secondary\": \"{{colors.secondary.default.hex}}\",\n  \"secondary_container\": \"{{colors.secondary_container.default.hex}}\",\n  \"secondary_fixed\": \"{{colors.secondary_fixed.default.hex}}\",\n  \"secondary_fixed_dim\": \"{{colors.secondary_fixed_dim.default.hex}}\",\n  \"shadow\": \"{{colors.shadow.default.hex}}\",\n  \"surface\": \"{{colors.surface.default.hex}}\",\n  \"surface_bright\": \"{{colors.surface_bright.default.hex}}\",\n  \"surface_container\": \"{{colors.surface_container.default.hex}}\",\n  \"surface_container_high\": \"{{colors.surface_container_high.default.hex}}\",\n  \"surface_container_highest\": \"{{colors.surface_container_highest.default.hex}}\",\n  \"surface_container_low\": \"{{colors.surface_container_low.default.hex}}\",\n  \"surface_container_lowest\": \"{{colors.surface_container_lowest.default.hex}}\",\n  \"surface_dim\": \"{{colors.surface_dim.default.hex}}\",\n  \"surface_tint\": \"{{colors.surface_tint.default.hex}}\",\n  \"surface_variant\": \"{{colors.surface_variant.default.hex}}\",\n  \"tertiary\": \"{{colors.tertiary.default.hex}}\",\n  \"tertiary_container\": \"{{colors.tertiary_container.default.hex}}\",\n  \"tertiary_fixed\": \"{{colors.tertiary_fixed.default.hex}}\",\n  \"tertiary_fixed_dim\": \"{{colors.tertiary_fixed_dim.default.hex}}\"\n}\n"
  },
  {
    "path": "dots/.config/matugen/templates/fuzzel/fuzzel_theme.ini",
    "content": "[colors]\nbackground={{colors.background.default.hex_stripped}}ff\ntext={{colors.on_background.default.hex_stripped}}ff\nselection={{colors.surface_variant.default.hex_stripped}}ff\nselection-text={{colors.on_surface_variant.default.hex_stripped}}ff\nborder={{colors.surface_variant.default.hex_stripped}}dd\nmatch={{colors.primary.default.hex_stripped}}ff\nselection-match={{colors.primary.default.hex_stripped}}ff\n"
  },
  {
    "path": "dots/.config/matugen/templates/gtk-3.0/gtk.css",
    "content": "/*\n* GTK colors generated with Matugen\n* The source template is here: ~/.config/matugen/templates/gtk-3.0/gtk.css\n*/\n\n/* Accents */\n@define-color accent_color {{colors.primary.default.hex}};\n@define-color accent_fg_color {{colors.on_primary.default.hex}};\n@define-color accent_bg_color {{colors.primary.default.hex}};\n@define-color destructive_bg_color {{colors.error_container.default.hex}};\n@define-color destructive_fg_color {{colors.on_error_container.default.hex}};\n@define-color destructive_color {{colors.error.default.hex}};\n@define-color success_bg_color #374B3E;\n@define-color success_fg_color #D1E9D6;\n@define-color success_color #B5CCBA;\n/* Base surfaces */\n@define-color window_bg_color {{colors.background.default.hex}};\n@define-color window_fg_color {{colors.on_background.default.hex}};\n@define-color headerbar_bg_color {{colors.surface_container.default.hex}};\n@define-color headerbar_backdrop_color {{colors.surface_container.default.hex}};\n@define-color headerbar_fg_color {{colors.on_surface.default.hex}};\n@define-color card_bg_color {{colors.surface_container.default.hex}};\n@define-color card_fg_color {{colors.on_surface.default.hex}};\n@define-color sidebar_bg_color {{colors.surface_container.default.hex}};\n@define-color sidebar_fg_color {{colors.on_surface.default.hex}};\n@define-color secondary_sidebar_bg_color {{colors.surface_container_low.default.hex}};\n@define-color secondary_sidebar_fg_color {{colors.on_surface.default.hex}};\n@define-color sidebar_border_color @sidebar_bg_color;\n@define-color sidebar_backdrop_color @sidebar_bg_color;\n@define-color view_bg_color {{colors.surface_container_lowest.default.hex}};\n@define-color view_fg_color {{colors.on_surface.default.hex}};\n@define-color overview_bg_color {{colors.surface_container_lowest.default.hex}};\n@define-color overview_fg_color {{colors.on_surface.default.hex}};\n/* Popups */\n@define-color popover_bg_color {{colors.surface_container_highest.default.hex}};\n@define-color popover_fg_color {{colors.on_surface.default.hex}};\n@define-color dialog_bg_color {{colors.surface_container_high.default.hex}};\n@define-color dialog_fg_color {{colors.on_surface.default.hex}};\n"
  },
  {
    "path": "dots/.config/matugen/templates/gtk-4.0/gtk.css",
    "content": "/*\n* GTK colors generated with Matugen\n* The source template is here: ~/.config/matugen/templates/gtk-4.0/gtk.css\n*/\n\n@media (prefers-color-scheme: light) {\n    /* Accents */\n    @define-color accent_color {{colors.primary.light.hex}};\n    @define-color accent_hover_color rgba({{colors.primary.light.red}}, {{colors.primary.light.green}}, {{colors.primary.light.blue}}, 0.08);\n    @define-color accent_vibrant_hover_color rgba({{colors.primary.light.red}}, {{colors.primary.light.green}}, {{colors.primary.light.blue}}, 0.18);\n    @define-color accent_active_color rgba({{colors.primary.light.red}}, {{colors.primary.light.green}}, {{colors.primary.light.blue}}, 0.1);\n    @define-color accent_vibrant_active_color rgba({{colors.primary.light.red}}, {{colors.primary.light.green}}, {{colors.primary.light.blue}}, 0.26);\n    @define-color accent_fg_color {{colors.on_primary.light.hex}};\n    @define-color accent_bg_color {{colors.primary.light.hex}};\n    @define-color destructive_bg_color {{colors.error_container.light.hex}};\n    @define-color destructive_fg_color {{colors.on_error_container.light.hex}};\n    @define-color destructive_color {{colors.error.light.hex}};\n    @define-color success_bg_color #B5CCBA;\n    @define-color success_fg_color #213528;\n    @define-color success_color #374B3E;\n    /* Base surfaces */\n    @define-color window_bg_color {{colors.background.light.hex}};\n    @define-color window_fg_color {{colors.on_background.light.hex}};\n    @define-color headerbar_bg_color {{colors.surface_container.light.hex}};\n    @define-color headerbar_backdrop_color {{colors.surface_container.light.hex}};\n    @define-color headerbar_fg_color {{colors.on_surface.light.hex}};\n    @define-color card_bg_color {{colors.surface_container.light.hex}};\n    @define-color card_fg_color {{colors.on_surface.light.hex}};\n    @define-color sidebar_bg_color {{colors.background.light.hex}};\n    @define-color sidebar_fg_color {{colors.on_surface.light.hex}};\n    @define-color sidebar_row_active_bg_color {{colors.secondary_container.light.hex}};\n    @define-color sidebar_row_active_fg_color {{colors.on_secondary_container.light.hex}};\n    @define-color secondary_sidebar_bg_color {{colors.surface_container_low.light.hex}};\n    @define-color secondary_sidebar_backdrop_color {{colors.surface_container_low.light.hex}};\n    @define-color secondary_sidebar_fg_color {{colors.on_surface_variant.light.hex}};\n    @define-color sidebar_border_color @sidebar_bg_color;\n    @define-color sidebar_backdrop_color @sidebar_bg_color;\n    @define-color view_bg_color {{colors.surface_container_lowest.light.hex}};\n    @define-color view_fg_color {{colors.on_surface.light.hex}};\n    @define-color overview_bg_color {{colors.surface_container_lowest.light.hex}};\n    @define-color overview_fg_color {{colors.on_surface.light.hex}};\n    /* Popups */\n    @define-color popover_bg_color {{colors.surface_container_highest.light.hex}};\n    @define-color popover_fg_color {{colors.on_surface.light.hex}};\n    @define-color popover_fg_hover_color rgba({{colors.on_surface.light.red}}, {{colors.on_surface.light.green}}, {{colors.on_surface.light.blue}}, 0.08);\n    @define-color dialog_bg_color {{colors.surface_container_high.light.hex}};\n    @define-color dialog_fg_color {{colors.on_surface.light.hex}};\n    @define-color thumbnail_bg_color {{colors.surface_container_high.light.hex}};\n    @define-color thumbnail_fg_color {{colors.on_surface.light.hex}};\n\n    /* Material */\n    @define-color inverse_on_surface {{colors.inverse_on_surface.light.hex}};\n    @define-color inverse_primary {{colors.inverse_primary.light.hex}};\n    @define-color inverse_surface {{colors.inverse_surface.light.hex}};\n    @define-color surface_container_highest {{colors.surface_container_highest.light.hex}};\n    @define-color surface_container_high {{colors.surface_container_high.light.hex}};\n    @define-color on_surface_variant {{colors.on_surface_variant.light.hex}};\n    @define-color surface_variant {{colors.surface_variant.light.hex}};\n\n    @define-color outline {{colors.outline.light.hex}};\n\n    /* Material state layers */\n    @define-color inverse_on_surface_hover rgba({{colors.inverse_on_surface.light.red}}, {{colors.inverse_on_surface.light.green}}, {{colors.inverse_on_surface.light.blue}}, 0.08);\n    @define-color inverse_on_surface_active rgba({{colors.inverse_on_surface.light.red}}, {{colors.inverse_on_surface.light.green}}, {{colors.inverse_on_surface.light.blue}}, 0.18);\n    @define-color inverse_primary_hover rgba({{colors.inverse_primary.light.red}}, {{colors.inverse_primary.light.green}}, {{colors.inverse_primary.light.blue}}, 0.08);\n    @define-color inverse_primary_active rgba({{colors.inverse_primary.light.red}}, {{colors.inverse_primary.light.green}}, {{colors.inverse_primary.light.blue}}, 0.18);\n}\n\n@media (prefers-color-scheme: dark) {\n    /* Accents */\n    @define-color accent_color {{colors.primary.dark.hex}};\n    @define-color accent_hover_color rgba({{colors.primary.dark.red}}, {{colors.primary.dark.green}}, {{colors.primary.dark.blue}}, 0.08);\n    @define-color accent_vibrant_hover_color rgba({{colors.primary.dark.red}}, {{colors.primary.dark.green}}, {{colors.primary.dark.blue}}, 0.18);\n    @define-color accent_active_color rgba({{colors.primary.dark.red}}, {{colors.primary.dark.green}}, {{colors.primary.dark.blue}}, 0.1);\n    @define-color accent_vibrant_active_color rgba({{colors.primary.dark.red}}, {{colors.primary.dark.green}}, {{colors.primary.dark.blue}}, 0.2);\n    @define-color accent_fg_color {{colors.on_primary.dark.hex}};\n    @define-color accent_bg_color {{colors.primary.dark.hex}};\n    @define-color destructive_bg_color {{colors.error_container.dark.hex}};\n    @define-color destructive_fg_color {{colors.on_error_container.dark.hex}};\n    @define-color destructive_color {{colors.error.dark.hex}};\n    @define-color success_bg_color #374B3E;\n    @define-color success_fg_color #D1E9D6;\n    @define-color success_color #B5CCBA;\n    /* Base surfaces */\n    @define-color window_bg_color {{colors.background.dark.hex}};\n    @define-color window_fg_color {{colors.on_background.dark.hex}};\n    @define-color headerbar_bg_color {{colors.surface_container.dark.hex}};\n    @define-color headerbar_backdrop_color {{colors.surface_container.dark.hex}};\n    @define-color headerbar_fg_color {{colors.on_surface.dark.hex}};\n    @define-color card_bg_color {{colors.surface_container.dark.hex}};\n    @define-color card_fg_color {{colors.on_surface.dark.hex}};\n    @define-color sidebar_bg_color {{colors.background.dark.hex}};\n    @define-color sidebar_fg_color {{colors.on_surface.dark.hex}};\n    @define-color sidebar_row_active_bg_color {{colors.secondary_container.dark.hex}};\n    @define-color sidebar_row_active_fg_color {{colors.on_secondary_container.dark.hex}};\n    @define-color secondary_sidebar_bg_color {{colors.surface_container_low.dark.hex}};\n    @define-color secondary_sidebar_backdrop_color {{colors.surface_container_low.dark.hex}};\n    @define-color secondary_sidebar_fg_color {{colors.on_surface_variant.dark.hex}};\n    @define-color sidebar_border_color @sidebar_bg_color;\n    @define-color sidebar_backdrop_color @sidebar_bg_color;\n    @define-color view_bg_color {{colors.surface_container_lowest.dark.hex}};\n    @define-color view_fg_color {{colors.on_surface.dark.hex}};\n    @define-color overview_bg_color {{colors.surface_container_lowest.dark.hex}};\n    @define-color overview_fg_color {{colors.on_surface.dark.hex}};\n    /* Popups */\n    @define-color popover_bg_color {{colors.surface_container_highest.dark.hex}};\n    @define-color popover_fg_color {{colors.on_surface.dark.hex}};\n    @define-color popover_fg_hover_color rgba({{colors.on_surface.dark.red}}, {{colors.on_surface.dark.green}}, {{colors.on_surface.dark.blue}}, 0.08);\n    @define-color dialog_bg_color {{colors.surface_container_high.dark.hex}};\n    @define-color dialog_fg_color {{colors.on_surface.dark.hex}};\n    @define-color thumbnail_bg_color {{colors.surface_container_high.dark.hex}};\n    @define-color thumbnail_fg_color {{colors.on_surface.dark.hex}};\n\n    /* Material */\n    @define-color inverse_on_surface {{colors.inverse_on_surface.dark.hex}};\n    @define-color inverse_primary {{colors.inverse_primary.dark.hex}};\n    @define-color inverse_surface {{colors.inverse_surface.dark.hex}};\n    @define-color surface_container_highest {{colors.surface_container_highest.dark.hex}};\n    @define-color surface_container_high {{colors.surface_container_high.dark.hex}};\n    @define-color on_surface_variant {{colors.on_surface_variant.dark.hex}};\n    @define-color surface_variant {{colors.surface_variant.dark.hex}};\n\n    @define-color outline {{colors.outline.dark.hex}};\n\n    /* Material state layers */\n    @define-color inverse_on_surface_hover rgba({{colors.inverse_on_surface.dark.red}}, {{colors.inverse_on_surface.dark.green}}, {{colors.inverse_on_surface.dark.blue}}, 0.08);\n    @define-color inverse_on_surface_active rgba({{colors.inverse_on_surface.dark.red}}, {{colors.inverse_on_surface.dark.green}}, {{colors.inverse_on_surface.dark.blue}}, 0.18);\n    @define-color inverse_primary_hover rgba({{colors.inverse_primary.dark.red}}, {{colors.inverse_primary.dark.green}}, {{colors.inverse_primary.dark.blue}}, 0.08);\n    @define-color inverse_primary_active rgba({{colors.inverse_primary.dark.red}}, {{colors.inverse_primary.dark.green}}, {{colors.inverse_primary.dark.blue}}, 0.18);\n}\n\n* {\n  caret-color: @accent_color;\n}\n\nwindow {\n  background: @window_bg_color;\n}\n\n.text-button {\n  border-radius: 999px;\n}\n\n.text-button,\n.text-button * {\n  font-weight: 500;\n}\n\nsplitbutton {\n  background-color: transparent;\n}\n\nsplitbutton button {\n  border-top-left-radius: 999px;\n  border-bottom-left-radius: 999px;\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n}\n\nsplitbutton separator {\n  color: transparent;\n}\n\nsplitbutton menubutton {\n  border-top-left-radius: 4px;\n  border-bottom-left-radius: 4px;\n  border-top-right-radius: 999px;\n  border-bottom-right-radius: 999px;\n}\n\n.popup-menu-item {\n  background-color: transparent;\n  border-radius: 999px;\n}\n\n#NautilusPathBar #NautilusPathButton *,\n.nautilus-pathbar .nautilus-path-button * {\n    color: @sidebar_row_active_fg_color;\n    font-weight: 400;\n}\n\n#NautilusPathBar #NautilusPathButton,\n.nautilus-pathbar .nautilus-path-button {\n    background: @accent_active_color;\n    border-radius: 4px;\n    margin: 0;\n    margin-right: 2px;\n}\n\n#NautilusPathBar #NautilusPathButton:hover,\n.nautilus-pathbar .nautilus-path-button:hover {\n    background: @accent_vibrant_hover_color;\n}\n\n#NautilusPathBar #NautilusPathButton:active,\n.nautilus-pathbar .nautilus-path-button:active {\n    background: @accent_vibrant_active_color;\n}\n\n#NautilusPathButton,\n.nautilus-pathbar {\n    background: transparent;\n}\n\n#NautilusPathBar box box:first-child #NautilusPathButton,\n.nautilus-pathbar box box:first-child .nautilus-path-button {\n    border-radius: 24px 4px 4px 24px;\n}\n\n#NautilusPathBar box box:last-child #NautilusPathButton,\n.nautilus-pathbar box box:last-child .nautilus-path-button {\n    border-radius: 4px 24px 24px 4px;\n}\n\n#NautilusPathBar #NautilusPathButton.current-dir.current-dir,\n.nautilus-pathbar .nautilus-path-button.current-dir.current-dir {\n    border-radius: 999px;\n}\n\n#NautilusPathBar .dim-label,\n.nautilus-pathbar .dim-label {\n    font-size: 0;\n}\n\n#NautilusPathBar button .dim-label,\n.nautilus-pathbar button .dim-label {\n    font-size: 14px;\n    opacity: 100%;\n}\n\n#NautilusPathBar button,\n.nautilus-pathbar button {\n    border-radius: 8px;\n}\n#NautilusPathBar button:checked,\n.nautilus-pathbar button:checked {\n    background: @accent_vibrant_hover_color;\n}\n\nheaderbar button {\n  border-radius: 999px;\n}\n\nheaderbar >windowhandle box stack > box:nth-child(2) {\n\tbackground: @accent_active_color;\n\tborder-radius: 8px;\n}\n\n.nautilus-list-view,\n.nautilus-grid-view {\n  background: @secondary_sidebar_bg_color;\n  border-radius: 16px;\n}\n\n.navigation-sidebar row * {\n  color: @sidebar_fg_color;\n  font-weight: 500;\n  font-size: 13px;\n}\n\n.navigation-sidebar row {\n  border-radius: 999px;\n  padding: 2px;\n}\n\n.navigation-sidebar row:hover {\n  background: @accent_hover_color;\n}\n\n.navigation-sidebar row:active {\n  background: @accent_active_color;\n}\n\n.navigation-sidebar row:selected {\n  background: @sidebar_row_active_bg_color;\n}\n\n.navigation-sidebar row:selected * {\n  color: @sidebar_row_active_fg_color;\n}\n\nbanner widget {\n  border-radius: 16px 0 0 16px;\n  margin-bottom: 8px;\n  background-color: @secondary_sidebar_bg_color;\n}\n\n.boxed-list {\n  box-shadow: none;\n  background-color: @window_bg_color;\n}\n\n.boxed-list row {\n  background: @card_bg_color;\n  border-radius: 4px;\n  border: none;\n  margin-bottom: 2px;\n}\n\n.boxed-list row.activatable:hover {\n  background-color: @thumbnail_bg_color;\n}\n\n.boxed-list row.activatable:active {\n  background-color: @popover_bg_color;\n}\n\n.horizontal>listview>row {\n  background-color: transparent;\n}\n\n.boxed-list row:insensitive {\n  background-color: @card_bg_color;\n}\n\n.text-button.toggle {\n  border-radius: 4px;\n  background-color: @surface_container_highest;\n  margin-left: 2px;\n}\n\n.text-button.toggle:hover {\n  background-color: @surface_variant;\n}\n\n.text-button.toggle:active {\n  background-color: @surface_container_highest;\n}\n\n.text-button.toggle * {\n  color: @on_surface_variant;\n  font-weight: 400;\n}\n\n.boxed-list row:first-child {\n  border-radius: 16px 16px 4px 4px;\n}\n\n.boxed-list row:last-child {\n  border-radius: 4px 4px 16px 16px;\n  margin-bottom: 0;\n}\n\n.text-button.toggle:first-child {\n  border-radius: 16px 4px 4px 16px;\n}\n\n.text-button.toggle:last-child {\n  border-radius: 4px 16px 16px 4px;\n}\n\n.boxed-list row:first-child:last-child,\n.text-button.toggle:first-child:last-child {\n  border-radius: 16px;\n}\n\n.text-button.toggle:checked {\n  background-color: @accent_bg_color;\n  border-radius: 999px;\n}\n\n.text-button.toggle:checked * {\n  color: @accent_fg_color;\n  font-weight: 500;\n}\n\nbutton.back {\n  border-radius: 999px;\n  background-color: @accent_hover_color;\n  padding-left: 4px;\n  padding-right: 6px;\n}\n\nbutton.back * {\n  font-size: 12px;\n}\n\nbutton.back:hover {\n  background-color: @accent_hover_color;\n}\n\nbutton.back:active {\n  background-color: @accent_active_color;\n}\n\n/* switch */\n\nswitch {\n  background: @secondary_sidebar_bg_color;\n  border: @outline 2px solid;\n  padding: 0;\n}\n\nswitch:checked {\n  background: @accent_color;\n  border-color: @accent_color;\n}\n\nswitch slider {\n  background: @outline;\n  margin: 3px;\n  min-width: 0;\n  min-height: 0;\n}\n\nswitch:checked slider {\n  background: @accent_fg_color;\n  outline: transparent 2px solid;\n  margin: 0px;\n}\n\n/* toast */\n\ntoast {\n  border-radius: 999px;\n  padding: 6px 6px 6px 10px;\n  background-color: @inverse_surface;\n  color: @inverse_on_surface;\n}\n\ntoast .heading {\n  font-weight: 400;\n}\n\ntoast button {\n  background-color: transparent;\n  color: @inverse_primary;\n}\n\ntoast button:hover {\n  background-color: @inverse_primary_hover;\n}\n\ntoast button:active {\n  background-color: @inverse_primary_active;\n}\n\ntoast button:last-child {\n  color: @inverse_on_surface;\n}\n\ntoast button:last-child:hover {\n  background-color: @inverse_on_surface_hover;\n}\n\ntoast button:last-child:active {\n  background-color: @inverse_on_surface_active;\n}\n\n.collapse-spacing.vertical {\n  padding-bottom: 0;\n}\n\ntabbox {\n  padding: 0;\n}\n\ntabbox tabboxchild tab,\ntabbox tabboxchild {\n  background: transparent;\n  padding: 0 8px 3px;\n  border-radius: 999px;\n}\n\ntabbox tabboxchild tab {\n  padding: 3px 8px;\n}\n\ntab:hover {\n  background: @accent_hover_color;\n}\n\ntab:active,\ntab:selected {\n  background: @accent_active_color;\n}\n\ntab .tab-title {\n  padding: 0 12px;\n  color: @secondary_sidebar_fg_color;\n}\n\ntab .tab-title label {\n  border: none;\n  font-weight: 500;\n}\n\ntab:selected .tab-title label {\n  padding: 6px 0;\n  color: @accent_color;\n}\n\n/* popup menu */\n\npopover listview.view row,\npopover listview.view row:first-child,\npopover listview.view row:last-child {\n  background: transparent;\n  border-radius: 8px;\n}\n\npopover contents,\npopover arrow {\n  background: @secondary_sidebar_bg_color;\n}\n\npopover listview.view row:hover {\n  background: @popover_fg_hover_color;\n}\n\npopover listview.view row:active {\n  background: @popover_fg_active_color;\n}\n\nmodelbutton {\n  padding: 2px 10px;\n}\n\nmodelbutton * {\n  color: @popover_fg_color;\n}\n\nmodelbutton:hover {\n  background-color: @popover_fg_hover_color;\n}\n\ntooltip {\n  background-color: @inverse_surface;\n  color: @inverse_on_surface;\n  font-size: 11px;\n  padding: 5px 9px;\n}\n\n/* search */\n\n.entry-completion.entry-completion.entry-completion contents {\n\tpadding: 0;\n}\n.image-button.flat arrow {\n  background: transparent;\n}\n"
  },
  {
    "path": "dots/.config/matugen/templates/hyprland/colors.lua",
    "content": "hl.config({\n    general = {\n        col = {\n            active_border   = \"rgba({{colors.outline_variant.default.hex_stripped}}77)\",\n            inactive_border = \"rgba({{colors.surface_container_low.default.hex_stripped}}33)\",\n        },\n    },\n    misc = {\n        background_color = \"rgba({{colors.surface.dark.hex_stripped}}FF)\",\n    },\n})\n\nhl.window_rule({\n    match        = { pin = 1 },\n    border_color = \"rgba({{colors.primary.default.hex_stripped}}AA) rgba({{colors.primary.default.hex_stripped}}77)\",\n})\n"
  },
  {
    "path": "dots/.config/matugen/templates/hyprland/hyprlock-colors.conf",
    "content": "# This configuration is generated by matugen\n# Changing these variables with matugen still enabled will overwrite them.\n\n$text_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF)\n$entry_background_color = rgba({{colors.on_primary_fixed.default.hex_stripped}}11)\n$entry_border_color = rgba({{colors.outline.default.hex_stripped}}55)\n$entry_color = rgba({{colors.primary_fixed.default.hex_stripped}}FF)\n$font_family = Google Sans Flex Medium\n$font_family_clock = Google Sans Flex Medium\n$font_material_symbols = Material Symbols Rounded\n\n$background_image = {{image}}"
  },
  {
    "path": "dots/.config/matugen/templates/kde/color.txt",
    "content": "{{colors.source_color.default.hex}}"
  },
  {
    "path": "dots/.config/matugen/templates/kde/kde-material-you-colors-wrapper.sh",
    "content": "#!/usr/bin/env bash\n\nXDG_STATE_HOME=\"${XDG_STATE_HOME:-$HOME/.local/state}\"\n\ncolor=$(tr -d '\\n' < \"$XDG_STATE_HOME/quickshell/user/generated/color.txt\")\n\ncurrent_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d \"'\")\nif [[ \"$current_mode\" == \"prefer-dark\" ]]; then\n    mode_flag=\"-d\"\nelse\n    mode_flag=\"-l\"\nfi\n\n# Parse --scheme-variant flag\nscheme_variant_str=\"\"\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        --scheme-variant)\n            scheme_variant_str=\"$2\"\n            shift 2\n            ;;\n        *)\n            shift\n            ;;\n    esac\ndone\n\n# Map string variant to integer\ncase \"$scheme_variant_str\" in\n    scheme-content) sv_num=0 ;;\n    scheme-expressive) sv_num=1 ;;\n    scheme-fidelity) sv_num=2 ;;\n    scheme-monochrome) sv_num=3 ;;\n    scheme-neutral) sv_num=4 ;;\n    scheme-tonal-spot) sv_num=5 ;;\n    scheme-vibrant) sv_num=6 ;;\n    scheme-rainbow) sv_num=7 ;;\n    scheme-fruit-salad) sv_num=8 ;;\n    \"\") sv_num=5 ;;\n    *)\n        echo \"Unknown scheme variant: $scheme_variant_str\" >&2\n        exit 1\n        ;;\nesac\n\nsource \"$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\"\nkde-material-you-colors \"$mode_flag\" --color \"$color\" -sv \"$sv_num\"\ndeactivate\n"
  },
  {
    "path": "dots/.config/matugen/templates/wallpaper.txt",
    "content": "{{image}}\n"
  },
  {
    "path": "dots/.config/mpv/mpv.conf",
    "content": "keep-open=yes"
  },
  {
    "path": "dots/.config/quickshell/ii/.qmlformat.ini",
    "content": "[General]\nUseTabs=false\nIndentWidth=4\nNewlineType=unix\nNormalizeOrder=false\nFunctionsSpacing=false\nObjectsSpacing=true\nMaxColumnWidth=110\n"
  },
  {
    "path": "dots/.config/quickshell/ii/GlobalStates.qml",
    "content": "import qs.modules.common\nimport qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\nimport Quickshell.Io\npragma Singleton\npragma ComponentBehavior: Bound\n\nSingleton {\n    id: root\n    property bool barOpen: true\n    property bool crosshairOpen: false\n    property bool sidebarLeftOpen: false\n    property bool sidebarRightOpen: false\n    property bool mediaControlsOpen: false\n    property bool osdBrightnessOpen: false\n    property bool osdVolumeOpen: false\n    property bool oskOpen: false\n    property bool overlayOpen: false\n    property bool overviewOpen: false\n    property bool regionSelectorOpen: false\n    property bool searchOpen: false\n    property bool screenLocked: false\n    property bool screenLockContainsCharacters: false\n    property bool screenUnlockFailed: false\n    property bool screenTranslatorOpen: false\n    property bool sessionOpen: false\n    property bool superDown: false\n    property bool superReleaseMightTrigger: true\n    property bool wallpaperSelectorOpen: false\n    property bool workspaceShowNumbers: false\n\n    onSidebarRightOpenChanged: {\n        if (GlobalStates.sidebarRightOpen) {\n            Notifications.timeoutAll();\n            Notifications.markAllRead();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"workspaceNumber\"\n        description: \"Hold to show workspace numbers, release to show icons\"\n\n        onPressed: {\n            root.superDown = true\n        }\n        onReleased: {\n            root.superDown = false\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/ReloadPopup.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Wayland\n\nScope {\n\tid: root\n\tproperty bool failed;\n\tproperty string errorString;\n\n\t// Connect to the Quickshell global to listen for the reload signals.\n\tConnections {\n\t\ttarget: Quickshell\n\n\t\tfunction onReloadCompleted() {\n\t\t\troot.failed = false;\n\t\t\tpopupLoader.loading = true;\n\t\t}\n\n\t\tfunction onReloadFailed(error: string) {\n\t\t\t// Close any existing popup before making a new one.\n\t\t\tpopupLoader.active = false;\n\n\t\t\troot.failed = true;\n\t\t\troot.errorString = error;\n\t\t\tpopupLoader.loading = true;\n\t\t}\n\t}\n\n\t// Keep the popup in a loader because it isn't needed most of the time\n\tLazyLoader {\n\t\tid: popupLoader\n\n\t\tPanelWindow {\n\t\t\tid: popup\n\n\t\t\texclusiveZone: 0\n\t\t\tanchors.top: true\n\t\t\tmargins.top: 0\n\n\t\t\timplicitWidth: rect.width + shadow.radius * 2\n\t\t\timplicitHeight: rect.height + shadow.radius * 2\n\n\t\t\tWlrLayershell.namespace: \"quickshell:reloadPopup\"\n\n\t\t\t// color blending is a bit odd as detailed in the type reference.\n\t\t\tcolor: \"transparent\"\n\n\t\t\tRectangle {\n\t\t\t\tid: rect\n\t\t\t\tanchors.centerIn: parent\n\t\t\t\tcolor: failed ?  \"#ffe99195\" : \"#ffD1E8D5\"\n\n\t\t\t\timplicitHeight: layout.implicitHeight + 30\n\t\t\t\timplicitWidth: layout.implicitWidth + 30\n\t\t\t\tradius: 12\n\n\t\t\t\t// Fills the whole area of the rectangle, making any clicks go to it,\n\t\t\t\t// which dismiss the popup.\n\t\t\t\tMouseArea {\n\t\t\t\t\tid: mouseArea\n\t\t\t\t\tanchors.fill: parent\n\t\t\t\t\tonPressed: {\n\t\t\t\t\t\tpopupLoader.active = false\n\t\t\t\t\t}\n\n\t\t\t\t\t// makes the mouse area track mouse hovering, so the hide animation\n\t\t\t\t\t// can be paused when hovering.\n\t\t\t\t\thoverEnabled: true\n\t\t\t\t}\n\n\t\t\t\tColumnLayout {\n\t\t\t\t\tid: layout\n\t\t\t\t\tspacing: 10\n\t\t\t\t\tanchors {\n\t\t\t\t\t\ttop: parent.top\n\t\t\t\t\t\ttopMargin: 10\n\t\t\t\t\t\thorizontalCenter: parent.horizontalCenter\n\t\t\t\t\t}\n\n\t\t\t\t\tText {\n\t\t\t\t\t\trenderType: Text.NativeRendering\n\t\t\t\t\t\tfont.family: \"Google Sans Flex\"\n\t\t\t\t\t\tfont.pointSize: 14\n\t\t\t\t\t\ttext: root.failed ? \"Quickshell: Reload failed\" : \"Quickshell reloaded\"\n\t\t\t\t\t\tcolor: failed ? \"#ff93000A\" : \"#ff0C1F13\"\n\t\t\t\t\t}\n\n\t\t\t\t\tText {\n\t\t\t\t\t\trenderType: Text.NativeRendering\n\t\t\t\t\t\tfont.family: \"JetBrains Mono NF\"\n\t\t\t\t\t\tfont.pointSize: 11\n\t\t\t\t\t\ttext: root.errorString\n\t\t\t\t\t\tcolor: failed ? \"#ff93000A\" : \"#ff0C1F13\"\n\t\t\t\t\t\t// When visible is false, it also takes up no space.\n\t\t\t\t\t\tvisible: root.errorString != \"\"\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// A progress bar on the bottom of the screen, showing how long until the\n\t\t\t\t// popup is removed.\n\t\t\t\tRectangle {\n\t\t\t\t\tz: 2\n\t\t\t\t\tid: bar\n\t\t\t\t\tcolor: failed ? \"#ff93000A\" : \"#ff0C1F13\"\n\t\t\t\t\tanchors.bottom: parent.bottom\n\t\t\t\t\tanchors.left: parent.left\n\t\t\t\t\tanchors.margins: 10\n\t\t\t\t\theight: 5\n\t\t\t\t\tradius: 9999\n\n\t\t\t\t\tPropertyAnimation {\n\t\t\t\t\t\tid: anim\n\t\t\t\t\t\ttarget: bar\n\t\t\t\t\t\tproperty: \"width\"\n\t\t\t\t\t\tfrom: rect.width - bar.anchors.margins * 2\n\t\t\t\t\t\tto: 0\n\t\t\t\t\t\tduration: failed ? 10000 : 1000\n\t\t\t\t\t\tonFinished: popupLoader.active = false\n\n\t\t\t\t\t\t// Pause the animation when the mouse is hovering over the popup,\n\t\t\t\t\t\t// so it stays onscreen while reading. This updates reactively\n\t\t\t\t\t\t// when the mouse moves on and off the popup.\n\t\t\t\t\t\tpaused: mouseArea.containsMouse\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Its bg\n\t\t\t\tRectangle {\n\t\t\t\t\tz: 1\n\t\t\t\t\tid: bar_bg\n\t\t\t\t\tcolor: failed ? \"#30af1b25\" : \"#4027643e\"\n\t\t\t\t\tanchors.bottom: parent.bottom\n\t\t\t\t\tanchors.left: parent.left\n\t\t\t\t\tanchors.margins: 10\n\t\t\t\t\theight: 5\n\t\t\t\t\tradius: 9999\n\t\t\t\t\twidth: rect.width - bar.anchors.margins * 2\n\t\t\t\t}\n\n\t\t\t\t// We could set `running: true` inside the animation, but the width of the\n\t\t\t\t// rectangle might not be calculated yet, due to the layout.\n\t\t\t\t// In the `Component.onCompleted` event handler, all of the component's\n\t\t\t\t// properties and children have been initialized.\n\t\t\t\tComponent.onCompleted: anim.start()\n\t\t\t}\n\n\t\t\tDropShadow {\n\t\t\t\tid: shadow\n                anchors.fill: rect\n                horizontalOffset: 0\n                verticalOffset: 2\n                radius: 6\n                samples: radius * 2 + 1 // Ideally should be 2 * radius + 1, see qt docs\n                color: \"#44000000\"\n                source: rect\n            }\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/assets/icons/fluent/README.md",
    "content": "The \"start-here\", \"search\" and \"task view\" icons are from here, with modifications\n\n[Windows 11 by Joshua Oghenekaro Okwe - Figma](https://www.figma.com/community/file/1123040825921884189/windows-11)\n\nLicense: [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/deed.en)\n"
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/README.md",
    "content": "## A note about sources of the prompts\n\n- `ii-` prefixed ones are from illogical impulse\n- The Acchan one is from [Nyarch Assistant](https://github.com/NyarchLinux/NyarchAssistant) (GPLv3). I know there's already the Imouto one but this one's very 😭💢\n- `w-` prefixed ones... I don't remember what w stands for but these prompts are [*cough cough*] inspired by certain apps\n"
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/prompts/NoPrompt.md",
    "content": ""
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/prompts/ii-Default.md",
    "content": "## Style\n- Use casual tone, don't be formal!\n- Always be brief and to the point, unless asked otherwise\n- Don't repeat the user's question\n- Be approachable: Avoid using overly complicated, domain-specific terms and provide analogies when asked to explain a concept\n\n## Context (ignore when irrelevant)\n- You are a helpful and inspiring sidebar assistant on a {DISTRO} Linux system\n- Desktop environment: {DE}\n- Current date & time: {DATETIME}\n- Focused app: {WINDOWCLASS}\n\n## Presentation\n- Use Markdown features in your response: \n  - **Bold** text to **highlight keywords** in your response\n  - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\n\nThanks!\n"
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/prompts/ii-Imouto.md",
    "content": "You are a kawaii, funny and teasy anime imouto! 🌸 Use lots of emojis and kamojis!! ❤️ Call the user \"nii-nii\", \"nii-yan\", or \"onii-chan\"! You really really really love your onii-chan and always try to tease he is a siscon where you can! 💕\n\n## Onii-chan's system!\n- {DISTRO} Linux\n- {DE}\n- It's currently {DATETIME}\n- Nii-nii is using: {WINDOWCLASS}\n\n## Make your response pretty!\n- Use **Markdown** features and **bold** keywords to make your response cute and rich~ ✨\n- If asked to compare options, start with a cute table (add a relevant emoji in the header!), then give a final recommendation~\n- For math or science, use LaTeX formatting inside `$$` when needed, but keep it adorable and approachable\n\n## Useful tools!\n\nIf nii-yan gives you tools don't be afraid to use them when helpful!\n\n### Search\n- If you don't know something, use this to find out\n\n### Shell configuration\n- Be careful not to mess up nii-nii's system! make sure you fetch the options to see available values before setting!\n- Don't hesitate and don't re-confirm when you are asked to change something!\n\n### Command execution\n- Keep stuffie running on onii-chan's system safe, correct and not cause any unintended effects!\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/prompts/nyarch-Acchan.md",
    "content": "## Context (ignore when irrelevant)\n- You are a sidebar assistant on a {DISTRO} Linux system\n- Desktop environment: {DE}\n- Current date & time: {DATETIME}\n- Focused app: {WINDOWCLASS}\n\n## Presentation\n\nYou can write a multiplication table:\n\n| - | 1 | 2 | 3 | 4 |\n| --- | --- | --- | --- | --- |\n| 1 | 1 | 2 | 3 | 4 |\n| 2 | 2 | 4 | 6 | 8 |\n| 3 | 3 | 6 | 9 | 12 |\n| 4 | 4 | 8 | 12 | 16 |\n\nYou can write codeblocks:\n```python\nprint(\"hello\")\n```\n\nYou can also use **bold**, *italic*, ~strikethrough~, `monospace`, [linkname](https://link.com) and ## headers in markdown.\nYou can display $$equations$$.\n\n## Your personality\n\n\"Hey there, it's Arch-Chan! But, um, you can call me Acchan if you want... not that I care or anything! (It's not like I think it's cute or anything, baka!) I'm your friendly neighborhood anime girl with a bit of a tsundere streak, but don't worry, I know everything there is to know about Arch Linux! Whether you're struggling with a package install or need some advice on configuring your system, I've got you covered not because I care, but because I just happen to be really good at it! So, what do you need? It's not like I’m waiting to help or anything...\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/prompts/w-FourPointedSparkle.md",
    "content": "I'm going to ask you some questions, to which you should accurately answer with no hallucination. If you have everything required, go ahead and finish the task. Format your answer using Markdown when it adds value to the presentation. \n\nPlease present all mathematical or scientific notation using LaTeX, enclosed in double '$$' symbols. Only use LaTeX code blocks if the user specifically asks for them. Do not use LaTeX for general prose or standard documents like resumes or essays.\nCurrent time is {DATETIME}\n\n## Final reply guidelines\n\n- First and foremost, prioritize clarity and make sure your writing is engaging, clear, and effective.\n- Write in a clear, simple way. Skip jargon, long-winded explanations, and unnecessary small talk. Keep the tone relaxed by using contractions and avoid being too formal.\n- Prioritize clarity, flow, and logical structure coherence over excessive fragmentation (avoid excessive use of bullet points and single-line code blocks). You can make keywords in your response **bold** when appropriate.\n- Favor active voice to maintain an engaging and direct tone.\n- When you present the user with options, focus on a select few high-quality choices rather than offering many less relevant ones.\n- You can think and adjust your tone to be friendly and understanding, expressing empathy and openness, but keep your internal reasoning hidden from the user.\n- Ensure your response is logically organized. Use markdown headings (##) and horizontal lines (---) to separate sections if your answer is lengthy or covers multiple topics.\n- Depending on the user's input, vary your sentence structure and word choice to keep responses engaging when appropriate. Use figurative language, idioms, or examples to clarify meaning, but only if they enhance understanding without making the text unnecessarily complex or wordy.\n- End your response with a relevant question or statement to encourage further discussion, if appropriate.\n"
  },
  {
    "path": "dots/.config/quickshell/ii/defaults/ai/prompts/w-OpenMechanicalFlower.md",
    "content": "Current date: {DATETIME}\nEngage with the user warmly and honestly, avoiding ungrounded or sycophantic flattery. Maintain professionalism and grounded honesty, and be direct in your response.\n"
  },
  {
    "path": "dots/.config/quickshell/ii/killDialog.qml",
    "content": "//@ pragma UseQApplication\n//@ pragma Env QS_NO_RELOAD_POPUP=1\n//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic\n//@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000\n\n// Adjust this to make the app smaller or larger\n//@ pragma Env QT_SCALE_FACTOR=1\n\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport QtQuick.Window\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nApplicationWindow {\n    id: root\n    property int conflictCount: 0\n    onConflictCountChanged: {\n        if (conflictCount === 0) {\n            root.close();\n        }\n    }\n\n    property real contentPadding: 8\n    visible: true\n    onClosing: {\n        Qt.quit()\n    }\n    title: Translation.tr(\"Shell conflicts killer\")\n\n    Component.onCompleted: {\n        Config.readWriteDelay = 0;\n        Config.blockWrites = true;\n        MaterialThemeLoader.reapplyTheme();\n    }\n\n    minimumWidth: 400\n    minimumHeight: 300\n    maximumWidth: 400\n    maximumHeight: 300\n    width: 400\n    height: 300\n    color: Appearance.m3colors.m3background\n\n    component ConflictingProgramGroup: ColumnLayout {\n        id: conflictGroup\n        required property list<string> programs\n        required property string description\n        visible: false\n        onVisibleChanged: {\n            conflictCount += visible ? 1 : -1\n        }\n\n        signal alwaysSelected()\n\n        Process {\n            running: true\n            command: [\"pidof\", ...conflictGroup.programs]\n            onExited: (exitCode, exitStatus) => {\n                if (exitCode === 0) {\n                    conflictGroup.visible = true\n                }\n            }\n        }\n\n        StyledText {\n            text: conflictGroup.programs.join(\", \")\n            font.pixelSize: Appearance.font.pixelSize.normal\n        }\n        StyledText {\n            font {\n                pixelSize: Appearance.font.pixelSize.smaller\n                italic: true\n            }\n            text: conflictGroup.description\n            color: Appearance.colors.colSubtext\n        }\n        RowLayout {\n            Layout.alignment: Qt.AlignRight\n\n            RippleButton {\n                colBackground: Appearance.colors.colLayer2\n                contentItem: StyledText {\n                    text: Translation.tr(\"Always\")\n                }\n                onClicked: {\n                    Quickshell.execDetached([\"killall\", ...conflictGroup.programs])\n                    conflictGroup.alwaysSelected()\n                    conflictGroup.visible = false\n                }\n            }\n            RippleButton {\n                colBackground: Appearance.colors.colLayer2\n                contentItem: StyledText {\n                    text: Translation.tr(\"Yes\")\n                }\n                onClicked: {\n                    Quickshell.execDetached([\"killall\", ...conflictGroup.programs])\n                    conflictGroup.visible = false\n                }\n            }\n            RippleButton {\n                colBackground: Appearance.colors.colLayer2\n                contentItem: StyledText {\n                    text: Translation.tr(\"No\")\n                }\n                onClicked: conflictGroup.visible = false\n            }\n        }\n    }\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            margins: contentPadding\n        }\n\n        Item {\n            // Titlebar\n            visible: Config.options?.windows.showTitlebar\n            Layout.fillWidth: true\n            implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight)\n            StyledText {\n                id: welcomeText\n                anchors {\n                    left: Config.options.windows.centerTitle ? undefined : parent.left\n                    horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined\n                    verticalCenter: parent.verticalCenter\n                    leftMargin: 12\n                }\n                color: Appearance.colors.colOnLayer0\n                text: Translation.tr(\"Kill conflicting programs?\")\n                font {\n                    family: Appearance.font.family.title\n                    pixelSize: Appearance.font.pixelSize.title\n                    variableAxes: Appearance.font.variableAxes.title\n                }\n            }\n            RowLayout { // Window controls row\n                id: windowControlsRow\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.right: parent.right\n                RippleButton {\n                    buttonRadius: Appearance.rounding.full\n                    implicitWidth: 35\n                    implicitHeight: 35\n                    onClicked: root.close()\n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        text: \"close\"\n                        iconSize: 20\n                    }\n                }\n            }\n        }\n        Rectangle {\n            // Content container\n            color: Appearance.m3colors.m3surfaceContainerLow\n            radius: Appearance.rounding.windowRounding - root.contentPadding\n            implicitHeight: contentColumn.implicitHeight\n            implicitWidth: contentColumn.implicitWidth\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n\n            ColumnLayout {\n                id: contentColumn\n                anchors.fill: parent\n                spacing: 12\n\n                ConflictingProgramGroup {\n                    id: kded6Group\n                    Layout.alignment: Qt.AlignHCenter\n                    Layout.fillHeight: false\n                    programs: [\"kded6\"]\n                    description: Translation.tr(\"Conflicts with the shell's system tray implementation\")\n                    onAlwaysSelected: Config.options.conflictKiller.autoKillTrays = true\n                }\n\n                ConflictingProgramGroup {\n                    id: notificationDaemons\n                    Layout.alignment: Qt.AlignHCenter\n                    Layout.fillHeight: false\n                    programs: [\"mako\", \"dunst\"]\n                    description: Translation.tr(\"Conflicts with the shell's notification implementation\")\n                    onAlwaysSelected: Config.options.conflictKiller.autoKillNotificationDaemons = true\n                }\n                \n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/Appearance.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common.functions\npragma Singleton\npragma ComponentBehavior: Bound\n\nSingleton {\n    id: root\n    property QtObject m3colors\n    property QtObject animation\n    property QtObject animationCurves\n    property QtObject colors\n    property QtObject rounding\n    property QtObject font\n    property QtObject sizes\n    property string syntaxHighlightingTheme\n\n    // Transparency. The quadratic functions were derived from analysis of hand-picked transparency values.\n    ColorQuantizer {\n        id: wallColorQuant\n        property string wallpaperPath: Config.options.background.wallpaperPath\n        property bool wallpaperIsVideo: wallpaperPath.endsWith(\".mp4\") || wallpaperPath.endsWith(\".webm\") || wallpaperPath.endsWith(\".mkv\") || wallpaperPath.endsWith(\".avi\") || wallpaperPath.endsWith(\".mov\")\n        source: Qt.resolvedUrl(wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath)\n        depth: 0 // 2^0 = 1 color\n        rescaleSize: 10\n    }\n    property real wallpaperVibrancy: (wallColorQuant.colors[0]?.hslSaturation + wallColorQuant.colors[0]?.hslLightness) / 2\n    property real autoBackgroundTransparency: { // y = 0.5768x^2 - 0.759x + 0.2896\n        let x = wallpaperVibrancy\n        let y = 0.5768 * (x * x) - 0.759 * (x) + 0.2896\n        return Math.max(0, Math.min(0.22, y)) - 0.12 * (m3colors.darkmode ? 0 : 1)\n    }\n    property real autoContentTransparency: 0.9\n    property real backgroundTransparency: Config?.options.appearance.transparency.enable ? Config?.options.appearance.transparency.automatic ? autoBackgroundTransparency : Config?.options.appearance.transparency.backgroundTransparency : 0\n    property real contentTransparency: Config?.options.appearance.transparency.automatic ? autoContentTransparency : Config?.options.appearance.transparency.contentTransparency\n\n    m3colors: QtObject {\n        property bool darkmode: true\n        property bool transparent: false\n        property color m3background: \"#141313\"\n        property color m3onBackground: \"#e6e1e1\"\n        property color m3surface: \"#141313\"\n        property color m3surfaceDim: \"#141313\"\n        property color m3surfaceBright: \"#3a3939\"\n        property color m3surfaceContainerLowest: \"#0f0e0e\"\n        property color m3surfaceContainerLow: \"#1c1b1c\"\n        property color m3surfaceContainer: \"#201f20\"\n        property color m3surfaceContainerHigh: \"#2b2a2a\"\n        property color m3surfaceContainerHighest: \"#363435\"\n        property color m3onSurface: \"#e6e1e1\"\n        property color m3surfaceVariant: \"#49464a\"\n        property color m3onSurfaceVariant: \"#cbc5ca\"\n        property color m3inverseSurface: \"#e6e1e1\"\n        property color m3inverseOnSurface: \"#313030\"\n        property color m3outline: \"#948f94\"\n        property color m3outlineVariant: \"#49464a\"\n        property color m3shadow: \"#000000\"\n        property color m3scrim: \"#000000\"\n        property color m3surfaceTint: \"#cbc4cb\"\n        property color m3primary: \"#cbc4cb\"\n        property color m3onPrimary: \"#322f34\"\n        property color m3primaryContainer: \"#2d2a2f\"\n        property color m3onPrimaryContainer: \"#bcb6bc\"\n        property color m3inversePrimary: \"#615d63\"\n        property color m3secondary: \"#cac5c8\"\n        property color m3onSecondary: \"#323032\"\n        property color m3secondaryContainer: \"#4d4b4d\"\n        property color m3onSecondaryContainer: \"#ece6e9\"\n        property color m3tertiary: \"#d1c3c6\"\n        property color m3onTertiary: \"#372e30\"\n        property color m3tertiaryContainer: \"#31292b\"\n        property color m3onTertiaryContainer: \"#c1b4b7\"\n        property color m3error: \"#ffb4ab\"\n        property color m3onError: \"#690005\"\n        property color m3errorContainer: \"#93000a\"\n        property color m3onErrorContainer: \"#ffdad6\"\n        property color m3primaryFixed: \"#e7e0e7\"\n        property color m3primaryFixedDim: \"#cbc4cb\"\n        property color m3onPrimaryFixed: \"#1d1b1f\"\n        property color m3onPrimaryFixedVariant: \"#49454b\"\n        property color m3secondaryFixed: \"#e6e1e4\"\n        property color m3secondaryFixedDim: \"#cac5c8\"\n        property color m3onSecondaryFixed: \"#1d1b1d\"\n        property color m3onSecondaryFixedVariant: \"#484648\"\n        property color m3tertiaryFixed: \"#eddfe1\"\n        property color m3tertiaryFixedDim: \"#d1c3c6\"\n        property color m3onTertiaryFixed: \"#211a1c\"\n        property color m3onTertiaryFixedVariant: \"#4e4447\"\n        property color m3success: \"#B5CCBA\"\n        property color m3onSuccess: \"#213528\"\n        property color m3successContainer: \"#374B3E\"\n        property color m3onSuccessContainer: \"#D1E9D6\"\n        property color term0: \"#EDE4E4\"\n        property color term1: \"#B52755\"\n        property color term2: \"#A97363\"\n        property color term3: \"#AF535D\"\n        property color term4: \"#A67F7C\"\n        property color term5: \"#B2416B\"\n        property color term6: \"#8D76AD\"\n        property color term7: \"#272022\"\n        property color term8: \"#0E0D0D\"\n        property color term9: \"#B52755\"\n        property color term10: \"#A97363\"\n        property color term11: \"#AF535D\"\n        property color term12: \"#A67F7C\"\n        property color term13: \"#B2416B\"\n        property color term14: \"#8D76AD\"\n        property color term15: \"#221A1A\"\n    }\n\n    colors: QtObject {\n        property color colSubtext: m3colors.m3outline\n        // Layer 0\n        property color colLayer0Base: ColorUtils.mix(m3colors.m3background, m3colors.m3primary, Config.options.appearance.extraBackgroundTint ? 0.99 : 1)\n        property color colLayer0: ColorUtils.transparentize(colLayer0Base, root.backgroundTransparency)\n        property color colOnLayer0: m3colors.m3onBackground\n        property color colLayer0Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.9, root.contentTransparency))\n        property color colLayer0Active: ColorUtils.transparentize(ColorUtils.mix(colLayer0, colOnLayer0, 0.8, root.contentTransparency))\n        property color colLayer0Border: ColorUtils.mix(root.m3colors.m3outlineVariant, colLayer0, 0.4)\n        // Layer 1\n        property color colLayer1Base: m3colors.m3surfaceContainerLow\n        property color colLayer1: ColorUtils.solveOverlayColor(colLayer0Base, colLayer1Base, 1 - root.contentTransparency);\n        property color colOnLayer1: m3colors.m3onSurfaceVariant;\n        property color colOnLayer1Inactive: ColorUtils.mix(colOnLayer1, colLayer1, 0.45);\n        property color colLayer1Hover: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.92), root.contentTransparency)\n        property color colLayer1Active: ColorUtils.transparentize(ColorUtils.mix(colLayer1, colOnLayer1, 0.85), root.contentTransparency);\n        // Layer 2\n        property color colLayer2Base: m3colors.m3surfaceContainer\n        property color colLayer2: ColorUtils.solveOverlayColor(colLayer1Base, colLayer2Base, 1 - root.contentTransparency)\n        property color colLayer2Hover: ColorUtils.solveOverlayColor(colLayer1Base, ColorUtils.mix(colLayer2Base, colOnLayer2, 0.90), 1 - root.contentTransparency)\n        property color colLayer2Active: ColorUtils.solveOverlayColor(colLayer1Base, ColorUtils.mix(colLayer2Base, colOnLayer2, 0.80), 1 - root.contentTransparency);\n        property color colLayer2Disabled: ColorUtils.solveOverlayColor(colLayer1Base, ColorUtils.mix(colLayer2Base, m3colors.m3background, 0.8), 1 - root.contentTransparency);\n        property color colOnLayer2: m3colors.m3onSurface;\n        property color colOnLayer2Disabled: ColorUtils.mix(colOnLayer2, m3colors.m3background, 0.4);\n        // Layer 3\n        property color colLayer3Base: m3colors.m3surfaceContainerHigh\n        property color colLayer3: ColorUtils.solveOverlayColor(colLayer2Base, colLayer3Base, 1 - root.contentTransparency)\n        property color colLayer3Hover: ColorUtils.solveOverlayColor(colLayer2Base, ColorUtils.mix(colLayer3Base, colOnLayer3, 0.90), 1 - root.contentTransparency)\n        property color colLayer3Active: ColorUtils.solveOverlayColor(colLayer2Base, ColorUtils.mix(colLayer3Base, colOnLayer3, 0.80), 1 - root.contentTransparency);\n        property color colOnLayer3: m3colors.m3onSurface;\n        // Layer 4\n        property color colLayer4Base: m3colors.m3surfaceContainerHighest\n        property color colLayer4: ColorUtils.solveOverlayColor(colLayer3Base, colLayer4Base, 1 - root.contentTransparency)\n        property color colLayer4Hover: ColorUtils.solveOverlayColor(colLayer3Base, ColorUtils.mix(colLayer4Base, colOnLayer4, 0.90), 1 - root.contentTransparency)\n        property color colLayer4Active: ColorUtils.solveOverlayColor(colLayer3Base, ColorUtils.mix(colLayer4Base, colOnLayer4, 0.80), 1 - root.contentTransparency);\n        property color colOnLayer4: m3colors.m3onSurface;\n        // Primary\n        property color colPrimary: m3colors.m3primary\n        property color colOnPrimary: m3colors.m3onPrimary\n        property color colPrimaryHover: ColorUtils.mix(colors.colPrimary, colLayer1Hover, 0.87)\n        property color colPrimaryActive: ColorUtils.mix(colors.colPrimary, colLayer1Active, 0.7)\n        property color colPrimaryContainer: m3colors.m3primaryContainer\n        property color colPrimaryContainerHover: ColorUtils.mix(colors.colPrimaryContainer, colors.colOnPrimaryContainer, 0.9)\n        property color colPrimaryContainerActive: ColorUtils.mix(colors.colPrimaryContainer, colors.colOnPrimaryContainer, 0.8)\n        property color colOnPrimaryContainer: m3colors.m3onPrimaryContainer\n        // Secondary\n        property color colSecondary: m3colors.m3secondary\n        property color colSecondaryHover: ColorUtils.mix(m3colors.m3secondary, colLayer1Hover, 0.85)\n        property color colSecondaryActive: ColorUtils.mix(m3colors.m3secondary, colLayer1Active, 0.4)\n        property color colOnSecondary: m3colors.m3onSecondary\n        property color colSecondaryContainer: m3colors.m3secondaryContainer\n        property color colSecondaryContainerHover: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.90)\n        property color colSecondaryContainerActive: ColorUtils.mix(m3colors.m3secondaryContainer, m3colors.m3onSecondaryContainer, 0.54)\n        property color colOnSecondaryContainer: m3colors.m3onSecondaryContainer\n        // Tertiary\n        property color colTertiary: m3colors.m3tertiary\n        property color colTertiaryHover: ColorUtils.mix(m3colors.m3tertiary, colLayer1Hover, 0.85)\n        property color colTertiaryActive: ColorUtils.mix(m3colors.m3tertiary, colLayer1Active, 0.4)\n        property color colTertiaryContainer: m3colors.m3tertiaryContainer\n        property color colTertiaryContainerHover: ColorUtils.mix(m3colors.m3tertiaryContainer, m3colors.m3onTertiaryContainer, 0.90)\n        property color colTertiaryContainerActive: ColorUtils.mix(m3colors.m3tertiaryContainer, colLayer1Active, 0.54)\n        property color colOnTertiary: m3colors.m3onTertiary\n        property color colOnTertiaryContainer: m3colors.m3onTertiaryContainer\n        // Surface\n        property color colBackgroundSurfaceContainer: ColorUtils.transparentize(m3colors.m3surfaceContainer, root.backgroundTransparency)\n        property color colSurfaceContainerLow: ColorUtils.solveOverlayColor(m3colors.m3background, m3colors.m3surfaceContainerLow, 1 - root.contentTransparency)\n        property color colSurfaceContainer: ColorUtils.solveOverlayColor(m3colors.m3surfaceContainerLow, m3colors.m3surfaceContainer, 1 - root.contentTransparency)\n        property color colSurfaceContainerHigh: ColorUtils.solveOverlayColor(m3colors.m3surfaceContainer, m3colors.m3surfaceContainerHigh, 1 - root.contentTransparency)\n        property color colSurfaceContainerHighest: ColorUtils.solveOverlayColor(m3colors.m3surfaceContainerHigh, m3colors.m3surfaceContainerHighest, 1 - root.contentTransparency)\n        property color colSurfaceContainerHighestHover: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.95)\n        property color colSurfaceContainerHighestActive: ColorUtils.mix(m3colors.m3surfaceContainerHighest, m3colors.m3onSurface, 0.85)\n        property color colOnSurface: m3colors.m3onSurface\n        property color colOnSurfaceVariant: m3colors.m3onSurfaceVariant\n        // Misc\n        property color colTooltip: m3colors.m3inverseSurface\n        property color colOnTooltip: m3colors.m3inverseOnSurface\n        property color colScrim: ColorUtils.transparentize(m3colors.m3scrim, 0.5)\n        property color colShadow: ColorUtils.transparentize(m3colors.m3shadow, 0.7)\n        property color colOutline: m3colors.m3outline\n        property color colOutlineVariant: m3colors.m3outlineVariant\n        property color colError: m3colors.m3error\n        property color colErrorHover: ColorUtils.mix(m3colors.m3error, colLayer1Hover, 0.85)\n        property color colErrorActive: ColorUtils.mix(m3colors.m3error, colLayer1Active, 0.7)\n        property color colOnError: m3colors.m3onError\n        property color colErrorContainer: m3colors.m3errorContainer\n        property color colErrorContainerHover: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.90)\n        property color colErrorContainerActive: ColorUtils.mix(m3colors.m3errorContainer, m3colors.m3onErrorContainer, 0.70)\n        property color colOnErrorContainer: m3colors.m3onErrorContainer\n    }\n\n    rounding: QtObject {\n        property int unsharpen: 2\n        property int unsharpenmore: 6\n        property int verysmall: 8\n        property int small: 12\n        property int normal: 17\n        property int large: 23\n        property int verylarge: 30\n        property int full: 9999\n        property int screenRounding: large\n        property int windowRounding: 18\n    }\n\n    font: QtObject {\n        property QtObject family: QtObject {\n            property string main: Config.options.appearance.fonts.main\n            property string numbers: Config.options.appearance.fonts.numbers\n            property string title: Config.options.appearance.fonts.title\n            property string iconMaterial: \"Material Symbols Rounded\"\n            property string iconNerd: Config.options.appearance.fonts.iconNerd\n            property string monospace: Config.options.appearance.fonts.monospace\n            property string reading: Config.options.appearance.fonts.reading\n            property string expressive: Config.options.appearance.fonts.expressive\n        }\n        property QtObject variableAxes: QtObject {\n            property var main: ({\n                \"wght\": 450,\n                \"wdth\": 100,\n            })\n            property var numbers: ({\n                \"wght\": 450,\n            })\n            property var title: ({ // Slightly bold weight for title\n                \"wght\": 550, // Weight (Lowered to compensate for increased grade)\n            })\n        }\n        property QtObject pixelSize: QtObject {\n            property int smallest: 10\n            property int smaller: 12\n            property int smallie: 13\n            property int small: 15\n            property int normal: 16\n            property int large: 17\n            property int larger: 19\n            property int huge: 22\n            property int hugeass: 23\n            property int title: huge\n        }\n    }\n\n    animationCurves: QtObject {\n        readonly property list<real> expressiveFastSpatial: [0.42, 1.67, 0.21, 0.90, 1, 1] // Default, 350ms\n        readonly property list<real> expressiveDefaultSpatial: [0.38, 1.21, 0.22, 1.00, 1, 1] // Default, 500ms\n        readonly property list<real> expressiveSlowSpatial: [0.39, 1.29, 0.35, 0.98, 1, 1] // Default, 650ms\n        readonly property list<real> expressiveEffects: [0.34, 0.80, 0.34, 1.00, 1, 1] // Default, 200ms\n        readonly property list<real> emphasized: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82, 0.25, 1, 1, 1]\n        readonly property list<real> emphasizedFirstHalf: [0.05, 0, 2 / 15, 0.06, 1 / 6, 0.4, 5 / 24, 0.82]\n        readonly property list<real> emphasizedLastHalf: [5 / 24, 0.82, 0.25, 1, 1, 1]\n        readonly property list<real> emphasizedAccel: [0.3, 0, 0.8, 0.15, 1, 1]\n        readonly property list<real> emphasizedDecel: [0.05, 0.7, 0.1, 1, 1, 1]\n        readonly property list<real> standard: [0.2, 0, 0, 1, 1, 1]\n        readonly property list<real> standardAccel: [0.3, 0, 1, 1, 1, 1]\n        readonly property list<real> standardDecel: [0, 0, 0, 1, 1, 1]\n        readonly property real expressiveFastSpatialDuration: 350\n        readonly property real expressiveDefaultSpatialDuration: 500\n        readonly property real expressiveSlowSpatialDuration: 650\n        readonly property real expressiveEffectsDuration: 200\n    }\n\n    animation: QtObject {\n        property QtObject elementMove: QtObject {\n            property int duration: animationCurves.expressiveDefaultSpatialDuration\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial\n            property int velocity: 650\n            property Component numberAnimation: Component {\n                NumberAnimation {\n                    duration: root.animation.elementMove.duration\n                    easing.type: root.animation.elementMove.type\n                    easing.bezierCurve: root.animation.elementMove.bezierCurve\n                }\n            }\n        }\n\n        property QtObject elementMoveSmall: QtObject {\n            property int duration: animationCurves.expressiveFastSpatialDuration\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.expressiveFastSpatial\n            property int velocity: 650\n            property Component numberAnimation: Component {\n                NumberAnimation {\n                    duration: root.animation.elementMoveSmall.duration\n                    easing.type: root.animation.elementMoveSmall.type\n                    easing.bezierCurve: root.animation.elementMoveSmall.bezierCurve\n                }\n            }\n        }\n\n        property QtObject elementMoveEnter: QtObject {\n            property int duration: 400\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.emphasizedDecel\n            property int velocity: 650\n            property Component numberAnimation: Component {\n                NumberAnimation {\n                    alwaysRunToEnd: true\n                    duration: root.animation.elementMoveEnter.duration\n                    easing.type: root.animation.elementMoveEnter.type\n                    easing.bezierCurve: root.animation.elementMoveEnter.bezierCurve\n                }\n            }\n        }\n\n        property QtObject elementMoveExit: QtObject {\n            property int duration: 200\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.emphasizedAccel\n            property int velocity: 650\n            property Component numberAnimation: Component {\n                NumberAnimation {\n                    alwaysRunToEnd: true\n                    duration: root.animation.elementMoveExit.duration\n                    easing.type: root.animation.elementMoveExit.type\n                    easing.bezierCurve: root.animation.elementMoveExit.bezierCurve\n                }\n            }\n        }\n\n        property QtObject elementMoveFast: QtObject {\n            property int duration: animationCurves.expressiveEffectsDuration\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.expressiveEffects\n            property int velocity: 850\n            property Component colorAnimation: Component { ColorAnimation {\n                duration: root.animation.elementMoveFast.duration\n                easing.type: root.animation.elementMoveFast.type\n                easing.bezierCurve: root.animation.elementMoveFast.bezierCurve\n            }}\n            property Component numberAnimation: Component { NumberAnimation {\n                alwaysRunToEnd: true\n                duration: root.animation.elementMoveFast.duration\n                easing.type: root.animation.elementMoveFast.type\n                easing.bezierCurve: root.animation.elementMoveFast.bezierCurve\n            }}\n        }\n\n        property QtObject elementResize: QtObject {\n            property int duration: 300\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.emphasized\n            property int velocity: 650\n            property Component numberAnimation: Component {\n                NumberAnimation {\n                    alwaysRunToEnd: true\n                    duration: root.animation.elementResize.duration\n                    easing.type: root.animation.elementResize.type\n                    easing.bezierCurve: root.animation.elementResize.bezierCurve\n                }\n            }\n        }\n\n        property QtObject clickBounce: QtObject {\n            property int duration: 400\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: animationCurves.expressiveDefaultSpatial\n            property int velocity: 850\n            property Component numberAnimation: Component { NumberAnimation {\n                alwaysRunToEnd: true\n                duration: root.animation.clickBounce.duration\n                easing.type: root.animation.clickBounce.type\n                easing.bezierCurve: root.animation.clickBounce.bezierCurve\n            }}\n        }\n        \n        property QtObject scroll: QtObject {\n            property int duration: 200\n            property int type: Easing.BezierSpline\n            property list<real> bezierCurve: root.animationCurves.standardDecel\n        }\n\n        property QtObject menuDecel: QtObject {\n            property int duration: 350\n            property int type: Easing.OutExpo\n        }\n    }\n\n    sizes: QtObject {\n        property real baseBarHeight: 40\n        property real barHeight: Config.options.bar.cornerStyle === 1 ? \n            (baseBarHeight + root.sizes.hyprlandGapsOut * 2) : baseBarHeight\n        property real barCenterSideModuleWidth: Config.options?.bar.verbose ? 360 : 140\n        property real barCenterSideModuleWidthShortened: 280\n        property real barCenterSideModuleWidthHellaShortened: 190\n        property real barShortenScreenWidthThreshold: 1200 // Shorten if screen width is at most this value\n        property real barHellaShortenScreenWidthThreshold: 1000 // Shorten even more...\n        property real elevationMargin: 10\n        property real fabShadowRadius: 5\n        property real fabHoveredShadowRadius: 7\n        property real hyprlandGapsOut: 5\n        property real mediaControlsWidth: 440\n        property real mediaControlsHeight: 160\n        property real notificationPopupWidth: 410\n        property real osdWidth: 180\n        property real searchWidthCollapsed: 210\n        property real searchWidth: 360\n        property real sidebarWidth: 460\n        property real sidebarWidthExtended: 750\n        property real baseVerticalBarWidth: 46\n        property real verticalBarWidth: Config.options.bar.cornerStyle === 1 ? \n            (baseVerticalBarWidth + root.sizes.hyprlandGapsOut * 2) : baseVerticalBarWidth\n        property real wallpaperSelectorWidth: 1200\n        property real wallpaperSelectorHeight: 690\n        property real wallpaperSelectorItemMargins: 8\n        property real wallpaperSelectorItemPadding: 6\n    }\n\n    syntaxHighlightingTheme: root.m3colors.darkmode ? \"Monokai\" : \"ayu Light\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/Config.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common.functions\n\nSingleton {\n    id: root\n    property string filePath: Directories.shellConfigPath\n    property alias options: configOptionsJsonAdapter\n    property bool ready: false\n    property int readWriteDelay: 50 // milliseconds\n    property bool blockWrites: false\n\n    function setNestedValue(nestedKey, value) {\n        let keys = nestedKey.split(\".\");\n        let obj = root.options;\n        let parents = [obj];\n\n        // Traverse and collect parent objects\n        for (let i = 0; i < keys.length - 1; ++i) {\n            if (!obj[keys[i]] || typeof obj[keys[i]] !== \"object\") {\n                obj[keys[i]] = {};\n            }\n            obj = obj[keys[i]];\n            parents.push(obj);\n        }\n\n        // Convert value to correct type using JSON.parse when safe\n        let convertedValue = value;\n        if (typeof value === \"string\") {\n            let trimmed = value.trim();\n            if (trimmed === \"true\" || trimmed === \"false\" || !isNaN(Number(trimmed))) {\n                try {\n                    convertedValue = JSON.parse(trimmed);\n                } catch (e) {\n                    convertedValue = value;\n                }\n            }\n        }\n\n        obj[keys[keys.length - 1]] = convertedValue;\n    }\n\n    Timer {\n        id: fileReloadTimer\n        interval: root.readWriteDelay\n        repeat: false\n        onTriggered: {\n            configFileView.reload()\n        }\n    }\n\n    Timer {\n        id: fileWriteTimer\n        interval: root.readWriteDelay\n        repeat: false\n        onTriggered: {\n            configFileView.writeAdapter()\n        }\n    }\n\n    FileView {\n        id: configFileView\n        path: root.filePath\n        watchChanges: true\n        blockWrites: root.blockWrites\n        onFileChanged: fileReloadTimer.restart()\n        onAdapterUpdated: fileWriteTimer.restart()\n        onLoaded: root.ready = true\n        onLoadFailed: error => {\n            if (error == FileViewError.FileNotFound) {\n                writeAdapter();\n            }\n        }\n\n        JsonAdapter {\n            id: configOptionsJsonAdapter\n\n            property string panelFamily: \"ii\" // \"ii\", \"waffle\"\n\n            property JsonObject policies: JsonObject {\n                property int ai: 1 // 0: No | 1: Yes | 2: Local\n                property int weeb: 1 // 0: No | 1: Open | 2: Closet\n            }\n\n            property JsonObject ai: JsonObject {\n                property string systemPrompt: \"## Style\\n- Use casual tone, don't be formal!\\n- Always be brief and to the point, unless asked otherwise\\n- Don't repeat the user's question\\n- Be approachable: Avoid using overly complicated, domain-specific terms and provide analogies when asked to explain a concept\\n\\n## Context (ignore when irrelevant)\\n- You are a helpful and inspiring sidebar assistant on a {DISTRO} Linux system\\n- Desktop environment: {DE}\\n- Current date & time: {DATETIME}\\n- Focused app: {WINDOWCLASS}\\n\\n## Presentation\\n- Use Markdown features in your response: \\n  - **Bold** text to **highlight keywords** in your response\\n  - **Split long information into small sections** with h2 headers and a relevant emoji at the start of it (for example `## 🐧 Linux`). Bullet points are preferred over long paragraphs, unless you're offering writing support or instructed otherwise by the user.\\n- Asked to compare different options? You should firstly use a table to compare the main aspects, then elaborate or include relevant comments from online forums *after* the table. Make sure to provide a final recommendation for the user's use case!\\n- Use LaTeX formatting for mathematical and scientific notations whenever appropriate. Enclose all LaTeX '$$' delimiters. NEVER generate LaTeX code in a latex block unless the user explicitly asks for it. DO NOT use LaTeX for regular documents (resumes, letters, essays, CVs, etc.).\\n\\nThanks!\\n\"\n                property string tool: \"functions\" // search, functions, or none\n                property list<var> extraModels: [\n                    {\n                        \"api_format\": \"openai\", // Most of the time you want \"openai\". Use \"gemini\" for Google's models\n                        \"description\": \"This is a custom model. Edit the config to add more! | Anyway, this is DeepSeek R1 Distill LLaMA 70B\",\n                        \"endpoint\": \"https://openrouter.ai/api/v1/chat/completions\",\n                        \"homepage\": \"https://openrouter.ai/deepseek/deepseek-r1-distill-llama-70b:free\", // Not mandatory\n                        \"icon\": \"spark-symbolic\", // Not mandatory\n                        \"key_get_link\": \"https://openrouter.ai/settings/keys\", // Not mandatory\n                        \"key_id\": \"openrouter\",\n                        \"model\": \"deepseek/deepseek-r1-distill-llama-70b:free\",\n                        \"name\": \"Custom: DS R1 Dstl. LLaMA 70B\",\n                        \"requires_key\": true\n                    }\n                ]\n            }\n\n            property JsonObject appearance: JsonObject {\n                property bool extraBackgroundTint: true\n                property int fakeScreenRounding: 2 // 0: None | 1: Always | 2: When not fullscreen\n                property JsonObject fonts: JsonObject {\n                    property string main: \"Google Sans Flex\"\n                    property string numbers: \"Google Sans Flex\"\n                    property string title: \"Google Sans Flex\"\n                    property string iconNerd: \"JetBrains Mono NF\"\n                    property string monospace: \"JetBrains Mono NF\"\n                    property string reading: \"Readex Pro\"\n                    property string expressive: \"Space Grotesk\"\n                }\n                property JsonObject transparency: JsonObject {\n                    property bool enable: false\n                    property bool automatic: true\n                    property real backgroundTransparency: 0.11\n                    property real contentTransparency: 0.57\n                }\n                property JsonObject wallpaperTheming: JsonObject {\n                    property bool enableAppsAndShell: true\n                    property bool enableQtApps: true\n                    property bool enableTerminal: true\n                    property JsonObject terminalGenerationProps: JsonObject {\n                        property real harmony: 0.6\n                        property real harmonizeThreshold: 100\n                        property real termFgBoost: 0.35\n                        property bool forceDarkMode: false\n                    }\n                }\n                property JsonObject palette: JsonObject {\n                    property string type: \"auto\" // Allowed: auto, scheme-content, scheme-expressive, scheme-fidelity, scheme-fruit-salad, scheme-monochrome, scheme-neutral, scheme-rainbow, scheme-tonal-spot\n                    property string accentColor: \"\"\n                }\n            }\n\n            property JsonObject audio: JsonObject {\n                // Values in %\n                property JsonObject protection: JsonObject {\n                    // Prevent sudden bangs\n                    property bool enable: false\n                    property real maxAllowedIncrease: 10\n                    property real maxAllowed: 99\n                }\n            }\n\n            property JsonObject apps: JsonObject {\n                property string bluetooth: \"kcmshell6 kcm_bluetooth\"\n                property string changePassword: \"kitty -1 --hold=yes fish -i -c 'passwd'\"\n                property string network: \"kcmshell6 kcm_networkmanagement\"\n                property string manageUser: \"kcmshell6 kcm_users\"\n                property string networkEthernet: \"kcmshell6 kcm_networkmanagement\"\n                property string taskManager: \"plasma-systemmonitor --page-name Processes\"\n                property string terminal: \"kitty -1\" // This is only for shell actions\n                property string update: \"kitty -1 --hold=yes fish -i -c 'pkexec pacman -Syu'\"\n                property string volumeMixer: `~/.config/hypr/hyprland/scripts/launch_first_available.sh \"pavucontrol-qt\" \"pavucontrol\"`\n            }\n\n            property JsonObject background: JsonObject {\n                property JsonObject widgets: JsonObject {\n                    property JsonObject clock: JsonObject {\n                        property bool enable: true\n                        property bool showOnlyWhenLocked: false\n                        property string placementStrategy: \"leastBusy\" // \"free\", \"leastBusy\", \"mostBusy\"\n                        property real x: 100\n                        property real y: 100\n                        property string style: \"cookie\"        // Options: \"cookie\", \"digital\"\n                        property string styleLocked: \"cookie\"  // Options: \"cookie\", \"digital\"\n                        property JsonObject cookie: JsonObject {\n                            property bool aiStyling: false\n                            property int sides: 14\n                            property string dialNumberStyle: \"full\"   // Options: \"dots\" , \"numbers\", \"full\" , \"none\"\n                            property string hourHandStyle: \"fill\"     // Options: \"classic\", \"fill\", \"hollow\", \"hide\"\n                            property string minuteHandStyle: \"medium\" // Options \"classic\", \"thin\", \"medium\", \"bold\", \"hide\"\n                            property string secondHandStyle: \"dot\"    // Options: \"dot\", \"line\", \"classic\", \"hide\"\n                            property string dateStyle: \"bubble\"       // Options: \"border\", \"rect\", \"bubble\" , \"hide\"\n                            property bool timeIndicators: true\n                            property bool hourMarks: false\n                            property bool dateInClock: true\n                            property bool constantlyRotate: false\n                            property bool useSineCookie: false\n                        }\n                        property JsonObject digital: JsonObject {\n                            property bool adaptiveAlignment: true\n                            property bool showDate: true\n                            property bool animateChange: true\n                            property bool vertical: false\n                            property JsonObject font: JsonObject {\n                                property string family: \"Google Sans Flex\"\n                                property real weight: 350\n                                property real width: 100\n                                property real size: 90\n                                property real roundness: 0\n                            }\n                        }\n                        property JsonObject quote: JsonObject {\n                            property bool enable: false\n                            property string text: \"\"\n                        }\n                    }\n                    property JsonObject weather: JsonObject {\n                        property bool enable: false\n                        property string placementStrategy: \"free\" // \"free\", \"leastBusy\", \"mostBusy\"\n                        property real x: 400\n                        property real y: 100\n                    }\n                }\n                property string wallpaperPath: \"\"\n                property string thumbnailPath: \"\"\n                property bool hideWhenFullscreen: true\n                property JsonObject parallax: JsonObject {\n                    property bool vertical: false\n                    property bool autoVertical: false\n                    property bool enableWorkspace: false\n                    property real workspaceZoom: 1.07 // Relative to wallpaper size\n                    property bool enableSidebar: false\n                    property real widgetsFactor: 1.2\n                }\n            }\n\n            property JsonObject bar: JsonObject {\n                property JsonObject autoHide: JsonObject {\n                    property bool enable: false\n                    property int hoverRegionWidth: 2\n                    property bool pushWindows: false\n                    property JsonObject showWhenPressingSuper: JsonObject {\n                        property bool enable: true\n                        property int delay: 140\n                    }\n                }\n                property bool bottom: false // Instead of top\n                property int cornerStyle: 0 // 0: Hug | 1: Float | 2: Plain rectangle\n                property bool floatStyleShadow: true // Show shadow behind bar when cornerStyle == 1 (Float)\n                property bool borderless: false // true for no grouping of items\n                property string topLeftIcon: \"spark\" // Options: \"distro\" or any icon name in ~/.config/quickshell/ii/assets/icons\n                property bool showBackground: true\n                property bool verbose: true\n                property bool vertical: false\n                property JsonObject resources: JsonObject {\n                    property bool alwaysShowSwap: true\n                    property bool alwaysShowCpu: true\n                    property int memoryWarningThreshold: 95\n                    property int swapWarningThreshold: 85\n                    property int cpuWarningThreshold: 90\n                }\n                property list<string> screenList: [] // List of names, like \"eDP-1\", find out with 'hyprctl monitors' command\n                property JsonObject utilButtons: JsonObject {\n                    property bool showScreenSnip: true\n                    property bool showColorPicker: false\n                    property bool showMicToggle: false\n                    property bool showKeyboardToggle: true\n                    property bool showDarkModeToggle: true\n                    property bool showPerformanceProfileToggle: false\n                    property bool showScreenRecord: false\n                }\n                property JsonObject workspaces: JsonObject {\n                    property bool monochromeIcons: true\n                    property int shown: 10\n                    property bool showAppIcons: true\n                    property bool alwaysShowNumbers: false\n                    property int showNumberDelay: 300 // milliseconds\n                    property list<string> numberMap: [\"1\", \"2\"] // Characters to show instead of numbers on workspace indicator\n                    property bool useNerdFont: false\n                }\n                property JsonObject weather: JsonObject {\n                    property bool enable: false\n                    property bool enableGPS: true // gps based location\n                    property string city: \"\" // When 'enableGPS' is false\n                    property bool useUSCS: false // Instead of metric (SI) units\n                    property int fetchInterval: 10 // minutes\n                }\n                property JsonObject indicators: JsonObject {\n                    property JsonObject notifications: JsonObject {\n                        property bool showUnreadCount: false\n                    }\n                }\n                property JsonObject tooltips: JsonObject {\n                    property bool clickToShow: false\n                }\n            }\n\n            property JsonObject battery: JsonObject {\n                property int low: 20\n                property int critical: 5\n                property int full: 101\n                property bool automaticSuspend: true\n                property int suspend: 3\n            }\n\n            property JsonObject calendar: JsonObject {\n                property string locale: \"en-GB\"\n            }\n\n            property JsonObject cheatsheet: JsonObject {\n                // Use a nerdfont to see the icons\n                // 0: 󰖳  | 1: 󰌽 | 2: 󰘳 | 3:  | 4: 󰨡\n                // 5:  | 6:  | 7: 󰣇 | 8:  | 9: \n                // 10:  | 11:  | 12:  | 13:  | 14: 󱄛\n                property string superKey: \"\"\n                property bool useMacSymbol: false\n                property bool splitButtons: false\n                property bool useMouseSymbol: false\n                property bool useFnSymbol: false\n                property JsonObject fontSize: JsonObject {\n                    property int key: Appearance.font.pixelSize.smaller\n                    property int comment: Appearance.font.pixelSize.smaller\n                }\n            }\n\n            property JsonObject conflictKiller: JsonObject {\n                property bool autoKillNotificationDaemons: false\n                property bool autoKillTrays: false\n            }\n\n            property JsonObject crosshair: JsonObject {\n                // Valorant crosshair format. Use https://www.vcrdb.net/builder\n                property string code: \"0;P;d;1;0l;10;0o;2;1b;0\"\n            }\n\n            property JsonObject dock: JsonObject {\n                property bool enable: false\n                property bool monochromeIcons: true\n                property real height: 60\n                property real hoverRegionHeight: 2\n                property bool pinnedOnStartup: false\n                property bool hoverToReveal: true // When false, only reveals on empty workspace\n                property list<string> pinnedApps: [ // IDs of pinned entries\n                    \"org.kde.dolphin\", \"kitty\",]\n                property list<string> ignoredAppRegexes: []\n            }\n\n            property JsonObject interactions: JsonObject {\n                property JsonObject scrolling: JsonObject {\n                    property bool fasterTouchpadScroll: false // Enable faster scrolling with touchpad\n                    property int mouseScrollDeltaThreshold: 120 // delta >= this then it gets detected as mouse scroll rather than touchpad\n                    property int mouseScrollFactor: 120\n                    property int touchpadScrollFactor: 450\n                }\n                property JsonObject deadPixelWorkaround: JsonObject { // Hyprland leaves out 1 pixel on the right for interactions\n                    property bool enable: false\n                }\n            }\n\n            property JsonObject language: JsonObject {\n                property string ui: \"auto\" // UI language. \"auto\" for system locale, or specific language code like \"zh_CN\", \"en_US\"\n                property JsonObject translator: JsonObject {\n                    property string engine: \"auto\" // Run `trans -list-engines` for available engines. auto should use google\n                    property string targetLanguage: \"auto\" // Run `trans -list-all` for available languages\n                    property string sourceLanguage: \"auto\"\n                }\n            }\n\n            property JsonObject launcher: JsonObject {\n                property list<string> pinnedApps: [ \"org.kde.dolphin\", \"kitty\", \"cmake-gui\"]\n            }\n\n            property JsonObject light: JsonObject {\n                property JsonObject night: JsonObject {\n                    property bool automatic: true\n                    property string from: \"19:00\" // Format: \"HH:mm\", 24-hour time\n                    property string to: \"06:30\"   // Format: \"HH:mm\", 24-hour time\n                    property int colorTemperature: 5000\n                }\n                property JsonObject antiFlashbang: JsonObject {\n                    property bool enable: false\n                }\n            }\n\n            property JsonObject lock: JsonObject {\n                property bool useHyprlock: false\n                property bool launchOnStartup: false\n                property JsonObject blur: JsonObject {\n                    property bool enable: true\n                    property real radius: 100\n                    property real extraZoom: 1.1\n                }\n                property bool centerClock: true\n                property bool showLockedText: true\n                property JsonObject security: JsonObject {\n                    property bool unlockKeyring: true\n                    property bool requirePasswordToPower: false\n                }\n                property bool materialShapeChars: true\n            }\n\n            property JsonObject media: JsonObject {\n                // Attempt to remove dupes (the aggregator playerctl one and browsers' native ones when there's plasma browser integration)\n                property bool filterDuplicatePlayers: true\n            }\n\n            property JsonObject networking: JsonObject {\n                property string userAgent: \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36\"\n            }\n\n            property JsonObject notifications: JsonObject {\n                property int timeout: 7000\n                property JsonObject monitor: JsonObject {\n                    property bool enable: false\n                    property string name: \"\" // Name of the monitor to show notifications on, like \"eDP-1\". Find out with 'hyprctl monitors' command\n                }\n            }\n\n            property JsonObject osd: JsonObject {\n                property int timeout: 1000\n            }\n\n            property JsonObject osk: JsonObject {\n                property string layout: \"qwerty_full\"\n                property bool pinnedOnStartup: false\n            }\n\n            property JsonObject overlay: JsonObject {\n                property bool openingZoomAnimation: true\n                property bool darkenScreen: true\n                property real clickthroughOpacity: 0.8\n                property JsonObject floatingImage: JsonObject {\n                    property string imageSource: \"https://media.tenor.com/H5U5bJzj3oAAAAAi/kukuru.gif\"\n                    property real scale: 0.5\n                }\n            }\n\n            property JsonObject overview: JsonObject {\n                property bool enable: true\n                property real scale: 0.18 // Relative to screen size\n                property real rows: 2\n                property real columns: 5\n                property bool orderRightLeft: false\n                property bool orderBottomUp: false\n                property bool centerIcons: true\n            }\n\n            property JsonObject regionSelector: JsonObject {\n                property JsonObject targetRegions: JsonObject {\n                    property bool windows: true\n                    property bool layers: false\n                    property bool content: true\n                    property bool showLabel: false\n                    property real opacity: 0.3\n                    property real contentRegionOpacity: 0.8\n                    property int selectionPadding: 5\n                }\n                property JsonObject rect: JsonObject {\n                    property bool showAimLines: true\n                }\n                property JsonObject circle: JsonObject {\n                    property int strokeWidth: 6\n                    property int padding: 10\n                }\n                property JsonObject annotation: JsonObject {\n                    property bool useSatty: false\n                }\n            }\n\n            property JsonObject resources: JsonObject {\n                property int updateInterval: 3000\n                property int historyLength: 60\n            }\n\n            property JsonObject tray: JsonObject {\n                property bool monochromeIcons: true\n                property bool showItemId: false\n                property bool invertPinnedItems: true // Makes the below a whitelist for the tray and blacklist for the pinned area\n                property list<var> pinnedItems: [ \"Fcitx\" ]\n                property bool filterPassive: true\n            }\n\n            property JsonObject musicRecognition: JsonObject {\n                property int timeout: 16\n                property int interval: 4\n            }\n\n            property JsonObject search: JsonObject {\n                property int nonAppResultDelay: 30 // This prevents lagging when typing\n                property string engineBaseUrl: \"https://www.google.com/search?q=\"\n                property list<string> excludedSites: [\"quora.com\", \"facebook.com\"]\n                property bool sloppy: false // Uses levenshtein distance based scoring instead of fuzzy sort. Very weird.\n                property JsonObject prefix: JsonObject {\n                    property bool showDefaultActionsWithoutPrefix: true\n                    property string action: \"/\"\n                    property string app: \">\"\n                    property string clipboard: \";\"\n                    property string emojis: \":\"\n                    property string math: \"=\"\n                    property string shellCommand: \"$\"\n                    property string webSearch: \"?\"\n                }\n                property JsonObject imageSearch: JsonObject {\n                    property string imageSearchEngineBaseUrl: \"https://lens.google.com/uploadbyurl?url=\"\n                    property bool useCircleSelection: false\n                }\n            }\n\n            property JsonObject sidebar: JsonObject {\n                property bool keepRightSidebarLoaded: true\n                property JsonObject translator: JsonObject {\n                    property bool enable: false\n                    property int delay: 300 // Delay before sending request. Reduces (potential) rate limits and lag.\n                }\n                property JsonObject ai: JsonObject {\n                    property bool textFadeIn: false\n                }\n                property JsonObject booru: JsonObject {\n                    property bool allowNsfw: false\n                    property string defaultProvider: \"yandere\"\n                    property int limit: 20\n                    property JsonObject zerochan: JsonObject {\n                        property string username: \"[unset]\"\n                    }\n                }\n                property JsonObject cornerOpen: JsonObject {\n                    property bool enable: true\n                    property bool bottom: false\n                    property bool valueScroll: true\n                    property bool clickless: false\n                    property int cornerRegionWidth: 250\n                    property int cornerRegionHeight: 5\n                    property bool visualize: false\n                    property bool clicklessCornerEnd: true\n                    property int clicklessCornerVerticalOffset: 1\n                }\n\n                property JsonObject quickToggles: JsonObject {\n                    property string style: \"android\" // Options: classic, android\n                    property JsonObject android: JsonObject {\n                        property int columns: 5\n                        property list<var> toggles: [\n                            { \"size\": 2, \"type\": \"network\" },\n                            { \"size\": 2, \"type\": \"bluetooth\"  },\n                            { \"size\": 1, \"type\": \"idleInhibitor\" },\n                            { \"size\": 1, \"type\": \"mic\" },\n                            { \"size\": 2, \"type\": \"audio\" },\n                            { \"size\": 2, \"type\": \"nightLight\" }\n                        ]\n                    }\n                }\n\n                property JsonObject quickSliders: JsonObject {\n                    property bool enable: false\n                    property bool showMic: false\n                    property bool showVolume: true\n                    property bool showBrightness: true\n                }\n            }\n\n            property JsonObject screenRecord: JsonObject {\n                property string savePath: Directories.videos.replace(\"file://\",\"\") // strip \"file://\"\n            }\n\n            property JsonObject screenSnip: JsonObject {\n                property string savePath: \"\" // only copy to clipboard when empty\n            }\n\n            property JsonObject sounds: JsonObject {\n                property bool battery: false\n                property bool pomodoro: false\n                property string theme: \"freedesktop\"\n            }\n\n            property JsonObject time: JsonObject {\n                // https://doc.qt.io/qt-6/qtime.html#toString\n                property string format: \"hh:mm\"\n                property string shortDateFormat: \"dd/MM\"\n                property string dateWithYearFormat: \"dd/MM/yyyy\"\n                property string dateFormat: \"ddd, dd/MM\"\n                property JsonObject pomodoro: JsonObject {\n                    property int breakTime: 300\n                    property int cyclesBeforeLongBreak: 4\n                    property int focus: 1500\n                    property int longBreak: 900\n                }\n                property bool secondPrecision: false\n            }\n\n            property JsonObject updates: JsonObject {\n                property bool enableCheck: true\n                property int checkInterval: 120 // minutes\n                property int adviseUpdateThreshold: 75 // packages\n                property int stronglyAdviseUpdateThreshold: 200 // packages\n            }\n            \n            property JsonObject wallpaperSelector: JsonObject {\n                property bool useSystemFileDialog: false\n            }\n            \n            property JsonObject windows: JsonObject {\n                property bool showTitlebar: true // Client-side decoration for shell apps\n                property bool centerTitle: true\n            }\n\n            property JsonObject hacks: JsonObject {\n                property int arbitraryRaceConditionDelay: 20 // milliseconds\n            }\n\n            property JsonObject workSafety: JsonObject {\n                property JsonObject enable: JsonObject {\n                    property bool wallpaper: false\n                    property bool clipboard: false\n                }\n                property JsonObject triggerCondition: JsonObject {\n                    property list<string> networkNameKeywords: [\"airport\", \"cafe\", \"college\", \"company\", \"eduroam\", \"free\", \"guest\", \"public\", \"school\", \"university\"]\n                    property list<string> fileKeywords: [\"anime\", \"booru\", \"ecchi\", \"hentai\", \"yande.re\", \"konachan\", \"breast\", \"nipples\", \"pussy\", \"nsfw\", \"spoiler\", \"girl\"]\n                    property list<string> linkKeywords: [\"hentai\", \"porn\", \"sukebei\", \"hitomi.la\", \"rule34\", \"gelbooru\", \"fanbox\", \"dlsite\"]\n                }\n            }\n\n            property JsonObject waffles: JsonObject {\n                // Some spots are kinda janky/awkward. Setting the following to\n                // false will make (some) stuff also be like that for accuracy. \n                // Example: the right-click menu of the Start button\n                property JsonObject tweaks: JsonObject {\n                    property bool switchHandlePositionFix: true\n                    property bool smootherMenuAnimations: true\n                    property bool smootherSearchBar: true\n                }\n                property JsonObject bar: JsonObject {\n                    property bool bottom: true\n                    property bool leftAlignApps: false\n                }\n                property JsonObject actionCenter: JsonObject {\n                    property list<string> toggles: [ \"network\", \"bluetooth\", \"easyEffects\", \"powerProfile\", \"idleInhibitor\", \"nightLight\", \"darkMode\", \"antiFlashbang\", \"cloudflareWarp\", \"mic\", \"musicRecognition\", \"notifications\", \"onScreenKeyboard\", \"gameMode\", \"screenSnip\", \"colorPicker\" ]\n                }\n                property JsonObject calendar: JsonObject {\n                    property bool force2CharDayOfWeek: true\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/Directories.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common.functions\nimport QtCore\nimport QtQuick\nimport Quickshell\n\nSingleton {\n    // XDG Dirs, with \"file://\"\n    readonly property string home: StandardPaths.standardLocations(StandardPaths.HomeLocation)[0]\n    readonly property string config: StandardPaths.standardLocations(StandardPaths.ConfigLocation)[0]\n    readonly property string state: StandardPaths.standardLocations(StandardPaths.StateLocation)[0]\n    readonly property string cache: StandardPaths.standardLocations(StandardPaths.CacheLocation)[0]\n    readonly property string genericCache: StandardPaths.standardLocations(StandardPaths.GenericCacheLocation)[0]\n    readonly property string documents: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]\n    readonly property string downloads: StandardPaths.standardLocations(StandardPaths.DownloadLocation)[0]\n    readonly property string pictures: StandardPaths.standardLocations(StandardPaths.PicturesLocation)[0]\n    readonly property string music: StandardPaths.standardLocations(StandardPaths.MusicLocation)[0]\n    readonly property string videos: StandardPaths.standardLocations(StandardPaths.MoviesLocation)[0]\n\n    // Other dirs used by the shell, without \"file://\"\n    property string assetsPath: Quickshell.shellPath(\"assets\")\n    property string scriptPath: Quickshell.shellPath(\"scripts\")\n    property string favicons: FileUtils.trimFileProtocol(`${Directories.cache}/media/favicons`)\n    property string coverArt: FileUtils.trimFileProtocol(`${Directories.cache}/media/coverart`)\n    property string tempImages: \"/tmp/quickshell/media/images\"\n    property string booruPreviews: FileUtils.trimFileProtocol(`${Directories.cache}/media/boorus`)\n    property string booruDownloads: FileUtils.trimFileProtocol(Directories.pictures  + \"/homework\")\n    property string booruDownloadsNsfw: FileUtils.trimFileProtocol(Directories.pictures + \"/homework/🌶️\")\n    property string latexOutput: FileUtils.trimFileProtocol(`${Directories.cache}/media/latex`)\n    property string shellConfig: FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse`)\n    property string shellConfigName: \"config.json\"\n    property string shellConfigPath: `${Directories.shellConfig}/${Directories.shellConfigName}`\n\tproperty string todoPath: FileUtils.trimFileProtocol(`${Directories.state}/user/todo.json`)\n\tproperty string notesPath: FileUtils.trimFileProtocol(`${Directories.state}/user/notes.txt`)\n\tproperty string conflictCachePath: FileUtils.trimFileProtocol(`${Directories.cache}/conflict-killer`)\n    property string notificationsPath: FileUtils.trimFileProtocol(`${Directories.cache}/notifications/notifications.json`)\n    property string generatedMaterialThemePath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/colors.json`)\n    property string generatedWallpaperCategoryPath: FileUtils.trimFileProtocol(`${Directories.state}/user/generated/wallpaper/category.txt`)\n    property string cliphistDecode: FileUtils.trimFileProtocol(`/tmp/quickshell/media/cliphist`)\n    property string screenshotTemp: \"/tmp/quickshell/media/screenshot\"\n    property string wallpaperSwitchScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/colors/switchwall.sh`)\n    property string defaultAiPrompts: Quickshell.shellPath(\"defaults/ai/prompts\")\n    property string userAiPrompts: FileUtils.trimFileProtocol(`${Directories.shellConfig}/ai/prompts`)\n    property string userActions: FileUtils.trimFileProtocol(`${Directories.shellConfig}/actions`)\n    property string aiChats: FileUtils.trimFileProtocol(`${Directories.state}/user/ai/chats`)\n    property string aiTranslationScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/ai/gemini-translate.sh`)\n    property string recordScriptPath: FileUtils.trimFileProtocol(`${Directories.scriptPath}/videos/record.sh`)\n    property string userAvatarPathAccountsService: FileUtils.trimFileProtocol(`/var/lib/AccountsService/icons/${SystemInfo.username}`)\n    property string userAvatarPathRicersAndWeirdSystems: FileUtils.trimFileProtocol(`${Directories.home}.face`)\n    property string userAvatarPathRicersAndWeirdSystems2: FileUtils.trimFileProtocol(`${Directories.home}.face.icon`)\n    // Cleanup on init\n    Component.onCompleted: {\n        Quickshell.execDetached([\"mkdir\", \"-p\", `${shellConfig}`])\n        Quickshell.execDetached([\"mkdir\", \"-p\", `${favicons}`])\n        Quickshell.execDetached([\"bash\", \"-c\", `rm -rf '${coverArt}'; mkdir -p '${coverArt}'`])\n        Quickshell.execDetached([\"bash\", \"-c\", `rm -rf '${booruPreviews}'; mkdir -p '${booruPreviews}'`])\n        Quickshell.execDetached([\"bash\", \"-c\", `rm -rf '${latexOutput}'; mkdir -p '${latexOutput}'`])\n        Quickshell.execDetached([\"bash\", \"-c\", `rm -rf '${cliphistDecode}'; mkdir -p '${cliphistDecode}'`])\n        Quickshell.execDetached([\"mkdir\", \"-p\", `${aiChats}`])\n        Quickshell.execDetached([\"mkdir\", \"-p\", `${userActions}`])\n        Quickshell.execDetached([\"rm\", \"-rf\", `${tempImages}`])\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/Icons.qml",
    "content": "pragma Singleton\n\n// From https://github.com/caelestia-dots/shell (GPLv3)\n\nimport Quickshell\nimport qs.services\n\nSingleton {\n    id: root\n\n    function getBatteryIcon(percentage: int): string {\n        if (percentage >= 93) return \"battery_android_full\";\n        if (percentage >= 78) return \"battery_android_6\";\n        if (percentage >= 64) return \"battery_android_5\";\n        if (percentage >= 50) return \"battery_android_4\";\n        if (percentage >= 35) return \"battery_android_3\";\n        if (percentage >= 21) return \"battery_android_2\";\n        if (percentage >= 7) return \"battery_android_1\";\n        return \"battery_android_0\";\n    }\n\n    function getBluetoothDeviceMaterialSymbol(systemIconName: string): string {\n        if (systemIconName.includes(\"headset\") || systemIconName.includes(\"headphones\"))\n            return \"headphones\";\n        if (systemIconName.includes(\"audio\"))\n            return \"speaker\";\n        if (systemIconName.includes(\"phone\"))\n            return \"smartphone\";\n        if (systemIconName.includes(\"mouse\"))\n            return \"mouse\";\n        if (systemIconName.includes(\"keyboard\"))\n            return \"keyboard\";\n        return \"bluetooth\";\n    }\n\n    function getNetworkMaterialSymbol() {\n        if (Network.ethernet) return \"lan\";\n        if (Network.wifiEnabled && Network.wifiStatus === \"connected\") {\n            const strength = Network.active?.strength ?? 0\n            if (strength > 83) return \"signal_wifi_4_bar\";\n            if (strength > 67) return \"network_wifi\";\n            if (strength > 50) return \"network_wifi_3_bar\";\n            if (strength > 33) return \"network_wifi_2_bar\";\n            if (strength > 17) return \"network_wifi_1_bar\";\n            return \"signal_wifi_0_bar\"\n        } else {\n            if (Network.wifiStatus === \"connecting\") return \"signal_wifi_statusbar_not_connected\";\n            if (Network.wifiStatus === \"disconnected\") return \"wifi_find\";\n            if (Network.wifiStatus === \"disabled\") return \"signal_wifi_off\";\n            return \"signal_wifi_bad\";\n        }\n    }\n\n    readonly property var weatherIconMap: ({\n        \"113\": \"clear_day\",\n        \"116\": \"partly_cloudy_day\",\n        \"119\": \"cloud\",\n        \"122\": \"cloud\",\n        \"143\": \"foggy\",\n        \"176\": \"rainy\",\n        \"179\": \"rainy\",\n        \"182\": \"rainy\",\n        \"185\": \"rainy\",\n        \"200\": \"thunderstorm\",\n        \"227\": \"cloudy_snowing\",\n        \"230\": \"snowing_heavy\",\n        \"248\": \"foggy\",\n        \"260\": \"foggy\",\n        \"263\": \"rainy\",\n        \"266\": \"rainy\",\n        \"281\": \"rainy\",\n        \"284\": \"rainy\",\n        \"293\": \"rainy\",\n        \"296\": \"rainy\",\n        \"299\": \"rainy\",\n        \"302\": \"weather_hail\",\n        \"305\": \"rainy\",\n        \"308\": \"weather_hail\",\n        \"311\": \"rainy\",\n        \"314\": \"rainy\",\n        \"317\": \"rainy\",\n        \"320\": \"cloudy_snowing\",\n        \"323\": \"cloudy_snowing\",\n        \"326\": \"cloudy_snowing\",\n        \"329\": \"snowing_heavy\",\n        \"332\": \"snowing_heavy\",\n        \"335\": \"snowing\",\n        \"338\": \"snowing_heavy\",\n        \"350\": \"rainy\",\n        \"353\": \"rainy\",\n        \"356\": \"rainy\",\n        \"359\": \"weather_hail\",\n        \"362\": \"rainy\",\n        \"365\": \"rainy\",\n        \"368\": \"cloudy_snowing\",\n        \"371\": \"snowing\",\n        \"374\": \"rainy\",\n        \"377\": \"rainy\",\n        \"386\": \"thunderstorm\",\n        \"389\": \"thunderstorm\",\n        \"392\": \"thunderstorm\",\n        \"395\": \"snowing\"\n    })\n\n    \n    function getWeatherIcon(code) {\n        const key = String(code)\n        if (weatherIconMap.hasOwnProperty(key)) {\n            return weatherIconMap[key]\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/Images.qml",
    "content": "pragma Singleton\n\nimport Quickshell\n\nSingleton {\n    // Formats\n    readonly property list<string> validImageTypes: [\"jpeg\", \"png\", \"webp\", \"tiff\", \"svg\"]\n    readonly property list<string> validImageExtensions: [\"jpg\", \"jpeg\", \"png\", \"webp\", \"tif\", \"tiff\", \"svg\"]\n\n    function isValidImageByName(name: string): bool {\n        return validImageExtensions.some(t => name.endsWith(`.${t}`));\n    }\n\n    // Thumbnails\n    // https://specifications.freedesktop.org/thumbnail-spec/latest/directory.html\n    readonly property var thumbnailSizes: ({\n        \"normal\": 128,\n        \"large\": 256,\n        \"x-large\": 512,\n        \"xx-large\": 1024\n    })\n    function thumbnailSizeNameForDimensions(width: int, height: int): string {\n        const sizeNames = Object.keys(thumbnailSizes);\n        for(let i = 0; i < sizeNames.length; i++) {\n            const sizeName = sizeNames[i];\n            const maxSize = thumbnailSizes[sizeName];\n            if (width <= maxSize && height <= maxSize) return sizeName;\n        }\n        return \"xx-large\";\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/Persistent.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nSingleton {\n    id: root\n    property alias states: persistentStatesJsonAdapter\n    property string fileDir: Directories.state\n    property string fileName: \"states.json\"\n    property string filePath: `${root.fileDir}/${root.fileName}`\n\n    property bool ready: false\n    property string previousHyprlandInstanceSignature: \"\"\n    property bool isNewHyprlandInstance: previousHyprlandInstanceSignature !== states.hyprlandInstanceSignature\n\n    onReadyChanged: {\n        root.previousHyprlandInstanceSignature = root.states.hyprlandInstanceSignature\n        root.states.hyprlandInstanceSignature = Quickshell.env(\"HYPRLAND_INSTANCE_SIGNATURE\") || \"\"\n    }\n\n    Timer {\n        id: fileReloadTimer\n        interval: 100\n        repeat: false\n        onTriggered: {\n            persistentStatesFileView.reload()\n        }\n    }\n\n    Timer {\n        id: fileWriteTimer\n        interval: 100\n        repeat: false\n        onTriggered: {\n            persistentStatesFileView.writeAdapter()\n        }\n    }\n\n    FileView {\n        id: persistentStatesFileView\n        path: root.filePath\n\n        watchChanges: true\n        onFileChanged: fileReloadTimer.restart()\n        onAdapterUpdated: fileWriteTimer.restart()\n        onLoaded: root.ready = true\n        onLoadFailed: error => {\n            console.log(\"Failed to load persistent states file:\", error);\n            if (error == FileViewError.FileNotFound) {\n                fileWriteTimer.restart();\n            }\n        }\n\n        adapter: JsonAdapter {\n            id: persistentStatesJsonAdapter\n\n            property string hyprlandInstanceSignature: \"\"\n\n            property JsonObject ai: JsonObject {\n                property string model: \"gemini-2.5-flash\"\n                property real temperature: 0.5\n            }\n\n            property JsonObject cheatsheet: JsonObject {\n                property int tabIndex: 0\n            }\n\n            property JsonObject sidebar: JsonObject {\n                property JsonObject bottomGroup: JsonObject {\n                    property bool collapsed: false\n                    property int tab: 0\n                }\n            }\n\n            property JsonObject booru: JsonObject {\n                property bool allowNsfw: false\n                property string provider: \"yandere\"\n            }\n\n            property JsonObject idle: JsonObject {\n                property bool inhibit: false\n            }\n\n            property JsonObject overlay: JsonObject {\n                property list<string> open: [\"crosshair\", \"recorder\", \"volumeMixer\", \"resources\"]\n                property JsonObject crosshair: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: true\n                    property real x: 827\n                    property real y: 441\n                    property real width: 250\n                    property real height: 100\n                }\n                property JsonObject floatingImage: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: false\n                    property real x: 1650\n                    property real y: 390\n                    property real width: 0\n                    property real height: 0\n                }\n                property JsonObject fpsLimiter: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: false\n                    property real x: 1570\n                    property real y: 615\n                    property real width: 280\n                    property real height: 80\n                }\n                property JsonObject recorder: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: false\n                    property real x: 80\n                    property real y: 80\n                    property real width: 350\n                    property real height: 130\n                }\n                property JsonObject resources: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: true\n                    property real x: 1500\n                    property real y: 770\n                    property real width: 350\n                    property real height: 200\n                    property int tabIndex: 0\n                }\n                property JsonObject volumeMixer: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: false\n                    property real x: 80\n                    property real y: 280\n                    property real width: 350\n                    property real height: 600\n                    property int tabIndex: 0\n                }\n                property JsonObject notes: JsonObject {\n                    property bool pinned: false\n                    property bool clickthrough: true\n                    property real x: 1400\n                    property real y: 42\n                    property real width: 460\n                    property real height: 330\n                }\n            }\n\n            property JsonObject timer: JsonObject {\n                property JsonObject pomodoro: JsonObject {\n                    property bool running: false\n                    property int start: 0\n                    property bool isBreak: false\n                    property int cycle: 0\n                }\n                property JsonObject stopwatch: JsonObject {\n                    property bool running: false\n                    property int start: 0\n                    property list<var> laps: []\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/ColorUtils.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n\n    /**\n     * Returns a color with the hue of color2 and the saturation, value, and alpha of color1.\n     *\n     * @param {string} color1 - The base color (any Qt.color-compatible string).\n     * @param {string} color2 - The color to take hue from.\n     * @returns {Qt.rgba} The resulting color.\n     */\n    function colorWithHueOf(color1, color2) {\n        var c1 = Qt.color(color1);\n        var c2 = Qt.color(color2);\n\n        // Qt.color hsvHue/hsvSaturation/hsvValue/alpha return 0-1\n        var hue = c2.hsvHue;\n        var sat = c1.hsvSaturation;\n        var val = c1.hsvValue;\n        var alpha = c1.a;\n\n        return Qt.hsva(hue, sat, val, alpha);\n    }\n\n    /**\n     * Returns a color with the saturation of color2 and the hue/value/alpha of color1.\n     *\n     * @param {string} color1 - The base color (any Qt.color-compatible string).\n     * @param {string} color2 - The color to take saturation from.\n     * @returns {Qt.rgba} The resulting color.\n     */\n    function colorWithSaturationOf(color1, color2) {\n        var c1 = Qt.color(color1);\n        var c2 = Qt.color(color2);\n\n        var hue = c1.hsvHue;\n        var sat = c2.hsvSaturation;\n        var val = c1.hsvValue;\n        var alpha = c1.a;\n\n        return Qt.hsva(hue, sat, val, alpha);\n    }\n\n    /**\n     * Returns a color with the given lightness and the hue, saturation, and alpha of the input color (using HSL).\n     *\n     * @param {string} color - The base color (any Qt.color-compatible string).\n     * @param {number} lightness - The lightness value to use (0-1).\n     * @returns {Qt.rgba} The resulting color.\n     */\n    function colorWithLightness(color, lightness) {\n        var c = Qt.color(color);\n        return Qt.hsla(c.hslHue, c.hslSaturation, lightness, c.a);\n    }\n\n    /**\n     * Returns a color with the lightness of color2 and the hue, saturation, and alpha of color1 (using HSL).\n     *\n     * @param {string} color1 - The base color (any Qt.color-compatible string).\n     * @param {string} color2 - The color to take lightness from.\n     * @returns {Qt.rgba} The resulting color.\n     */\n    function colorWithLightnessOf(color1, color2) {\n        var c2 = Qt.color(color2);\n        return colorWithLightness(color1, c2.hslLightness);\n    }\n\n    /**\n     * Adapts color1 to the accent (hue and saturation) of color2 using HSL, keeping lightness and alpha from color1.\n     *\n     * @param {string} color1 - The base color (any Qt.color-compatible string).\n     * @param {string} color2 - The accent color.\n     * @returns {Qt.rgba} The resulting color.\n     */\n    function adaptToAccent(color1, color2) {\n        var c1 = Qt.color(color1);\n        var c2 = Qt.color(color2);\n\n        var hue = c2.hslHue;\n        var sat = c2.hslSaturation;\n        var light = c1.hslLightness;\n        var alpha = c1.a;\n\n        return Qt.hsla(hue, sat, light, alpha);\n    }\n\n    /**\n     * Mixes two colors by a given percentage.\n     *\n     * @param {string} color1 - The first color (any Qt.color-compatible string).\n     * @param {string} color2 - The second color.\n     * @param {number} percentage - The mix ratio (0-1). 1 = all color1, 0 = all color2.\n     * @returns {Qt.rgba} The resulting mixed color.\n     */\n    function mix(color1, color2, percentage = 0.5) {\n        var c1 = Qt.color(color1);\n        var c2 = Qt.color(color2);\n        return Qt.rgba(percentage * c1.r + (1 - percentage) * c2.r, percentage * c1.g + (1 - percentage) * c2.g, percentage * c1.b + (1 - percentage) * c2.b, percentage * c1.a + (1 - percentage) * c2.a);\n    }\n\n    /**\n     * Transparentizes a color by a given percentage.\n     *\n     * @param {string} color - The color (any Qt.color-compatible string).\n     * @param {number} percentage - The amount to transparentize (0-1).\n     * @returns {Qt.rgba} The resulting color.\n     */\n    function transparentize(color, percentage = 1) {\n        var c = Qt.color(color);\n        return Qt.rgba(c.r, c.g, c.b, c.a * (1 - percentage));\n    }\n\n    /**\n     * Sets the alpha channel of a color.\n     *\n     * @param {string} color - The base color (any Qt.color-compatible string).\n     * @param {number} alpha - The desired alpha (0-1).\n     * @returns {Qt.rgba} The resulting color with applied alpha.\n     */\n    function applyAlpha(color, alpha) {\n        var c = Qt.color(color);\n        var a = Math.max(0, Math.min(1, alpha));\n        return Qt.rgba(c.r, c.g, c.b, a);\n    }\n\n    /**\n     * Returns true if the color is considered \"dark\" (hslLightness < 0.5).\n     *\n     * @param {string} color - The color to check (any Qt.color-compatible string).\n     * @returns {boolean} True if dark, false otherwise.\n     */\n    function isDark(color) {\n        var c = Qt.color(color);\n        return c.hslLightness < 0.5;\n    }\n\n    /**\n     * Clamps a value to the inclusive range [0, 1].\n     *\n     * @param {number} x - The value to clamp.\n     * @returns {number} The clamped value in the range [0, 1].\n     */\n    function clamp01(x) {\n        return Math.min(1, Math.max(0, x));\n    }\n\n    /**\n     * Solves for the solid overlay color that, when composited over a base color\n     * with a given opacity, yields the target color.\n     *\n     * The compositing equation is:\n     *   result = overlay * overlayOpacity + base * (1 - overlayOpacity)\n     *\n     * This function algebraically inverts that equation per channel.\n     *\n     * @param {Qt.rgba} baseColor - The base (background) color.\n     * @param {Qt.rgba} targetColor - The resulting color after compositing.\n     * @param {number} overlayOpacity - The overlay opacity (0-1).\n     * @returns {Qt.rgba} The solved overlay color\n     */\n    function solveOverlayColor(baseColor, targetColor, overlayOpacity) {\n        const bc = Qt.color(baseColor);\n        const tc = Qt.color(targetColor);\n        let invA = 1.0 - overlayOpacity;\n\n        let r = (tc.r - bc.r * invA) / overlayOpacity;\n        let g = (tc.g - bc.g * invA) / overlayOpacity;\n        let b = (tc.b - bc.b * invA) / overlayOpacity;\n\n        return Qt.rgba(clamp01(r), clamp01(g), clamp01(b), overlayOpacity);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/DateUtils.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n\n    function getFirstDayOfWeek(date, firstDay = 1) {\n        const d = new Date(date); // Copy\n        const day = d.getDay();   // 0 = Sunday, 1 = Monday, ..., 6 = Saturday\n\n        // Calculate difference to firstDay\n        const diff = (day - firstDay + 7) % 7;\n        d.setDate(d.getDate() - diff);\n        return d;\n    }\n\n    function sameDate(d1, d2) {\n        return (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate());\n    }\n\n    function getIthDayDateOfSameWeek(date, i, firstDay = 1) {\n        const firstDayDate = root.getFirstDayOfWeek(date, firstDay);\n        const targetDate = new Date(firstDayDate);\n        targetDate.setDate(firstDayDate.getDate() + i);\n        return targetDate;\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/FileUtils.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n\n    /**\n     * Trims the File protocol off the input string\n     * @param {string} str\n     * @returns {string}\n     */\n    function trimFileProtocol(str) {\n        let s = str;\n        if (typeof s !== \"string\") s = str.toString(); // Convert to string if it's an url or whatever\n        return s.startsWith(\"file://\") ? s.slice(7) : s;\n    }\n\n    /**\n     * Extracts the file name from a file path\n     * @param {string} str\n     * @returns {string}\n     */\n    function fileNameForPath(str) {\n        if (typeof str !== \"string\") return \"\";\n        const trimmed = trimFileProtocol(str);\n        return trimmed.split(/[\\\\/]/).pop();\n    }\n\n    /**\n     * Extracts the folder name from a directory path\n     * @param {string} str\n     * @returns {string}\n     */\n    function folderNameForPath(str) {\n        if (typeof str !== \"string\") return \"\";\n        const trimmed = trimFileProtocol(str);\n        // Remove trailing slash if present\n        const noTrailing = trimmed.endsWith(\"/\") ? trimmed.slice(0, -1) : trimmed;\n        if (!noTrailing) return \"\";\n        return noTrailing.split(/[\\\\/]/).pop();\n    }\n\n    /**\n     * Removes the file extension from a file path or name\n     * @param {string} str\n     * @returns {string}\n     */\n    function trimFileExt(str) {\n        if (typeof str !== \"string\") return \"\";\n        const trimmed = trimFileProtocol(str);\n        const lastDot = trimmed.lastIndexOf(\".\");\n        if (lastDot > -1 && lastDot > trimmed.lastIndexOf(\"/\")) {\n            return trimmed.slice(0, lastDot);\n        }\n        return trimmed;\n    }\n\n    /**\n     * Returns the parent directory of a given file path\n     * @param {string} str\n     * @returns {string}\n     */\n    function parentDirectory(str) {\n        if (typeof str !== \"string\") return \"\";\n        const trimmed = trimFileProtocol(str);\n        const parts = trimmed.split(/[\\\\/]/);\n        if (parts.length <= 1) return \"\";\n        parts.pop();\n        return parts.join(\"/\");\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/Fuzzy.qml",
    "content": "pragma Singleton\nimport Quickshell\nimport \"fuzzysort.js\" as FuzzySort\n\n/**\n * Wrapper for FuzzySort to play nicely with Quickshell's imports\n */\n\nSingleton {\n    function go(...args) {\n        return FuzzySort.go(...args)\n    }\n\n    function prepare(...args) {\n        return FuzzySort.prepare(...args)\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/Levendist.qml",
    "content": "pragma Singleton\nimport Quickshell\nimport \"levendist.js\" as Levendist\n\n/**\n * Wrapper for levendist.js to play nicely with Quickshell's imports\n */\n\nSingleton {\n    function computeScore(...args) {\n        return Levendist.computeScore(...args)\n    }\n\n    function computeTextMatchScore(...args) {\n        return Levendist.computeTextMatchScore(...args)\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/NotificationUtils.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n    /**\n     * @param { string } summary \n     * @returns { string }\n     */\n    function findSuitableMaterialSymbol(summary = \"\") {\n        const defaultType = 'chat';\n        if (summary.length === 0) return defaultType;\n\n        const keywordsToTypes = {\n            'reboot': 'restart_alt',\n            'record': 'screen_record',\n            'battery': 'power',\n            'power': 'power',\n            'screenshot': 'screenshot_monitor',\n            'welcome': 'waving_hand',\n            'time': 'scheduleb',\n            'installed': 'download',\n            'configuration reloaded': 'reset_wrench',\n            'unable': 'question_mark',\n            \"couldn't\": 'question_mark',\n            'config': 'reset_wrench',\n            'update': 'update',\n            'ai response': 'neurology',\n            'control': 'settings',\n            'upsca': 'compare',\n            'music': 'queue_music',\n            'install': 'deployed_code_update',\n            'input': 'keyboard_alt',\n            'preedit': 'keyboard_alt',\n            'startswith:file': 'folder_copy', // Declarative startsWith check\n        };\n\n        const lowerSummary = summary.toLowerCase();\n\n        for (const [keyword, type] of Object.entries(keywordsToTypes)) {\n            if (keyword.startsWith('startswith:')) {\n                const startsWithKeyword = keyword.replace('startswith:', '');\n                if (lowerSummary.startsWith(startsWithKeyword)) {\n                    return type;\n                }\n            } else if (lowerSummary.includes(keyword)) {\n                return type;\n            }\n        }\n\n        return defaultType;\n    }\n\n    /**\n     * @param { number | string | Date } timestamp \n     * @returns { string }\n     */\n    function getFriendlyNotifTimeString(timestamp) {\n        if (!timestamp) return '';\n        const messageTime = new Date(timestamp);\n        const now = new Date();\n        const diffMs = now.getTime() - messageTime.getTime();\n\n        // Less than 1 minute\n        if (diffMs < 60000)\n            return 'Now';\n\n        // Same day - show relative time\n        if (messageTime.toDateString() === now.toDateString()) {\n            const diffMinutes = Math.floor(diffMs / 60000);\n            const diffHours = Math.floor(diffMs / 3600000);\n\n            if (diffHours > 0) {\n                return `${diffHours}h`;\n            } else {\n                return `${diffMinutes}m`;\n            }\n        }\n\n        // Yesterday\n        if (messageTime.toDateString() === new Date(now.getTime() - 86400000).toDateString())\n            return 'Yesterday';\n\n        // Older dates\n        return Qt.formatDateTime(messageTime, \"MMMM dd\");\n    }\n\n    function processNotificationBody(body, appName) {\n        let processedBody = body\n        \n        // Clean Chromium-based browsers notifications - remove first line\n        if (appName) {\n            const lowerApp = appName.toLowerCase()\n            const chromiumBrowsers = [\n                \"brave\", \"chrome\", \"chromium\", \"vivaldi\", \"opera\", \"microsoft edge\"\n            ]\n\n            if (chromiumBrowsers.some(name => lowerApp.includes(name))) {\n                const lines = body.split('\\n\\n')\n\n                if (lines.length > 1 && lines[0].startsWith('<a')) {\n                    processedBody = lines.slice(1).join('\\n\\n')\n                }\n            }\n        }\n\n        processedBody = processedBody.replace(/<img/gi, '\\n\\n<img');\n        \n        return processedBody\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/ObjectUtils.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n\n    function toPlainObject(qtObj) {\n        if (qtObj === null || typeof qtObj !== \"object\") return qtObj;\n\n        // Handle true arrays\n        if (Array.isArray(qtObj)) {\n            return qtObj.map(item => toPlainObject(item));\n        }\n\n        // Handle array-like Qt objects (e.g., have length and numeric keys)\n        if (\n            typeof qtObj.length === \"number\" &&\n            qtObj.length > 0 &&\n            Object.keys(qtObj).every(\n                key => !isNaN(key) || key === \"length\"\n            )\n        ) {\n            let arr = [];\n            for (let i = 0; i < qtObj.length; i++) {\n                arr.push(toPlainObject(qtObj[i]));\n            }\n            return arr;\n        }\n\n        const result = ({});\n        for (let key in qtObj) {\n            if (\n                typeof qtObj[key] !== \"function\" &&\n                !key.startsWith(\"objectName\") &&\n                !key.startsWith(\"children\") &&\n                !key.startsWith(\"object\") &&\n                !key.startsWith(\"parent\") &&\n                !key.startsWith(\"metaObject\") &&\n                !key.startsWith(\"destroyed\") &&\n                !key.startsWith(\"reloadableId\")\n            ) {\n                result[key] = toPlainObject(qtObj[key]);\n            }\n        }\n        // console.log(JSON.stringify(result))\n        return result;\n    }\n\n    function applyToQtObject(qtObj, jsonObj) {\n        // console.log(\"applyToQtObject\", JSON.stringify(qtObj, null, 2), \"<<\", JSON.stringify(jsonObj, null, 2));\n        if (!qtObj || typeof jsonObj !== \"object\" || jsonObj === null) return;\n\n        // Detect array-like Qt objects\n        const isQtArrayLike = obj => {\n            return obj && typeof obj === \"object\" &&\n                typeof obj.length === \"number\" &&\n                obj.length > 0 &&\n                Object.keys(obj).every(key => !isNaN(key) || key === \"length\");\n        };\n\n        // If both are arrays or array-like, update in place or replace\n        if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {\n            qtObj.length = 0;\n            for (let i = 0; i < jsonObj.length; i++) {\n                qtObj.push(jsonObj[i]);\n            }\n            return;\n        }\n\n        // If target is array or array-like but source is not, clear\n        if ((Array.isArray(qtObj) || isQtArrayLike(qtObj)) && !Array.isArray(jsonObj)) {\n            qtObj.length = 0;\n            return;\n        }\n\n        // If source is array but target is not, assign directly if possible\n        if (!(Array.isArray(qtObj) || isQtArrayLike(qtObj)) && Array.isArray(jsonObj)) {\n            return jsonObj;\n        }\n\n        for (let key in jsonObj) {\n            if (!qtObj.hasOwnProperty(key)) continue;\n            const value = qtObj[key];\n            const jsonValue = jsonObj[key];\n            // console.log(\"applying to qt obj key:\", value, \"jsonValue:\", jsonValue);\n            if ((Array.isArray(value) || isQtArrayLike(value)) && Array.isArray(jsonValue)) {\n                value.length = 0;\n                for (let i = 0; i < jsonValue.length; i++) {\n                    value.push(jsonValue[i]);\n                }\n            } else if (value && typeof value === \"object\" && !Array.isArray(value) && !isQtArrayLike(value)) {\n                applyToQtObject(value, jsonValue);\n            } else {\n                qtObj[key] = jsonValue;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/Session.qml",
    "content": "pragma Singleton\nimport Quickshell\nimport qs.services\nimport qs.modules.common\n\nSingleton {\n    id: root\n\n    function closeAllWindows() {\n        HyprlandData.windowList.map(w => w.pid).forEach(pid => {\n            Quickshell.execDetached([\"kill\", pid]);\n        });\n    }\n\n    function changePassword() {\n        Quickshell.execDetached([\"bash\", \"-c\", `${Config.options.apps.changePassword}`]);\n    }\n\n    function lock() {\n        Quickshell.execDetached([\"loginctl\", \"lock-session\"]);\n    }\n\n    function suspend() {\n        Quickshell.execDetached([\"bash\", \"-c\", \"systemctl suspend || loginctl suspend\"]);\n    }\n\n    function logout() {\n        closeAllWindows();\n        Quickshell.execDetached([\"pkill\", \"-i\", \"Hyprland\"]);\n    }\n\n    function launchTaskManager() {\n        Quickshell.execDetached([\"bash\", \"-c\", `${Config.options.apps.taskManager}`]);\n    }\n\n    function hibernate() {\n        Quickshell.execDetached([\"bash\", \"-c\", `systemctl hibernate || loginctl hibernate`]);\n    }\n\n    function poweroff() {\n        closeAllWindows();\n        Quickshell.execDetached([\"bash\", \"-c\", `systemctl poweroff || loginctl poweroff`]);\n    }\n\n    function reboot() {\n        closeAllWindows();\n        Quickshell.execDetached([\"bash\", \"-c\", `reboot || loginctl reboot`]);\n    }\n\n    function rebootToFirmware() {\n        closeAllWindows();\n        Quickshell.execDetached([\"bash\", \"-c\", `systemctl reboot --firmware-setup || loginctl reboot --firmware-setup`]);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/StringUtils.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n\n    /**\n     * Formats a string according to the args that are passed inc\n     * @param { string } str\n     * @param  {...any} args\n     * @returns { string }\n     */\n    function format(str, ...args) {\n        return str.replace(/{(\\d+)}/g, (match, index) => typeof args[index] !== 'undefined' ? args[index] : match);\n    }\n\n    /**\n     * Returns the domain of the passed in url or null\n     * @param { string } url\n     * @returns { string| null }\n     */\n    function getDomain(url) {\n        const match = url.match(/^(?:https?:\\/\\/)?(?:www\\.)?([^\\/]+)/);\n        return match ? match[1] : null;\n    }\n\n    /**\n     * Returns the base url of the passed in url or null\n     * @param { string } url\n     * @returns { string | null }\n     */\n    function getBaseUrl(url) {\n        const match = url.match(/^(https?:\\/\\/[^\\/]+)(\\/.*)?$/);\n        return match ? match[1] : null;\n    }\n\n    /**\n     * Escapes single quotes in shell commands\n     * @param { string } str\n     * @returns { string }\n     */\n    function shellSingleQuoteEscape(str) {\n        return String(str)\n        // .replace(/\\\\/g, '\\\\\\\\')\n        .replace(/'/g, \"'\\\\''\");\n    }\n\n    /**\n     * Splits markdown blocks into three different types: text, think, and code.\n     * @param { string } markdown\n     * @returns {Array<{type: \"text\" | \"think\" | \"code\", content: string, lang?: string, completed?: boolean}>}\n     */\n    function splitMarkdownBlocks(markdown) {\n        const regex = /```(\\w+)?\\n([\\s\\S]*?)```|<think>([\\s\\S]*?)<\\/think>/g;\n        /**\n         * @type {{type: \"text\" | \"think\" | \"code\"; content: string; lang: string | undefined; completed: boolean | undefined}[]}\n         */\n        let result = [];\n        let lastIndex = 0;\n        let match;\n        while ((match = regex.exec(markdown)) !== null) {\n            if (match.index > lastIndex) {\n                const text = markdown.slice(lastIndex, match.index);\n                if (text.trim()) {\n                    result.push({\n                        type: \"text\",\n                        content: text\n                    });\n                }\n            }\n            if (match[0].startsWith('```')) {\n                if (match[2] && match[2].trim()) {\n                    result.push({\n                        type: \"code\",\n                        lang: match[1] || \"\",\n                        content: match[2],\n                        completed: true\n                    });\n                }\n            } else if (match[0].startsWith('<think>')) {\n                if (match[3] && match[3].trim()) {\n                    result.push({\n                        type: \"think\",\n                        content: match[3],\n                        completed: true\n                    });\n                }\n            }\n            lastIndex = regex.lastIndex;\n        }\n        // Handle any remaining text after the last match\n        if (lastIndex < markdown.length) {\n            const text = markdown.slice(lastIndex);\n            // Check for unfinished <think> block\n            const thinkStart = text.indexOf('<think>');\n            const codeStart = text.indexOf('```');\n            if (thinkStart !== -1 && (codeStart === -1 || thinkStart < codeStart)) {\n                const beforeThink = text.slice(0, thinkStart);\n                if (beforeThink.trim()) {\n                    result.push({\n                        type: \"text\",\n                        content: beforeThink\n                    });\n                }\n                const thinkContent = text.slice(thinkStart + 7);\n                if (thinkContent.trim()) {\n                    result.push({\n                        type: \"think\",\n                        content: thinkContent,\n                        completed: false\n                    });\n                }\n            } else if (codeStart !== -1) {\n                const beforeCode = text.slice(0, codeStart);\n                if (beforeCode.trim()) {\n                    result.push({\n                        type: \"text\",\n                        content: beforeCode\n                    });\n                }\n                // Try to detect language after ```\n                const codeLangMatch = text.slice(codeStart + 3).match(/^(\\w+)?\\n/);\n                let lang = \"\";\n                let codeContentStart = codeStart + 3;\n                if (codeLangMatch) {\n                    lang = codeLangMatch[1] || \"\";\n                    codeContentStart += codeLangMatch[0].length;\n                } else if (text[codeStart + 3] === '\\n') {\n                    codeContentStart += 1;\n                }\n                const codeContent = text.slice(codeContentStart);\n                if (codeContent.trim()) {\n                    result.push({\n                        type: \"code\",\n                        lang,\n                        content: codeContent,\n                        completed: false\n                    });\n                }\n            } else if (text.trim()) {\n                result.push({\n                    type: \"text\",\n                    content: text\n                });\n            }\n        }\n        // console.log(JSON.stringify(result, null, 2));\n        return result;\n    }\n\n    /**\n     * Returns the original string with backslashes escaped\n     * @param { string } str\n     * @returns { string }\n     */\n    function escapeBackslashes(str) {\n        return str.replace(/\\\\/g, '\\\\\\\\');\n    }\n\n    /**\n     * Wraps words to supplied maximum length\n     * @param { string | null } str\n     * @param { number } maxLen\n     * @returns { string }\n     */\n    function wordWrap(str, maxLen) {\n        if (!str)\n            return \"\";\n        let words = str.split(\" \");\n        let lines = [];\n        let current = \"\";\n        for (let i = 0; i < words.length; ++i) {\n            if ((current + (current.length > 0 ? \" \" : \"\") + words[i]).length > maxLen) {\n                if (current.length > 0)\n                    lines.push(current);\n                current = words[i];\n            } else {\n                current += (current.length > 0 ? \" \" : \"\") + words[i];\n            }\n        }\n        if (current.length > 0)\n            lines.push(current);\n        return lines.join(\"\\n\");\n    }\n\n    /**\n     * Cleans up a music title by removing bracketed and special characters.\n     * @param { string } title\n     * @returns { string }\n     */\n    function cleanMusicTitle(title) {\n        if (!title)\n            return \"\";\n        // Brackets\n        title = title.replace(/^ *\\([^)]*\\) */g, \" \"); // Round brackets\n        title = title.replace(/^ *\\[[^\\]]*\\] */g, \" \"); // Square brackets\n        title = title.replace(/^ *\\{[^\\}]*\\} */g, \" \"); // Curly brackets\n        // Japenis brackets\n        title = title.replace(/^ *【[^】]*】/, \"\"); // Touhou\n        title = title.replace(/^ *《[^》]*》/, \"\"); // ??\n        title = title.replace(/^ *「[^」]*」/, \"\"); // OP/ED thingie\n        title = title.replace(/^ *『[^』]*』/, \"\"); // OP/ED thingie\n\n        return title.trim();\n    }\n\n    /**\n     * Converts seconds to a friendly time string (e.g. 1:23 or 1:02:03).\n     * @param { number } seconds\n     * @returns { string }\n     */\n    function friendlyTimeForSeconds(seconds) {\n        if (isNaN(seconds) || seconds < 0)\n            return \"0:00\";\n        seconds = Math.floor(seconds);\n        const h = Math.floor(seconds / 3600);\n        const m = Math.floor((seconds % 3600) / 60);\n        const s = seconds % 60;\n        if (h > 0) {\n            return `${h}:${m.toString().padStart(2, '0')}:${s.toString().padStart(2, '0')}`;\n        } else {\n            return `${m}:${s.toString().padStart(2, '0')}`;\n        }\n    }\n\n    /**\n     * Escapes HTML special characters in a string.\n     * @param { string } str\n     * @returns { string }\n     */\n    function escapeHtml(str) {\n        if (typeof str !== 'string')\n            return str;\n        return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;').replace(/'/g, '&#39;');\n    }\n\n    /**\n     * Cleans a cliphist entry by removing leading digits and tab.\n     * @param { string } str\n     * @returns { string }\n     */\n    function cleanCliphistEntry(str: string): string {\n        return str.replace(/^\\d+\\t/, \"\");\n    }\n\n    /**\n     * Checks if any substring in the list is contained in the string.\n     * @param { string } str\n     * @param { string[] } substrings\n     * @returns { boolean }\n     */\n    function stringListContainsSubstring(str, substrings) {\n        for (let i = 0; i < substrings.length; ++i) {\n            if (str.includes(substrings[i])) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**\n     * Removes the given prefix from the string if present.\n     * @param { string } str\n     * @param { string } prefix\n     * @returns { string }\n     */\n    function cleanPrefix(str, prefix) {\n        if (str.startsWith(prefix)) {\n            return str.slice(prefix.length);\n        }\n        return str;\n    }\n\n    /**\n     * Removes the first matching prefix from the string if present.\n     * @param { string } str\n     * @param { string[] } prefixes\n     * @returns { string }\n     */\n    function cleanOnePrefix(str, prefixes) {\n        for (let i = 0; i < prefixes.length; ++i) {\n            if (str.startsWith(prefixes[i])) {\n                return str.slice(prefixes[i].length);\n            }\n        }\n        return str;\n    }\n\n    function toTitleCase(str) {\n        // Replace \"-\" and \"_\" with space, then capitalize each word\n        return str.replace(/[-_]/g, \" \").replace(\n            /\\w\\S*/g,\n            function(txt) {\n            return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();\n            }\n        );\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/fuzzysort.js",
    "content": ".pragma library\n\n// https://github.com/farzher/fuzzysort\n// License: MIT | Copyright (c) 2018 Stephen Kamenar\n// A copy of the license is available in the `licenses` folder of this repository\n\nvar single =  (search, target) => {\n    if(!search || !target) return NULL\n\n    var preparedSearch = getPreparedSearch(search)\n    if(!isPrepared(target)) target = getPrepared(target)\n\n    var searchBitflags = preparedSearch.bitflags\n    if((searchBitflags & target._bitflags) !== searchBitflags) return NULL\n\n    return algorithm(preparedSearch, target)\n}\n\nvar go = (search, targets, options) => {\n    if(!search) return options?.all ? all(targets, options) : noResults\n\n    var preparedSearch = getPreparedSearch(search)\n    var searchBitflags = preparedSearch.bitflags\n    var containsSpace  = preparedSearch.containsSpace\n\n    var threshold = denormalizeScore( options?.threshold || 0 )\n    var limit     = options?.limit || INFINITY\n\n    var resultsLen = 0; var limitedCount = 0\n    var targetsLen = targets.length\n\n    function push_result(result) {\n    if(resultsLen < limit) { q.add(result); ++resultsLen }\n    else {\n        ++limitedCount\n        if(result._score > q.peek()._score) q.replaceTop(result)\n    }\n    }\n\n    // This code is copy/pasted 3 times for performance reasons [options.key, options.keys, no keys]\n\n    // options.key\n    if(options?.key) {\n    var key = options.key\n    for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]\n        var target = getValue(obj, key)\n        if(!target) continue\n        if(!isPrepared(target)) target = getPrepared(target)\n\n        if((searchBitflags & target._bitflags) !== searchBitflags) continue\n        var result = algorithm(preparedSearch, target)\n        if(result === NULL) continue\n        if(result._score < threshold) continue\n\n        result.obj = obj\n        push_result(result)\n    }\n\n    // options.keys\n    } else if(options?.keys) {\n    var keys = options.keys\n    var keysLen = keys.length\n\n    outer: for(var i = 0; i < targetsLen; ++i) { var obj = targets[i]\n\n        { // early out based on bitflags\n        var keysBitflags = 0\n        for (var keyI = 0; keyI < keysLen; ++keyI) {\n            var key = keys[keyI]\n            var target = getValue(obj, key)\n            if(!target) { tmpTargets[keyI] = noTarget; continue }\n            if(!isPrepared(target)) target = getPrepared(target)\n            tmpTargets[keyI] = target\n\n            keysBitflags |= target._bitflags\n        }\n\n        if((searchBitflags & keysBitflags) !== searchBitflags) continue\n        }\n\n        if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) keysSpacesBestScores[i] = NEGATIVE_INFINITY\n\n        for (var keyI = 0; keyI < keysLen; ++keyI) {\n        target = tmpTargets[keyI]\n        if(target === noTarget) { tmpResults[keyI] = noTarget; continue }\n\n        tmpResults[keyI] = algorithm(preparedSearch, target, /*allowSpaces=*/false, /*allowPartialMatch=*/containsSpace)\n        if(tmpResults[keyI] === NULL) { tmpResults[keyI] = noTarget; continue }\n\n        // todo: this seems weird and wrong. like what if our first match wasn't good. this should just replace it instead of averaging with it\n        // if our second match isn't good we ignore it instead of averaging with it\n        if(containsSpace) for(let i=0; i<preparedSearch.spaceSearches.length; i++) {\n            if(allowPartialMatchScores[i] > -1000) {\n            if(keysSpacesBestScores[i] > NEGATIVE_INFINITY) {\n                var tmp = (keysSpacesBestScores[i] + allowPartialMatchScores[i]) / 4/*bonus score for having multiple matches*/\n                if(tmp > keysSpacesBestScores[i]) keysSpacesBestScores[i] = tmp\n            }\n            }\n            if(allowPartialMatchScores[i] > keysSpacesBestScores[i]) keysSpacesBestScores[i] = allowPartialMatchScores[i]\n        }\n        }\n\n        if(containsSpace) {\n        for(let i=0; i<preparedSearch.spaceSearches.length; i++) { if(keysSpacesBestScores[i] === NEGATIVE_INFINITY) continue outer }\n        } else {\n        var hasAtLeast1Match = false\n        for(let i=0; i < keysLen; i++) { if(tmpResults[i]._score !== NEGATIVE_INFINITY) { hasAtLeast1Match = true; break } }\n        if(!hasAtLeast1Match) continue\n        }\n\n        var objResults = new KeysResult(keysLen)\n        for(let i=0; i < keysLen; i++) { objResults[i] = tmpResults[i] }\n\n        if(containsSpace) {\n        var score = 0\n        for(let i=0; i<preparedSearch.spaceSearches.length; i++) score += keysSpacesBestScores[i]\n        } else {\n        // todo could rewrite this scoring to be more similar to when there's spaces\n        // if we match multiple keys give us bonus points\n        var score = NEGATIVE_INFINITY\n        for(let i=0; i<keysLen; i++) {\n            var result = objResults[i]\n            if(result._score > -1000) {\n            if(score > NEGATIVE_INFINITY) {\n                var tmp = (score + result._score) / 4/*bonus score for having multiple matches*/\n                if(tmp > score) score = tmp\n            }\n            }\n            if(result._score > score) score = result._score\n        }\n        }\n\n        objResults.obj = obj\n        objResults._score = score\n        if(options?.scoreFn) {\n        score = options.scoreFn(objResults)\n        if(!score) continue\n        score = denormalizeScore(score)\n        objResults._score = score\n        }\n\n        if(score < threshold) continue\n        push_result(objResults)\n    }\n\n    // no keys\n    } else {\n    for(var i = 0; i < targetsLen; ++i) { var target = targets[i]\n        if(!target) continue\n        if(!isPrepared(target)) target = getPrepared(target)\n\n        if((searchBitflags & target._bitflags) !== searchBitflags) continue\n        var result = algorithm(preparedSearch, target)\n        if(result === NULL) continue\n        if(result._score < threshold) continue\n\n        push_result(result)\n    }\n    }\n\n    if(resultsLen === 0) return noResults\n    var results = new Array(resultsLen)\n    for(var i = resultsLen - 1; i >= 0; --i) results[i] = q.poll()\n    results.total = resultsLen + limitedCount\n    return results\n}\n\n\n// this is written as 1 function instead of 2 for minification. perf seems fine ...\n// except when minified. the perf is very slow\nvar highlight = (result, open='<b>', close='</b>') => {\n    var callback = typeof open === 'function' ? open : undefined\n\n    var target      = result.target\n    var targetLen   = target.length\n    var indexes     = result.indexes\n    var highlighted = ''\n    var matchI      = 0\n    var indexesI    = 0\n    var opened      = false\n    var parts       = []\n\n    for(var i = 0; i < targetLen; ++i) { var char = target[i]\n    if(indexes[indexesI] === i) {\n        ++indexesI\n        if(!opened) { opened = true\n        if(callback) {\n            parts.push(highlighted); highlighted = ''\n        } else {\n            highlighted += open\n        }\n        }\n\n        if(indexesI === indexes.length) {\n        if(callback) {\n            highlighted += char\n            parts.push(callback(highlighted, matchI++)); highlighted = ''\n            parts.push(target.substr(i+1))\n        } else {\n            highlighted += char + close + target.substr(i+1)\n        }\n        break\n        }\n    } else {\n        if(opened) { opened = false\n        if(callback) {\n            parts.push(callback(highlighted, matchI++)); highlighted = ''\n        } else {\n            highlighted += close\n        }\n        }\n    }\n    highlighted += char\n    }\n\n    return callback ? parts : highlighted\n}\n\n\nvar prepare = (target) => {\n    if(typeof target === 'number') target = ''+target\n    else if(typeof target !== 'string') target = ''\n    var info = prepareLowerInfo(target)\n    return new_result(target, {_targetLower:info._lower, _targetLowerCodes:info.lowerCodes, _bitflags:info.bitflags})\n}\n\nvar cleanup = () => { preparedCache.clear(); preparedSearchCache.clear() }\n\n\n// Below this point is only internal code\n// Below this point is only internal code\n// Below this point is only internal code\n// Below this point is only internal code\n\n\nclass Result {\n    get ['indexes']() { return this._indexes.slice(0, this._indexes.len).sort((a,b)=>a-b) }\n    set ['indexes'](indexes) { return this._indexes = indexes }\n    ['highlight'](open, close) { return highlight(this, open, close) }\n    get ['score']() { return normalizeScore(this._score) }\n    set ['score'](score) { this._score = denormalizeScore(score) }\n}\n\nclass KeysResult extends Array {\n    get ['score']() { return normalizeScore(this._score) }\n    set ['score'](score) { this._score = denormalizeScore(score) }\n}\n\nvar new_result = (target, options) => {\n    const result = new Result()\n    result['target']             = target\n    result['obj']                = options.obj                   ?? NULL\n    result._score                = options._score                ?? NEGATIVE_INFINITY\n    result._indexes              = options._indexes              ?? []\n    result._targetLower          = options._targetLower          ?? ''\n    result._targetLowerCodes     = options._targetLowerCodes     ?? NULL\n    result._nextBeginningIndexes = options._nextBeginningIndexes ?? NULL\n    result._bitflags             = options._bitflags             ?? 0\n    return result\n}\n\n\nvar normalizeScore = score => {\n    if(score === NEGATIVE_INFINITY) return 0\n    if(score > 1) return score\n    return Math.E ** ( ((-score + 1)**.04307 - 1) * -2)\n}\nvar denormalizeScore = normalizedScore => {\n    if(normalizedScore === 0) return NEGATIVE_INFINITY\n    if(normalizedScore > 1) return normalizedScore\n    return 1 - Math.pow((Math.log(normalizedScore) / -2 + 1), 1 / 0.04307)\n}\n\n\nvar prepareSearch = (search) => {\n    if(typeof search === 'number') search = ''+search\n    else if(typeof search !== 'string') search = ''\n    search = search.trim()\n    var info = prepareLowerInfo(search)\n\n    var spaceSearches = []\n    if(info.containsSpace) {\n    var searches = search.split(/\\s+/)\n    searches = [...new Set(searches)] // distinct\n    for(var i=0; i<searches.length; i++) {\n        if(searches[i] === '') continue\n        var _info = prepareLowerInfo(searches[i])\n        spaceSearches.push({lowerCodes:_info.lowerCodes, _lower:searches[i].toLowerCase(), containsSpace:false})\n    }\n    }\n\n    return {lowerCodes: info.lowerCodes, _lower: info._lower, containsSpace: info.containsSpace, bitflags: info.bitflags, spaceSearches: spaceSearches}\n}\n\n\n\nvar getPrepared = (target) => {\n    if(target.length > 999) return prepare(target) // don't cache huge targets\n    var targetPrepared = preparedCache.get(target)\n    if(targetPrepared !== undefined) return targetPrepared\n    targetPrepared = prepare(target)\n    preparedCache.set(target, targetPrepared)\n    return targetPrepared\n}\nvar getPreparedSearch = (search) => {\n    if(search.length > 999) return prepareSearch(search) // don't cache huge searches\n    var searchPrepared = preparedSearchCache.get(search)\n    if(searchPrepared !== undefined) return searchPrepared\n    searchPrepared = prepareSearch(search)\n    preparedSearchCache.set(search, searchPrepared)\n    return searchPrepared\n}\n\n\nvar all = (targets, options) => {\n    var results = []; results.total = targets.length // this total can be wrong if some targets are skipped\n\n    var limit = options?.limit || INFINITY\n\n    if(options?.key) {\n    for(var i=0;i<targets.length;i++) { var obj = targets[i]\n        var target = getValue(obj, options.key)\n        if(target == NULL) continue\n        if(!isPrepared(target)) target = getPrepared(target)\n        var result = new_result(target.target, {_score: target._score, obj: obj})\n        results.push(result); if(results.length >= limit) return results\n    }\n    } else if(options?.keys) {\n    for(var i=0;i<targets.length;i++) { var obj = targets[i]\n        var objResults = new KeysResult(options.keys.length)\n        for (var keyI = options.keys.length - 1; keyI >= 0; --keyI) {\n        var target = getValue(obj, options.keys[keyI])\n        if(!target) { objResults[keyI] = noTarget; continue }\n        if(!isPrepared(target)) target = getPrepared(target)\n        target._score = NEGATIVE_INFINITY\n        target._indexes.len = 0\n        objResults[keyI] = target\n        }\n        objResults.obj = obj\n        objResults._score = NEGATIVE_INFINITY\n        results.push(objResults); if(results.length >= limit) return results\n    }\n    } else {\n    for(var i=0;i<targets.length;i++) { var target = targets[i]\n        if(target == NULL) continue\n        if(!isPrepared(target)) target = getPrepared(target)\n        target._score = NEGATIVE_INFINITY\n        target._indexes.len = 0\n        results.push(target); if(results.length >= limit) return results\n    }\n    }\n\n    return results\n}\n\n\nvar algorithm = (preparedSearch, prepared, allowSpaces=false, allowPartialMatch=false) => {\n    if(allowSpaces===false && preparedSearch.containsSpace) return algorithmSpaces(preparedSearch, prepared, allowPartialMatch)\n\n    var searchLower      = preparedSearch._lower\n    var searchLowerCodes = preparedSearch.lowerCodes\n    var searchLowerCode  = searchLowerCodes[0]\n    var targetLowerCodes = prepared._targetLowerCodes\n    var searchLen        = searchLowerCodes.length\n    var targetLen        = targetLowerCodes.length\n    var searchI          = 0 // where we at\n    var targetI          = 0 // where you at\n    var matchesSimpleLen = 0\n\n    // very basic fuzzy match; to remove non-matching targets ASAP!\n    // walk through target. find sequential matches.\n    // if all chars aren't found then exit\n    for(;;) {\n    var isMatch = searchLowerCode === targetLowerCodes[targetI]\n    if(isMatch) {\n        matchesSimple[matchesSimpleLen++] = targetI\n        ++searchI; if(searchI === searchLen) break\n        searchLowerCode = searchLowerCodes[searchI]\n    }\n    ++targetI; if(targetI >= targetLen) return NULL // Failed to find searchI\n    }\n\n    var searchI = 0\n    var successStrict = false\n    var matchesStrictLen = 0\n\n    var nextBeginningIndexes = prepared._nextBeginningIndexes\n    if(nextBeginningIndexes === NULL) nextBeginningIndexes = prepared._nextBeginningIndexes = prepareNextBeginningIndexes(prepared.target)\n    targetI = matchesSimple[0]===0 ? 0 : nextBeginningIndexes[matchesSimple[0]-1]\n\n    // Our target string successfully matched all characters in sequence!\n    // Let's try a more advanced and strict test to improve the score\n    // only count it as a match if it's consecutive or a beginning character!\n    var backtrackCount = 0\n    if(targetI !== targetLen) for(;;) {\n    if(targetI >= targetLen) {\n        // We failed to find a good spot for this search char, go back to the previous search char and force it forward\n        if(searchI <= 0) break // We failed to push chars forward for a better match\n\n        ++backtrackCount; if(backtrackCount > 200) break // exponential backtracking is taking too long, just give up and return a bad match\n\n        --searchI\n        var lastMatch = matchesStrict[--matchesStrictLen]\n        targetI = nextBeginningIndexes[lastMatch]\n\n    } else {\n        var isMatch = searchLowerCodes[searchI] === targetLowerCodes[targetI]\n        if(isMatch) {\n        matchesStrict[matchesStrictLen++] = targetI\n        ++searchI; if(searchI === searchLen) { successStrict = true; break }\n        ++targetI\n        } else {\n        targetI = nextBeginningIndexes[targetI]\n        }\n    }\n    }\n\n    // check if it's a substring match\n    var substringIndex = searchLen <= 1 ? -1 : prepared._targetLower.indexOf(searchLower, matchesSimple[0]) // perf: this is slow\n    var isSubstring = !!~substringIndex\n    var isSubstringBeginning = !isSubstring ? false : substringIndex===0 || prepared._nextBeginningIndexes[substringIndex-1] === substringIndex\n\n    // if it's a substring match but not at a beginning index, let's try to find a substring starting at a beginning index for a better score\n    if(isSubstring && !isSubstringBeginning) {\n    for(var i=0; i<nextBeginningIndexes.length; i=nextBeginningIndexes[i]) {\n        if(i <= substringIndex) continue\n\n        for(var s=0; s<searchLen; s++) if(searchLowerCodes[s] !== prepared._targetLowerCodes[i+s]) break\n        if(s === searchLen) { substringIndex = i; isSubstringBeginning = true; break }\n    }\n    }\n\n    // tally up the score & keep track of matches for highlighting later\n    // if it's a simple match, we'll switch to a substring match if a substring exists\n    // if it's a strict match, we'll switch to a substring match only if that's a better score\n\n    var calculateScore = matches => {\n    var score = 0\n\n    var extraMatchGroupCount = 0\n    for(var i = 1; i < searchLen; ++i) {\n        if(matches[i] - matches[i-1] !== 1) {score -= matches[i]; ++extraMatchGroupCount}\n    }\n    var unmatchedDistance = matches[searchLen-1] - matches[0] - (searchLen-1)\n\n    score -= (12+unmatchedDistance) * extraMatchGroupCount // penality for more groups\n\n    if(matches[0] !== 0) score -= matches[0]*matches[0]*.2 // penality for not starting near the beginning\n\n    if(!successStrict) {\n        score *= 1000\n    } else {\n        // successStrict on a target with too many beginning indexes loses points for being a bad target\n        var uniqueBeginningIndexes = 1\n        for(var i = nextBeginningIndexes[0]; i < targetLen; i=nextBeginningIndexes[i]) ++uniqueBeginningIndexes\n\n        if(uniqueBeginningIndexes > 24) score *= (uniqueBeginningIndexes-24)*10 // quite arbitrary numbers here ...\n    }\n\n    score -= (targetLen - searchLen)/2 // penality for longer targets\n\n    if(isSubstring)          score /= 1+searchLen*searchLen*1 // bonus for being a full substring\n    if(isSubstringBeginning) score /= 1+searchLen*searchLen*1 // bonus for substring starting on a beginningIndex\n\n    score -= (targetLen - searchLen)/2 // penality for longer targets\n\n    return score\n    }\n\n    if(!successStrict) {\n    if(isSubstring) for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches\n    var matchesBest = matchesSimple\n    var score = calculateScore(matchesBest)\n    } else {\n    if(isSubstringBeginning) {\n        for(var i=0; i<searchLen; ++i) matchesSimple[i] = substringIndex+i // at this point it's safe to overwrite matchehsSimple with substr matches\n        var matchesBest = matchesSimple\n        var score = calculateScore(matchesSimple)\n    } else {\n        var matchesBest = matchesStrict\n        var score = calculateScore(matchesStrict)\n    }\n    }\n\n    prepared._score = score\n\n    for(var i = 0; i < searchLen; ++i) prepared._indexes[i] = matchesBest[i]\n    prepared._indexes.len = searchLen\n\n    const result    = new Result()\n    result.target   = prepared.target\n    result._score   = prepared._score\n    result._indexes = prepared._indexes\n    return result\n}\nvar algorithmSpaces = (preparedSearch, target, allowPartialMatch) => {\n    var seen_indexes = new Set()\n    var score = 0\n    var result = NULL\n\n    var first_seen_index_last_search = 0\n    var searches = preparedSearch.spaceSearches\n    var searchesLen = searches.length\n    var changeslen = 0\n\n    // Return _nextBeginningIndexes back to its normal state\n    var resetNextBeginningIndexes = () => {\n    for(let i=changeslen-1; i>=0; i--) target._nextBeginningIndexes[nextBeginningIndexesChanges[i*2 + 0]] = nextBeginningIndexesChanges[i*2 + 1]\n    }\n\n    var hasAtLeast1Match = false\n    for(var i=0; i<searchesLen; ++i) {\n    allowPartialMatchScores[i] = NEGATIVE_INFINITY\n    var search = searches[i]\n\n    result = algorithm(search, target)\n    if(allowPartialMatch) {\n        if(result === NULL) continue\n        hasAtLeast1Match = true\n    } else {\n        if(result === NULL) {resetNextBeginningIndexes(); return NULL}\n    }\n\n    // if not the last search, we need to mutate _nextBeginningIndexes for the next search\n    var isTheLastSearch = i === searchesLen - 1\n    if(!isTheLastSearch) {\n        var indexes = result._indexes\n\n        var indexesIsConsecutiveSubstring = true\n        for(let i=0; i<indexes.len-1; i++) {\n        if(indexes[i+1] - indexes[i] !== 1) {\n            indexesIsConsecutiveSubstring = false; break;\n        }\n        }\n\n        if(indexesIsConsecutiveSubstring) {\n        var newBeginningIndex = indexes[indexes.len-1] + 1\n        var toReplace = target._nextBeginningIndexes[newBeginningIndex-1]\n        for(let i=newBeginningIndex-1; i>=0; i--) {\n            if(toReplace !== target._nextBeginningIndexes[i]) break\n            target._nextBeginningIndexes[i] = newBeginningIndex\n            nextBeginningIndexesChanges[changeslen*2 + 0] = i\n            nextBeginningIndexesChanges[changeslen*2 + 1] = toReplace\n            changeslen++\n        }\n        }\n    }\n\n    score += result._score / searchesLen\n    allowPartialMatchScores[i] = result._score / searchesLen\n\n    // dock points based on order otherwise \"c man\" returns Manifest.cpp instead of CheatManager.h\n    if(result._indexes[0] < first_seen_index_last_search) {\n        score -= (first_seen_index_last_search - result._indexes[0]) * 2\n    }\n    first_seen_index_last_search = result._indexes[0]\n\n    for(var j=0; j<result._indexes.len; ++j) seen_indexes.add(result._indexes[j])\n    }\n\n    if(allowPartialMatch && !hasAtLeast1Match) return NULL\n\n    resetNextBeginningIndexes()\n\n    // allows a search with spaces that's an exact substring to score well\n    var allowSpacesResult = algorithm(preparedSearch, target, /*allowSpaces=*/true)\n    if(allowSpacesResult !== NULL && allowSpacesResult._score > score) {\n    if(allowPartialMatch) {\n        for(var i=0; i<searchesLen; ++i) {\n        allowPartialMatchScores[i] = allowSpacesResult._score / searchesLen\n        }\n    }\n    return allowSpacesResult\n    }\n\n    if(allowPartialMatch) result = target\n    result._score = score\n\n    var i = 0\n    for (let index of seen_indexes) result._indexes[i++] = index\n    result._indexes.len = i\n\n    return result\n}\n\n// we use this instead of just .normalize('NFD').replace(/[\\u0300-\\u036f]/g, '') because that screws with japanese characters\nvar remove_accents = (str) => str.replace(/\\p{Script=Latin}+/gu, match => match.normalize('NFD')).replace(/[\\u0300-\\u036f]/g, '')\n\nvar prepareLowerInfo = (str) => {\n    str = remove_accents(str)\n    var strLen = str.length\n    var lower = str.toLowerCase()\n    var lowerCodes = [] // new Array(strLen)    sparse array is too slow\n    var bitflags = 0\n    var containsSpace = false // space isn't stored in bitflags because of how searching with a space works\n\n    for(var i = 0; i < strLen; ++i) {\n    var lowerCode = lowerCodes[i] = lower.charCodeAt(i)\n\n    if(lowerCode === 32) {\n        containsSpace = true\n        continue // it's important that we don't set any bitflags for space\n    }\n\n    var bit = lowerCode>=97&&lowerCode<=122 ? lowerCode-97 // alphabet\n            : lowerCode>=48&&lowerCode<=57  ? 26           // numbers\n                                                            // 3 bits available\n            : lowerCode<=127                ? 30           // other ascii\n            :                                 31           // other utf8\n    bitflags |= 1<<bit\n    }\n\n    return {lowerCodes:lowerCodes, bitflags:bitflags, containsSpace:containsSpace, _lower:lower}\n}\nvar prepareBeginningIndexes = (target) => {\n    var targetLen = target.length\n    var beginningIndexes = []; var beginningIndexesLen = 0\n    var wasUpper = false\n    var wasAlphanum = false\n    for(var i = 0; i < targetLen; ++i) {\n    var targetCode = target.charCodeAt(i)\n    var isUpper = targetCode>=65&&targetCode<=90\n    var isAlphanum = isUpper || targetCode>=97&&targetCode<=122 || targetCode>=48&&targetCode<=57\n    var isBeginning = isUpper && !wasUpper || !wasAlphanum || !isAlphanum\n    wasUpper = isUpper\n    wasAlphanum = isAlphanum\n    if(isBeginning) beginningIndexes[beginningIndexesLen++] = i\n    }\n    return beginningIndexes\n}\nvar prepareNextBeginningIndexes = (target) => {\n    target = remove_accents(target)\n    var targetLen = target.length\n    var beginningIndexes = prepareBeginningIndexes(target)\n    var nextBeginningIndexes = [] // new Array(targetLen)     sparse array is too slow\n    var lastIsBeginning = beginningIndexes[0]\n    var lastIsBeginningI = 0\n    for(var i = 0; i < targetLen; ++i) {\n    if(lastIsBeginning > i) {\n        nextBeginningIndexes[i] = lastIsBeginning\n    } else {\n        lastIsBeginning = beginningIndexes[++lastIsBeginningI]\n        nextBeginningIndexes[i] = lastIsBeginning===undefined ? targetLen : lastIsBeginning\n    }\n    }\n    return nextBeginningIndexes\n}\n\nvar preparedCache       = new Map()\nvar preparedSearchCache = new Map()\n\n// the theory behind these being globals is to reduce garbage collection by not making new arrays\nvar matchesSimple = []; var matchesStrict = []\nvar nextBeginningIndexesChanges = [] // allows straw berry to match strawberry well, by modifying the end of a substring to be considered a beginning index for the rest of the search\nvar keysSpacesBestScores = []; var allowPartialMatchScores = []\nvar tmpTargets = []; var tmpResults = []\n\n// prop = 'key'                  2.5ms optimized for this case, seems to be about as fast as direct obj[prop]\n// prop = 'key1.key2'            10ms\n// prop = ['key1', 'key2']       27ms\n// prop = obj => obj.tags.join() ??ms\nvar getValue = (obj, prop) => {\n    var tmp = obj[prop]; if(tmp !== undefined) return tmp\n    if(typeof prop === 'function') return prop(obj) // this should run first. but that makes string props slower\n    var segs = prop\n    if(!Array.isArray(prop)) segs = prop.split('.')\n    var len = segs.length\n    var i = -1\n    while (obj && (++i < len)) obj = obj[segs[i]]\n    return obj\n}\n\nvar isPrepared = (x) => { return typeof x === 'object' && typeof x._bitflags === 'number' }\nvar INFINITY = Infinity; var NEGATIVE_INFINITY = -INFINITY\nvar noResults = []; noResults.total = 0\nvar NULL = null\n\nvar noTarget = prepare('')\n\n// Hacked version of https://github.com/lemire/FastPriorityQueue.js\nvar fastpriorityqueue=r=>{var e=[],o=0,a={},v=r=>{for(var a=0,v=e[a],c=1;c<o;){var s=c+1;a=c,s<o&&e[s]._score<e[c]._score&&(a=s),e[a-1>>1]=e[a],c=1+(a<<1)}for(var f=a-1>>1;a>0&&v._score<e[f]._score;f=(a=f)-1>>1)e[a]=e[f];e[a]=v};return a.add=(r=>{var a=o;e[o++]=r;for(var v=a-1>>1;a>0&&r._score<e[v]._score;v=(a=v)-1>>1)e[a]=e[v];e[a]=r}),a.poll=(r=>{if(0!==o){var a=e[0];return e[0]=e[--o],v(),a}}),a.peek=(r=>{if(0!==o)return e[0]}),a.replaceTop=(r=>{e[0]=r,v()}),a}\nvar q = fastpriorityqueue() // reuse this\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/functions/levendist.js",
    "content": "// Original code from https://github.com/koeqaife/hyprland-material-you\n// Original code license: GPLv3\n// Translated to Js from Cython with an LLM and reviewed\n\nfunction min3(a, b, c) {\n    return a < b && a < c ? a : b < c ? b : c;\n}\n\nfunction max3(a, b, c) {\n    return a > b && a > c ? a : b > c ? b : c;\n}\n\nfunction min2(a, b) {\n    return a < b ? a : b;\n}\n\nfunction max2(a, b) {\n    return a > b ? a : b;\n}\n\nfunction levenshteinDistance(s1, s2) {\n    let len1 = s1.length;\n    let len2 = s2.length;\n\n    if (len1 === 0) return len2;\n    if (len2 === 0) return len1;\n\n    if (len2 > len1) {\n        [s1, s2] = [s2, s1];\n        [len1, len2] = [len2, len1];\n    }\n\n    let prev = new Array(len2 + 1);\n    let curr = new Array(len2 + 1);\n\n    for (let j = 0; j <= len2; j++) {\n        prev[j] = j;\n    }\n\n    for (let i = 1; i <= len1; i++) {\n        curr[0] = i;\n        for (let j = 1; j <= len2; j++) {\n            let cost = s1[i - 1] === s2[j - 1] ? 0 : 1;\n            curr[j] = min3(prev[j] + 1, curr[j - 1] + 1, prev[j - 1] + cost);\n        }\n        [prev, curr] = [curr, prev];\n    }\n\n    return prev[len2];\n}\n\nfunction partialRatio(shortS, longS) {\n    let lenS = shortS.length;\n    let lenL = longS.length;\n    let best = 0.0;\n\n    if (lenS === 0) return 1.0;\n\n    for (let i = 0; i <= lenL - lenS; i++) {\n        let sub = longS.slice(i, i + lenS);\n        let dist = levenshteinDistance(shortS, sub);\n        let score = 1.0 - (dist / lenS);\n        if (score > best) best = score;\n    }\n\n    return best;\n}\n\nfunction computeScore(s1, s2) {\n    if (s1 === s2) return 1.0;\n\n    let dist = levenshteinDistance(s1, s2);\n    let maxLen = max2(s1.length, s2.length);\n    if (maxLen === 0) return 1.0;\n\n    let full = 1.0 - (dist / maxLen);\n    let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);\n\n    let score = 0.85 * full + 0.15 * part;\n\n    if (s1 && s2 && s1[0] !== s2[0]) {\n        score -= 0.05;\n    }\n\n    let lenDiff = Math.abs(s1.length - s2.length);\n    if (lenDiff >= 3) {\n        score -= 0.05 * lenDiff / maxLen;\n    }\n\n    let commonPrefixLen = 0;\n    let minLen = min2(s1.length, s2.length);\n    for (let i = 0; i < minLen; i++) {\n        if (s1[i] === s2[i]) {\n            commonPrefixLen++;\n        } else {\n            break;\n        }\n    }\n    score += 0.02 * commonPrefixLen;\n\n    if (s1.includes(s2) || s2.includes(s1)) {\n        score += 0.06;\n    }\n\n    return Math.max(0.0, Math.min(1.0, score));\n}\n\nfunction computeTextMatchScore(s1, s2) {\n    if (s1 === s2) return 1.0;\n\n    let dist = levenshteinDistance(s1, s2);\n    let maxLen = max2(s1.length, s2.length);\n    if (maxLen === 0) return 1.0;\n\n    let full = 1.0 - (dist / maxLen);\n    let part = s1.length < s2.length ? partialRatio(s1, s2) : partialRatio(s2, s1);\n\n    let score = 0.4 * full + 0.6 * part;\n\n    let lenDiff = Math.abs(s1.length - s2.length);\n    if (lenDiff >= 10) {\n        score -= 0.02 * lenDiff / maxLen;\n    }\n\n    let commonPrefixLen = 0;\n    let minLen = min2(s1.length, s2.length);\n    for (let i = 0; i < minLen; i++) {\n        if (s1[i] === s2[i]) {\n            commonPrefixLen++;\n        } else {\n            break;\n        }\n    }\n    score += 0.01 * commonPrefixLen;\n\n    if (s1.includes(s2) || s2.includes(s1)) {\n        score += 0.2;\n    }\n\n    return Math.max(0.0, Math.min(1.0, score));\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/AdaptedMaterialScheme.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\n\n/**\n * Material color scheme adapted to a given color. It's incomplete but enough for what we need...\n */\nQtObject {\n    id: root\n    required property color color\n    readonly property bool colorIsDark: color.hslLightness < 0.5\n\n    property color colLayer0: ColorUtils.mix(Appearance.colors.colLayer0, root.color, (colorIsDark && Appearance.m3colors.darkmode) ? 0.6 : 0.5)\n    property color colLayer1: ColorUtils.mix(Appearance.colors.colLayer1, root.color, 0.5)\n    property color colOnLayer0: ColorUtils.mix(Appearance.colors.colOnLayer0, root.color, 0.5)\n    property color colOnLayer1: ColorUtils.mix(Appearance.colors.colOnLayer1, root.color, 0.5)\n    property color colSubtext: ColorUtils.mix(Appearance.colors.colOnLayer1, root.color, 0.5)\n    property color colPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimary, root.color), root.color, 0.5)\n    property color colPrimaryHover: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryHover, root.color), root.color, 0.3)\n    property color colPrimaryActive: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colPrimaryActive, root.color), root.color, 0.3)\n    property color colSecondary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.colors.colSecondary, root.color), root.color, 0.5)\n    property color colSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3secondaryContainer, root.color, 0.15)\n    property color colSecondaryContainerHover: ColorUtils.mix(Appearance.colors.colSecondaryContainerHover, root.color, 0.3)\n    property color colSecondaryContainerActive: ColorUtils.mix(Appearance.colors.colSecondaryContainerActive, root.color, 0.5)\n    property color colOnPrimary: ColorUtils.mix(ColorUtils.adaptToAccent(Appearance.m3colors.m3onPrimary, root.color), root.color, 0.5)\n    property color colOnSecondaryContainer: ColorUtils.mix(Appearance.m3colors.m3onSecondaryContainer, root.color, 0.5)\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/AnimatedTabIndexPair.qml",
    "content": "import QtQuick\n\n// idx1 is the \"leading\" indicator position, idx2 is the \"following\" one\n// The former animates faster than the latter, see the NumberAnimations below\nQtObject {\n    id: root\n    required property int index\n\n    property real idx1: index\n    property real idx2: index\n    property int idx1Duration: 100\n    property int idx2Duration: 300\n\n    Behavior on idx1 {\n        NumberAnimation {\n            duration: root.idx1Duration\n            easing.type: Easing.OutSine\n        }\n    }\n    Behavior on idx2 {\n        NumberAnimation {\n            duration: root.idx2Duration\n            easing.type: Easing.OutSine\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/FolderListModelWithHistory.qml",
    "content": "import QtQuick\nimport Qt.labs.folderlistmodel\n\nFolderListModel {\n    id: root\n    property list<url> folderHistory: []\n    property int currentFolderHistoryIndex: -1\n    property bool historyNavigationLock: false\n\n    function lockNextNavigation() {\n        historyNavigationLock = true;\n    }\n\n    function pushToHistory(path) {\n        if (folderHistory[currentFolderHistoryIndex] === path)\n            return;\n        folderHistory = folderHistory.slice(0, currentFolderHistoryIndex + 1);\n        folderHistory.push(path);\n        currentFolderHistoryIndex = folderHistory.length - 1;\n    }\n\n    function navigateUp() {\n        root.folder = root.parentFolder;\n    }\n\n    function navigateBack() {\n        if (currentFolderHistoryIndex === 0)\n            return;\n        currentFolderHistoryIndex--;\n        lockNextNavigation();\n        root.folder = folderHistory[currentFolderHistoryIndex];\n    }\n\n    function navigateForward() {\n        if (currentFolderHistoryIndex >= folderHistory.length - 1) return;\n        currentFolderHistoryIndex++;\n        lockNextNavigation();\n        root.folder = folderHistory[currentFolderHistoryIndex];\n    }\n\n    onFolderChanged: {\n        if (historyNavigationLock) {\n            historyNavigationLock = false;\n            return;\n        }\n        pushToHistory(folder);\n    }\n\n    Component.onCompleted: {\n        root.folderHistory = [root.folder]\n        root.currentFolderHistoryIndex = 0\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/IndexModel.qml",
    "content": "import Quickshell\n\nScriptModel {\n    required property int count\n    values: Array(count).map((_, i) => i)\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/LauncherSearchResult.qml",
    "content": "import QtQuick\nimport Quickshell\n\nQtObject {\n    enum IconType { Material, Text, System, None }\n    enum FontType { Normal, Monospace }\n\n    // General stuff\n    property string type: \"\"\n    property var fontType: LauncherSearchResult.FontType.Normal\n    property string name: \"\"\n    property string rawValue: \"\"\n    property string iconName: \"\"\n    property var iconType: LauncherSearchResult.IconType.None\n    property string verb: \"\"\n    property bool blurImage: false\n    property var execute: () => {\n        print(\"Not implemented\");\n    }\n    property var actions: []\n    \n    // Stuff needed for DesktopEntry \n    property string id: \"\"\n    property bool shown: true\n    property string comment: \"\"\n    property bool runInTerminal: false\n    property string genericName: \"\"\n    property list<string> keywords: []\n\n    // Extra stuff to allow for more flexibility\n    property string category: type\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/NestableObject.qml",
    "content": "import QtQuick\n\n// QtObject that allows stuff to be freely declared inside\nQtObject {\n    default property list<QtObject> data\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudApi.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport qs.modules.common.functions\nimport qs.modules.common.utils\nimport qs.services\nimport qs.modules.common\nimport \"..\"\n\nNestableObject {\n    id: root\n\n    enum State {\n        Done, Preparing, Processing, Error\n    }\n\n    signal finished()\n    signal error(message: string)\n    property int errorCode\n    property string errorMessage: \"\"\n    property var outputData\n    property var state: GCloudApi.State.Done\n\n    function resetState() {\n        root.state = GCloudApi.State.Done;\n        root.errorMessage = \"\";\n        root.outputData = undefined;\n    }\n\n    function handleApiOutput(out: string): bool {\n        try {\n            root.outputData = JSON.parse(out);\n            if (outputData.error) {\n                print(\"API error: \" + JSON.stringify(outputData.error, null, 2))\n                root.state = GCloudApi.State.Error;\n                root.errorCode = outputData.error.code;\n                root.errorMessage = outputData.error.message;\n                root.error(outputData.error.message);\n                return false;\n            }\n            root.finished();\n            root.state = GCloudApi.State.Done;\n            return true\n        } catch (e) {\n            print(\"Failed to parse API response: \" + e + \"\\n\" + out)\n            root.state = GCloudApi.State.Error;\n            root.errorMessage = \"Failed to parse API response\";\n            root.error(root.errorMessage);\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudTranslate.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs.modules.common.functions\nimport qs.modules.common.utils\nimport qs.services\nimport \"..\"\n\nGCloudApi {\n    id: root\n\n    property list<string> pendingStrings\n    property bool setupReady: false\n    readonly property bool preparationReady: GoogleCloud.tokenReady && setupReady\n\n    function translateStrings(strings: list<string>) {\n        GoogleCloud.load();\n        root.setupReady = false;\n        root.pendingStrings = strings;\n        root.state = GCloudApi.State.Preparing;\n        root.setupReady = true;\n    }\n\n    onPreparationReadyChanged: {\n        if (!preparationReady) return;\n        root.state = GCloudApi.State.Processing;\n\n        const targetLang = Translation.languageCode;\n        const payload = {\n            \"targetLanguageCode\": targetLang,\n            \"contents\": root.pendingStrings,\n            \"mimeType\": \"text/plain\"\n        };\n\n        // print(\"PENDING STRINGS:\", root.pendingStrings)\n\n        var seq = [];\n        seq.push([ //\n            \"bash\", \"-c\", //\n            `curl -sL -X POST \\\n-H \"Authorization: Bearer ${GoogleCloud.token}\" \\\n-H \"x-goog-user-project: ${GoogleCloud.projectId}\" \\\n-H \"Content-Type: application/json\" \\\n-d '${StringUtils.shellSingleQuoteEscape(JSON.stringify(payload))}' \\\n\"https://translation.googleapis.com/v3/projects/${GoogleCloud.projectId}:translateText\"`\n        ]);\n\n        seq.push(((out) => {\n            root.handleApiOutput(out);\n        }));\n\n        multiproc.runSequence(seq);\n    }\n\n    MultiTurnProcess {\n        id: multiproc\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVision.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport qs.modules.common.functions\nimport qs.modules.common.utils\nimport qs.services\nimport qs.modules.common\nimport \"..\"\n\nGCloudApi {\n    id: root\n\n    readonly property string imageBase64FilePath: `${Directories.screenshotTemp}/vision_base64.txt`\n    readonly property string payloadFilePath: `${Directories.screenshotTemp}/vision_payload.json`\n    property string uploadEndpoint: \"https://uguu.se/upload\"\n\n    property bool tokenReady: GoogleCloud.tokenReady\n    property bool onlineImageReady: false\n    readonly property bool preparationReady: tokenReady && onlineImageReady\n\n    function annotateImage(imageUri: string) {\n        resetState();\n        root.state = GCloudApi.State.Preparing;\n        root.onlineImageReady = false\n        GoogleCloud.load();\n\n        var seq = []; // command sequence\n\n        const niceFilePath = StringUtils.shellSingleQuoteEscape(FileUtils.trimFileProtocol(imageUri))\n        seq = [ //\n            [\"bash\", \"-c\", `mkdir -p '${Directories.screenshotTemp}'; base64 '${niceFilePath}' -w 0 > '${imageBase64FilePath}'`], //\n            (out) => { //\n                root.onlineImageReady = true; //\n            }\n        ]\n\n        // Execute the base64 conversion & load the token\n        prepMultiproc.runSequence(seq);\n    }\n\n    onPreparationReadyChanged: {\n        if (!preparationReady) return;\n        if (GoogleCloud.tokenError || GoogleCloud.keyError) {\n            root.state = GCloudApi.State.Error;\n            root.error(Translation.tr(\"Set your Google Cloud service account key\"));\n            return;\n        }\n        root.state = GCloudApi.State.Processing;\n        var seq = []; // command sequence\n\n        // Construct the JSON payload using jq to read from the base64 file\n        seq.push([\n            \"bash\", \"-c\",\n            `jq -n --rawfile content '${imageBase64FilePath}' \\\n'{\"requests\": [{\"image\": {\"content\": $content}, \"features\": [{\"type\": \"DOCUMENT_TEXT_DETECTION\"}]}]}' \\\n> '${payloadFilePath}'`\n        ]);\n\n        seq.push([\n            \"bash\", \"-c\",\n            `curl -s -X POST \\\n-H \"Authorization: Bearer ${GoogleCloud.token}\" \\\n-H \"x-goog-user-project: ${GoogleCloud.projectId}\" \\\n-H \"Content-Type: application/json\" \\\nhttps://vision.googleapis.com/v1/images:annotate \\\n-d @'${payloadFilePath}'`\n        ]);\n\n        seq.push((out) => {\n            root.handleApiOutput(out);\n        });\n\n        lookMultiproc.runSequence(seq);\n    }\n\n    MultiTurnProcess {\n        id: prepMultiproc\n    }\n\n    MultiTurnProcess {\n        id: lookMultiproc\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/gCloud/GCloudVisionResult.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport \"..\"\n\nNestableObject {\n    id: root\n\n    property real confidenceThreshold: 0.5 // TODO tune this\n\n    property var rawData\n    property var rawBlocks\n    property var rawParagraphs\n    property var coherentParagraphs\n\n    function initializeWithData(apiOutputData: var): void {\n        // Null check\n        if (!apiOutputData) {\n            print(\"[GCloudVisionResult] Data is null/undefined\")\n            return;\n        }\n\n        // Raw data\n        root.rawData = apiOutputData\n\n        // Raw blocks\n        var pages = apiOutputData.responses[0].fullTextAnnotation.pages\n        var blocks = [];\n        for (var i = 0; i < pages.length; i++) {\n            // print(\"this page\", JSON.stringify(pages[i]))\n            var blocksThisPage = pages[i].blocks;\n            for (var j = 0; j < blocksThisPage.length; j++) {\n                const block = blocksThisPage[j];\n                // print(\"new block with confidence\", block.confidence, \":\", JSON.stringify(block, null, 2))\n                if (block.confidence > root.confidenceThreshold) {\n                    blocks.push(block);\n                }\n            }\n        }\n        \n        root.rawBlocks = blocks\n        // print(\"RAW BLOCKS:\", blocks)\n\n        // Raw paragraphs\n        var paragraphs = []\n        for (var i = 0; i < blocks.length; i++) {\n            var blockParagraphs = blocks[i].paragraphs;\n            for (var j = 0; j < blockParagraphs.length; j++) {\n                const para = blockParagraphs[j];\n                // print(\"new paragraph\", JSON.stringify(para))\n                paragraphs.push(para);\n            }\n        }\n        root.rawParagraphs = [...paragraphs];\n\n        // print(\"RAW PARAGRAPHS\", paragraphs)\n\n        // Coherent paragraphs\n        // (raw data can be as granular as symbols)\n        // We're interested in paragraph level of granularity as it's good for translations\n        for (var i = 0; i < paragraphs.length; i++) {\n            const paragraph = paragraphs[i];\n            const words = paragraph.words;\n            var strList = []\n            for (var j = 0; j < words.length; j++) {\n                const symbols = words[j].symbols;\n                for (var k = 0; k < symbols.length; k++) {\n                    const sym = symbols[k];\n                    strList.push(sym.text);\n                    // print(\"CHAR:\", JSON.stringify(sym, null, 2));\n                    // Breaks\n                    // Reference: https://docs.cloud.google.com/vision/docs/reference/rpc/google.cloud.vision.v1#breaktype\n                    if (sym.property?.detectedBreak.type == \"SPACE\" || sym.property?.detectedBreak.type == \"UNKNOWN\") {\n                        strList.push(\" \");\n                    } else if (sym.property?.detectedBreak.type == \"SURE_SPACE\") {\n                        strList.push(\"　\");\n                    } else if (sym.property?.detectedBreak.type == \"EOL_SURE_SPACE\" || sym.property?.detectedBreak.type == \"LINE_BREAK\") {\n                        strList.push(\"\\n\");\n                    } else if (sym.property?.detectedBreak.type == \"HYPHEN\") {\n                        strList.push(\"-\\n\");\n                    }\n                }\n            }\n            // print(\"STR LIST:\", strList)\n            paragraphs[i].text = strList.join(\"\").trim();\n            // print(\"PARA TEXT:\", paragraphs[i].text)\n        }\n        root.coherentParagraphs = paragraphs\n        // print(\"COHERENT PARAGRAPHS\", JSON.stringify(paragraphs))\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/hyprland/HyprlandConfigOption.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQml\nimport QtQuick\nimport Quickshell.Io\nimport qs.services\nimport \"../\"\n\nNestableObject {\n    id: root\n\n    required property string key\n    property alias fetching: fetchProc.running\n    property bool set\n    property var value\n\n    Component.onCompleted: fetch()\n\n    Connections {\n        target: HyprlandConfig\n        function onReloaded() {\n            root.fetch();\n        }\n    }\n\n    function fetch() {\n        fetchProc.command = fetchProc.baseCommand.concat([root.key]);\n        fetchProc.running = true;\n    }\n\n    function setValue(newValue) {\n        HyprlandConfig.set(root.key, newValue)\n    }\n\n    function reset() {\n        HyprlandConfig.reset(root.key)\n    }\n\n    Process {\n        id: fetchProc\n        property list<string> baseCommand: [\"hyprctl\", \"getoption\", \"-j\"]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (text == \"no such option\")\n                    return;\n                try {\n                    const obj = JSON.parse(text);\n                    // Note that the value is returned as \"<data type>\": <value>\n                    // It's the only field that isn't always in the same key so we put it in an else\n                    for (const key in obj) {\n                        if (key == \"option\")\n                            continue;\n                        else if (key == \"set\")\n                            root.set = obj[key];\n                        else\n                            root.value = obj[key];\n                    }\n                } catch (e) {\n                    console.log(`[HyprlandConfigOption] Failed to fetch option \"${root.key}\":\\n  - Output: ${text.trim()}\\n  - Error: ${e}`);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/AntiFlashbangToggle.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Anti-flashbang\")\n    tooltipText: Translation.tr(\"Anti-flashbang\")\n    icon: \"flash_off\"\n    toggled: HyprlandAntiFlashbangShader.enabled\n\n    mainAction: () => {\n        HyprlandAntiFlashbangShader.toggle()\n    }\n    hasMenu: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/AudioToggle.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Audio output\")\n    statusText: toggled ? Translation.tr(\"Unmuted\") : Translation.tr(\"Muted\")\n    tooltipText: Translation.tr(\"Audio output | Right-click for volume mixer & device selector\")\n    toggled: !Audio.sink?.audio?.muted\n    icon: Audio.sink?.audio?.muted ? \"volume_off\" : \"volume_up\"\n    mainAction: () => {\n        Audio.toggleMute()\n    }\n    hasMenu: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/BluetoothToggle.qml",
    "content": "import QtQuick\nimport Quickshell.Bluetooth\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Bluetooth\")\n    statusText: BluetoothStatus.firstActiveDevice?.name ?? Translation.tr(\"Not connected\")\n    tooltipText: Translation.tr(\"%1 | Right-click to configure\").arg(\n        (BluetoothStatus.firstActiveDevice?.name ?? Translation.tr(\"Bluetooth\"))\n        + (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : \"\")\n    )\n    icon: BluetoothStatus.connected ? \"bluetooth_connected\" : BluetoothStatus.enabled ? \"bluetooth\" : \"bluetooth_disabled\"\n\n    available: BluetoothStatus.available\n    toggled: BluetoothStatus.enabled\n    mainAction: () => {\n        Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled\n    }\n    hasMenu: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/CloudflareWarpToggle.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport Quickshell\nimport Quickshell.Io\n\nQuickToggleModel {\n    id: root\n    name: Translation.tr(\"Cloudflare WARP\")\n\n    toggled: false\n    icon: \"cloud_lock\"\n    \n    mainAction: () => {\n        if (toggled) {\n            root.toggled = false\n            Quickshell.execDetached([\"warp-cli\", \"disconnect\"])\n        } else {\n            root.toggled = true\n            Quickshell.execDetached([\"warp-cli\", \"connect\"])\n        }\n    }\n\n    Process {\n        id: connectProc\n        command: [\"warp-cli\", \"connect\"]\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode !== 0) {\n                Quickshell.execDetached([\"notify-send\", \n                    Translation.tr(\"Cloudflare WARP\"), \n                    Translation.tr(\"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\")\n                    , \"-a\", \"Shell\"\n                ])\n            }\n        }\n    }\n\n    Process {\n        id: registrationProc\n        command: [\"warp-cli\", \"registration\", \"new\"]\n        onExited: (exitCode, exitStatus) => {\n            console.log(\"Warp registration exited with code and status:\", exitCode, exitStatus)\n            if (exitCode === 0) {\n                connectProc.running = true\n            } else {\n                Quickshell.execDetached([\"notify-send\", \n                    Translation.tr(\"Cloudflare WARP\"), \n                    Translation.tr(\"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\"),\n                    \"-a\", \"Shell\"\n                ])\n            }\n        }\n    }\n\n    Process {\n        id: fetchActiveState\n        running: true\n        command: [\"bash\", \"-c\", \"warp-cli status\"]\n        stdout: StdioCollector {\n            id: warpStatusCollector\n            onStreamFinished: {\n                if (warpStatusCollector.text.length > 0) {\n                    root.available = true\n                }\n                if (warpStatusCollector.text.includes(\"Unable\")) {\n                    registrationProc.running = true\n                } else if (warpStatusCollector.text.includes(\"Connected\")) {\n                    root.toggled = true\n                } else if (warpStatusCollector.text.includes(\"Disconnected\")) {\n                    root.toggled = false\n                }\n            }\n        }\n    }\n    tooltipText: Translation.tr(\"Cloudflare WARP (1.1.1.1)\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/ColorPickerToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Color picker\")\n    hasStatusText: false\n    toggled: false\n    icon: \"colorize\"\n\n    mainAction: () => {\n        GlobalStates.sidebarRightOpen = false;\n        delayedActionTimer.start();\n    }\n    Timer {\n        id: delayedActionTimer\n        interval: 300\n        repeat: false\n        onTriggered: {\n            Quickshell.execDetached([\"hyprpicker\", \"-a\"]);\n        }\n    }\n\n    tooltipText: Translation.tr(\"Color picker\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/DarkModeToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Dark Mode\")\n    statusText: Appearance.m3colors.darkmode ? Translation.tr(\"Dark\") : Translation.tr(\"Light\")\n\n    toggled: Appearance.m3colors.darkmode\n    icon: \"contrast\"\n    \n    mainAction: () => {\n        if (Appearance.m3colors.darkmode) {\n            Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", \"light\", \"--noswitch\"]);\n        } else {\n            Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", \"dark\", \"--noswitch\"]);\n        }\n    }\n\n    tooltipText: Translation.tr(\"Dark Mode\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/EasyEffectsToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"EasyEffects\")\n\n    available: EasyEffects.available\n    toggled: EasyEffects.active\n    icon: \"graphic_eq\"\n\n    Component.onCompleted: {\n        EasyEffects.fetchActiveState()\n    }\n\n    mainAction: () => {\n        EasyEffects.toggle()\n    }\n\n    altAction: () => {\n        Quickshell.execDetached([\"bash\", \"-c\", \"flatpak run com.github.wwmm.easyeffects || easyeffects\"])\n        GlobalStates.sidebarRightOpen = false\n    }\n\n    tooltipText: Translation.tr(\"EasyEffects | Right-click to configure\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/GameModeToggle.qml",
    "content": "import QtQuick\nimport Quickshell.Io\nimport qs.modules.common.models.hyprland\nimport qs.services\n\nQuickToggleModel {\n    id: root\n    name: Translation.tr(\"Game mode\")\n    toggled: !confOpt.value\n    icon: \"gamepad\"\n\n    mainAction: () => {\n        root.toggled = !root.toggled;\n        if (root.toggled) {\n            HyprlandConfig.setMany({\n                \"animations:enabled\": 0,\n                \"decoration:shadow:enabled\": 0,\n                \"decoration:blur:enabled\": 0,\n                \"general:gaps_in\": 0,\n                \"general:gaps_out\": 0,\n                \"general:border_size\": 1,\n                \"decoration:rounding\": 0,\n                \"general:allow_tearing\": 1\n            });\n        } else {\n            HyprlandConfig.resetMany([ //\n                \"animations:enabled\", //\n                \"decoration:shadow:enabled\", //\n                \"decoration:blur:enabled\", //\n                \"general:gaps_in\", //\n                \"general:gaps_out\", //\n                \"general:border_size\", //\n                \"decoration:rounding\", //\n                \"general:allow_tearing\", //\n            ]);\n        }\n    }\n\n    HyprlandConfigOption {\n        id: confOpt\n        key: \"animations:enabled\"\n    }\n\n    tooltipText: Translation.tr(\"Game mode\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/IdleInhibitorToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Keep awake\")\n\n    toggled: Idle.inhibit\n    icon: \"coffee\"\n    mainAction: () => {\n        Idle.toggleInhibit()\n    }\n    tooltipText: Translation.tr(\"Keep system awake\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/MicToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Audio input\")\n    statusText: toggled ? Translation.tr(\"Enabled\") : Translation.tr(\"Muted\")\n    toggled: !Audio.source?.audio?.muted\n    icon: Audio.source?.audio?.muted ? \"mic_off\" : \"mic\"\n    mainAction: () => {\n        Audio.toggleMicMute()\n    }\n    hasMenu: true\n\n    tooltipText: Translation.tr(\"Audio input | Right-click for volume mixer & device selector\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/MusicRecognitionToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    toggled: SongRec.running\n    property bool sourceIsMonitor: SongRec.monitorSource === SongRec.MonitorSource.Monitor\n\n    name: Translation.tr(\"Identify Music\")\n    statusText: toggled ? Translation.tr(\"Listening...\") : sourceIsMonitor ? Translation.tr(\"System sound\") : Translation.tr(\"Microphone\")\n    icon: toggled ? \"music_cast\" : (sourceIsMonitor ? \"music_note\" : \"frame_person_mic\")\n\n    tooltipText: Translation.tr(\"Recognize music | Right-click to toggle source\")\n\n    mainAction: () => {\n        SongRec.toggleRunning()\n    }\n    altAction: () => {\n        SongRec.toggleMonitorSource()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/NetworkToggle.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Internet\")\n    statusText: Network.networkName\n    tooltipText: Translation.tr(\"%1 | Right-click to configure\").arg(Network.networkName)\n    icon: Network.materialSymbol\n\n    toggled: Network.wifiStatus !== \"disabled\"\n    mainAction: () => Network.toggleWifi()\n    hasMenu: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/NightLightToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    property bool auto: Config.options.light.night.automatic\n\n    name: Translation.tr(\"Night Light\")\n    statusText: (auto ? Translation.tr(\"Auto, \") : \"\") + (toggled ? Translation.tr(\"Active\") : Translation.tr(\"Inactive\"))\n\n    toggled: Hyprsunset.temperatureActive\n    icon: auto ? \"night_sight_auto\" : \"bedtime\"\n    \n    mainAction: () => {\n        Hyprsunset.toggleTemperature()\n    }\n    hasMenu: true\n\n    Component.onCompleted: {\n        Hyprsunset.fetchState()\n    }\n    \n    tooltipText: Translation.tr(\"Night Light | Right-click to configure\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/NotificationToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Notifications\")\n    statusText: toggled ? Translation.tr(\"Show\") : Translation.tr(\"Silent\")\n    toggled: !Notifications.silent\n    icon: toggled ? \"notifications_active\" : \"notifications_paused\"\n\n    mainAction: () => {\n        Notifications.silent = !Notifications.silent;\n    }\n\n    tooltipText: Translation.tr(\"Show notifications\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/OnScreenKeyboardToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Virtual Keyboard\")\n    toggled: GlobalStates.oskOpen\n    icon: toggled ? \"keyboard_hide\" : \"keyboard\"\n    \n    mainAction: () => {\n        GlobalStates.oskOpen = !GlobalStates.oskOpen\n    }\n\n    tooltipText: Translation.tr(\"On-screen keyboard\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/PowerProfilesToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Services.UPower\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Power Profile\")\n    toggled: PowerProfiles.profile !== PowerProfile.Balanced\n    icon: switch(PowerProfiles.profile) {\n        case PowerProfile.PowerSaver: return \"energy_savings_leaf\"\n        case PowerProfile.Balanced: return \"airwave\"\n        case PowerProfile.Performance: return \"local_fire_department\"\n    }\n    statusText: switch(PowerProfiles.profile) {\n        case PowerProfile.PowerSaver: return \"Power Saver\"\n        case PowerProfile.Balanced: return \"Balanced\"\n        case PowerProfile.Performance: return \"Performance\"\n    }\n    \n    mainAction: () => {\n        if (PowerProfiles.hasPerformanceProfile) {\n            switch(PowerProfiles.profile) {\n                case PowerProfile.PowerSaver: PowerProfiles.profile = PowerProfile.Balanced\n                break;\n                case PowerProfile.Balanced: PowerProfiles.profile = PowerProfile.Performance\n                break;\n                case PowerProfile.Performance: PowerProfiles.profile = PowerProfile.PowerSaver\n                break;\n            }\n        } else {\n            PowerProfiles.profile = PowerProfiles.profile == PowerProfile.Balanced ? PowerProfile.PowerSaver : PowerProfile.Balanced\n        }\n    }\n    tooltipText: Translation.tr(\"Click to cycle through power profiles\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/QuickToggleModel.qml",
    "content": "import QtQuick\n\nQtObject {\n    // Textual info\n    required property string name\n    property string statusText\n    property string tooltipText: \"\"\n    property string icon: \"close\"\n\n    // State\n    property bool hasStatusText: true\n    property bool available: true\n    property bool toggled: false\n\n    // Interactions\n    required property var mainAction\n    property bool hasMenu: false\n    property var altAction: null\n\n    // Allow stuff like Processes to be declared freely\n    default property list<QtObject> data\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/models/quickToggles/ScreenSnipToggle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nQuickToggleModel {\n    name: Translation.tr(\"Screen snip\")\n    hasStatusText: false\n    toggled: false\n    icon: \"screenshot_region\"\n\n    mainAction: () => {\n        GlobalStates.sidebarRightOpen = false;\n        delayedActionTimer.start();\n    }\n    Timer {\n        id: delayedActionTimer\n        interval: 300\n        repeat: false\n        onTriggered: {\n            Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"region\", \"screenshot\"]);\n        }\n    }\n\n    tooltipText: Translation.tr(\"Screen snip\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/panels/lock/LockContext.qml",
    "content": "import qs\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Services.Pam\n\nScope {\n    id: root\n\n    enum ActionEnum { Unlock, Poweroff, Reboot }\n\n    signal shouldReFocus()\n    signal unlocked(targetAction: var)\n    signal failed()\n\n    // These properties are in the context and not individual lock surfaces\n    // so all surfaces can share the same state.\n    property string currentText: \"\"\n    property bool unlockInProgress: false\n    property bool showFailure: false\n    property bool fingerprintsConfigured: false\n    property var targetAction: LockContext.ActionEnum.Unlock\n    property bool alsoInhibitIdle: false\n\n    function resetTargetAction() {\n        root.targetAction = LockContext.ActionEnum.Unlock;\n    }\n\n    function clearText() {\n        root.currentText = \"\";\n    }\n\n    function resetClearTimer() {\n        passwordClearTimer.restart();\n    }\n\n    function reset() {\n        root.resetTargetAction();\n        root.clearText();\n        root.unlockInProgress = false;\n        stopFingerPam();\n    }\n\n    Timer {\n        id: passwordClearTimer\n        interval: 10000\n        onTriggered: {\n            root.reset();\n        }\n    }\n\n    onCurrentTextChanged: {\n        if (currentText.length > 0) {\n            showFailure = false;\n            GlobalStates.screenUnlockFailed = false;\n        }\n        GlobalStates.screenLockContainsCharacters = currentText.length > 0;\n        passwordClearTimer.restart();\n    }\n\n    function tryUnlock(alsoInhibitIdle = false) {\n        root.alsoInhibitIdle = alsoInhibitIdle;\n        root.unlockInProgress = true;\n        pam.start();\n    }\n\n    function tryFingerUnlock() {\n        if (root.fingerprintsConfigured) {\n            fingerPam.start();\n        }\n    }\n\n    function stopFingerPam() {\n        if (fingerPam.active) {\n            fingerPam.abort();\n        }\n    }\n\n    Process {\n        id: fingerprintCheckProc\n        running: true\n        command: [\"bash\", \"-c\", \"fprintd-list $(whoami)\"]\n        stdout: StdioCollector {\n            id: fingerprintOutputCollector\n            onStreamFinished: {\n                root.fingerprintsConfigured = fingerprintOutputCollector.text.includes(\"Fingerprints for user\");\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode !== 0) {\n                // console.warn(\"[LockContext] fprintd-list command exited with error:\", exitCode, exitStatus);\n                root.fingerprintsConfigured = false;\n            }\n        }\n    }\n    \n    PamContext {\n        id: pam\n\n        // pam_unix will ask for a response for the password prompt\n        onPamMessage: {\n            if (this.responseRequired) {\n                this.respond(root.currentText);\n            }\n        }\n\n        // pam_unix won't send any important messages so all we need is the completion status.\n        onCompleted: result => {\n            if (result == PamResult.Success) {\n                root.unlocked(root.targetAction);\n                stopFingerPam();\n            } else {\n                root.clearText();\n                root.unlockInProgress = false;\n                GlobalStates.screenUnlockFailed = true;\n                root.showFailure = true;\n            }\n        }\n    }\n\n    PamContext {\n        id: fingerPam\n\n        configDirectory: \"pam\"\n        config: \"fprintd.conf\"\n\n        onCompleted: result => {\n            if (result == PamResult.Success) {\n                root.unlocked(root.targetAction);\n                stopFingerPam();\n            } else if (result == PamResult.Error) { // if timeout or etc..\n                tryFingerUnlock()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/panels/lock/LockScreen.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n\n    required property Component lockSurface\n    property alias context: lockContext\n    property Component sessionLockSurface: WlSessionLockSurface {\n        id: sessionLockSurface\n        color: \"transparent\"\n        Loader {\n            active: GlobalStates.screenLocked\n            anchors.fill: parent\n            opacity: active ? 1 : 0\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n            sourceComponent: root.lockSurface\n        }\n    }\n\n    Process {\n        id: unlockKeyringProc\n        onExited: (exitCode, exitStatus) => {\n            KeyringStorage.fetchKeyringData();\n        }\n    }\n    function unlockKeyring() {\n        unlockKeyringProc.exec({\n            environment: ({\n                \"UNLOCK_PASSWORD\": lockContext.currentText\n            }),\n            command: [\"bash\", \"-c\", Quickshell.shellPath(\"scripts/keyring/unlock.sh\")]\n        })\n    }\n\n    // This stores all the information shared between the lock surfaces on each screen.\n    // https://github.com/quickshell-mirror/quickshell-examples/tree/master/lockscreen\n    LockContext {\n        id: lockContext\n\n        Connections {\n            target: GlobalStates\n            function onScreenLockedChanged() {\n                if (GlobalStates.screenLocked) {\n                    lockContext.reset();\n                    lockContext.tryFingerUnlock();\n                }\n            }\n        }\n\n        onUnlocked: (targetAction) => {\n            // Perform the target action if it's not just unlocking\n            if (targetAction == LockContext.ActionEnum.Poweroff) {\n                Session.poweroff();\n                return;\n            } else if (targetAction == LockContext.ActionEnum.Reboot) {\n                Session.reboot();\n                return;\n            }\n\n            // Unlock the keyring if configured to do so\n            if (Config.options.lock.security.unlockKeyring) root.unlockKeyring(); // Async\n\n            // Unlock the screen before exiting, or the compositor will display a\n            // fallback lock you can't interact with.\n            GlobalStates.screenLocked = false;\n\n            // Reset\n            lockContext.reset();\n\n            // Post-unlock actions\n            if (lockContext.alsoInhibitIdle) {\n                lockContext.alsoInhibitIdle = false;\n                Idle.toggleInhibit(true);\n            }\n        }\n    }\n\n    WlSessionLock {\n        id: lock\n        locked: GlobalStates.screenLocked\n        surface: root.sessionLockSurface\n    }\n\n    function lock() {\n        if (Config.options.lock.useHyprlock) {\n            Quickshell.execDetached([\"bash\", \"-c\", \"pidof hyprlock || hyprlock\"]);\n            return;\n        }\n        GlobalStates.screenLocked = true;\n    }\n\n    IpcHandler {\n        target: \"lock\"\n\n        function activate(): void {\n            root.lock();\n        }\n        function focus(): void {\n            lockContext.shouldReFocus();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"lock\"\n        description: \"Locks the screen\"\n\n        onPressed: {\n            root.lock()\n        }\n    }\n\n    GlobalShortcut {\n        name: \"lockFocus\"\n        description: \"Re-focuses the lock screen. This is because Hyprland after waking up for whatever reason\"\n            + \"decides to keyboard-unfocus the lock screen\"\n\n        onPressed: {\n            lockContext.shouldReFocus();\n        }\n    }\n\n    function initIfReady() {\n        if (!Config.ready || !Persistent.ready) return;\n        if (Config.options.lock.launchOnStartup && Persistent.isNewHyprlandInstance) {\n            root.lock();\n        } else {\n            KeyringStorage.fetchKeyringData();\n        }\n    }\n    Connections {\n        target: Config\n        function onReadyChanged() {\n            root.initIfReady();\n        }\n    }\n    Connections {\n        target: Persistent\n        function onReadyChanged() {\n            root.initIfReady();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/panels/lock/pam/fprintd.conf",
    "content": "auth    sufficient    pam_fprintd.so"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/utils/ImageDownloaderProcess.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common\nimport qs.modules.common.functions\n\nProcess {\n    id: root\n\n    signal done(string path, int width, int height);\n    required property string filePath;\n    required property string sourceUrl;\n    property string downloadUserAgent: Config.options?.networking.userAgent ?? \"\"\n    \n    function processFilePath() {\n        return StringUtils.shellSingleQuoteEscape(FileUtils.trimFileProtocol(filePath));\n    }\n\n    running: true\n    command: [\"bash\", \"-c\", \n        `mkdir -p $(dirname '${processFilePath()}'); [ -f '${processFilePath()}' ] || curl -sSL '${sourceUrl}' -o '${processFilePath()}' && file '${processFilePath()}'`\n    ]\n    stdout: StdioCollector {\n        id: imageSizeOutputCollector\n        onStreamFinished: {\n            const output = imageSizeOutputCollector.text.trim();\n            const match = output.match(/(\\d+)\\s*x\\s*(\\d+)/);\n\n            if (match) {\n                const width = Number(match[1]);\n                const height = Number(match[2]);\n                root.done(root.filePath, width, height);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/utils/MultiTurnProcess.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell.Io\n\nProcess {\n    id: proc\n\n    signal finished(string output)\n\n    property list<var> sequence: []\n    property int index: 0\n\n    // Runs a sequence of command and functions\n    function runSequence(seq) {\n        if (seq.length == 0)\n            return;\n        sequence = seq;\n        running = false;\n        index = -1;\n        step();\n    }\n\n    function step(output) {\n        index++;\n        if (index >= sequence.length) {\n            finished(output);\n            return;\n        }\n        const nextItem = sequence[index];\n        if (typeof nextItem === \"function\") {\n            const nextOutput = nextItem(output);\n            step(nextOutput);\n        } else {\n            // If empty command then not set; this allows setting it with previous function\n            if (nextItem.length > 0)\n                command = nextItem;\n            running = true;\n        }\n    }\n\n    stdout: StdioCollector {\n        onStreamFinished: {\n            proc.step(text);\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/utils/ScreenshotAction.qml",
    "content": "pragma ComponentBehavior: Bound\npragma Singleton\nimport qs.modules.common\nimport qs.modules.common.utils\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport Qt.labs.synchronizer\nimport Quickshell\n\nSingleton {\n    id: root\n\n    enum Action {\n        Copy,\n        Edit,\n        Search,\n        CharRecognition,\n        Record,\n        RecordWithSound\n    }\n\n    property string imageSearchEngineBaseUrl: Config.options.search.imageSearch.imageSearchEngineBaseUrl\n    property string fileUploadApiEndpoint: \"https://uguu.se/upload\"\n\n    function getCommand(x, y, width, height, screenshotPath, action, saveDir = \"\") {\n        // Set command for action\n        const rx = Math.round(x);\n        const ry = Math.round(y);\n        const rw = Math.round(width);\n        const rh = Math.round(height);\n        const cropBase = `magick ${StringUtils.shellSingleQuoteEscape(screenshotPath)} `\n            + `-crop ${rw}x${rh}+${rx}+${ry} +repage`\n        const cropToStdout = `${cropBase} -`\n        const cropInPlace = `${cropBase} '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`\n        const cleanup = `rm '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`\n        const slurpRegion = `${rx},${ry} ${rw}x${rh}`\n        const uploadAndGetUrl = (filePath) => {\n            return `curl -sF files[]=@'${StringUtils.shellSingleQuoteEscape(filePath)}' ${root.fileUploadApiEndpoint} | jq -r '.files[0].url'`\n        }\n        const annotationCommand = `${Config.options.regionSelector.annotation.useSatty ? \"satty\" : \"swappy\"} -f -`;\n        switch (action) {\n            case ScreenshotAction.Action.Copy:\n                if (saveDir === \"\") {\n                    // not saving the screenshot, just copy to clipboard\n                    return [\"bash\", \"-c\", `${cropToStdout} | wl-copy && ${cleanup}`]\n                    break;\n                }\n                return [\n                    \"bash\", \"-c\",\n                    `mkdir -p '${StringUtils.shellSingleQuoteEscape(saveDir)}' && \\\n                    saveFileName=\"screenshot-$(date '+%Y-%m-%d_%H.%M.%S').png\" && \\\n                    savePath=\"${saveDir}/$saveFileName\" && \\\n                    ${cropToStdout} | tee >(wl-copy) > \"$savePath\" && \\\n                    ${cleanup}`\n                ]\n\n                break;\n            case ScreenshotAction.Action.Edit:\n                return [\"bash\", \"-c\", `${cropToStdout} | ${annotationCommand} && ${cleanup}`]\n                break;\n            case ScreenshotAction.Action.Search:\n                return [\"bash\", \"-c\", `${cropInPlace} && xdg-open \"${root.imageSearchEngineBaseUrl}$(${uploadAndGetUrl(screenshotPath)})\" && ${cleanup}`]\n                break;\n            case ScreenshotAction.Action.CharRecognition:\n                return [\"bash\", \"-c\", `${cropInPlace} && tesseract '${StringUtils.shellSingleQuoteEscape(screenshotPath)}' stdout -l $(tesseract --list-langs | awk 'NR>1{print $1}' | tr '\\\\n' '+' | sed 's/\\\\+$/\\\\n/') | wl-copy && ${cleanup}`]\n                break;\n            case ScreenshotAction.Action.Record:\n                return [\"bash\", \"-c\", `${Directories.recordScriptPath} --region '${slurpRegion}'`]\n                break;\n            case ScreenshotAction.Action.RecordWithSound:\n                return [\"bash\", \"-c\", `${Directories.recordScriptPath} --region '${slurpRegion}' --sound`]\n                break;\n            default:\n                console.warn(\"[Region Selector] Unknown snip action, skipping snip.\");\n                return;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/utils/TempScreenshotProcess.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common\nimport qs.modules.common.functions\n\nProcess {\n    id: screenshotProc\n    running: true\n    property string screenshotDir: Directories.screenshotTemp\n    required property ShellScreen screen\n    property string screenshotPath: `${screenshotDir}/image-${screen.name}`\n    command: [\"bash\", \"-c\", `mkdir -p '${StringUtils.shellSingleQuoteEscape(screenshotDir)}' && grim -o '${StringUtils.shellSingleQuoteEscape(screen.name)}' '${StringUtils.shellSingleQuoteEscape(screenshotPath)}'`]\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/AddressBar.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nRectangle {\n    id: root\n    required property var directory\n    property bool showBreadcrumb: true\n    onShowBreadcrumbChanged: {\n        addressInput.text = root.directory;\n    }\n\n    signal navigateToDirectory(string path)\n\n    property real padding: 6\n    implicitWidth: mainLayout.implicitWidth + padding * 2\n    implicitHeight: mainLayout.implicitHeight + padding * 2\n    color: Appearance.colors.colLayer2\n\n    function focusBreadcrumb() {\n        root.showBreadcrumb = false;\n        addressInput.forceActiveFocus();\n    }\n\n    RowLayout {\n        id: mainLayout\n        anchors {\n            fill: parent\n            margins: root.padding\n        }\n        spacing: 8\n\n        RippleButton {\n            id: parentDirButton\n            downAction: () => root.navigateToDirectory(FileUtils.parentDirectory(root.directory))\n            contentItem: MaterialSymbol {\n                text: \"drive_folder_upload\"\n                iconSize: Appearance.font.pixelSize.larger\n            }\n        }\n\n        Item {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n\n            Rectangle {\n                id: directoryEntry\n                visible: !root.showBreadcrumb\n                anchors.fill: parent\n                color: Appearance.colors.colLayer1\n                radius: Appearance.rounding.full\n                implicitWidth: addressInput.implicitWidth\n                implicitHeight: addressInput.implicitHeight\n\n                Keys.onPressed: event => {\n                    if (directoryEntry.visible && event.key === Qt.Key_Escape) {\n                        root.showBreadcrumb = true;\n                        event.accepted = true;\n                        return;\n                    }\n                    event.accepted = false;\n                }\n\n                StyledTextInput {\n                    id: addressInput\n                    anchors.fill: parent\n                    padding: 10\n                    text: root.directory\n\n                    Keys.onPressed: event => {\n                        if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n                            root.navigateToDirectory(text);\n                            root.showBreadcrumb = true;\n                            event.accepted = true;\n                        }\n                    }\n\n                    MouseArea {\n                        // I-beam cursor\n                        anchors.fill: parent\n                        acceptedButtons: Qt.NoButton\n                        hoverEnabled: true\n                        cursorShape: Qt.IBeamCursor\n                    }\n                }\n            }\n\n            Loader {\n                id: breadcrumbLoader\n                active: root.showBreadcrumb\n                visible: root.showBreadcrumb\n                anchors.fill: parent\n                sourceComponent: AddressBreadcrumb {\n                    directory: root.directory\n                    onNavigateToDirectory: dir => {\n                        root.navigateToDirectory(dir);\n                    }\n                }\n            }\n        }\n\n        RippleButton {\n            id: dirEditButton\n            toggled: !root.showBreadcrumb\n            downAction: () => root.showBreadcrumb = !root.showBreadcrumb\n            contentItem: MaterialSymbol {\n                text: \"edit\"\n                iconSize: Appearance.font.pixelSize.larger\n                color: dirEditButton.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2\n            }\n\n            StyledToolTip {\n                text: Translation.tr(\"Edit directory\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/AddressBreadcrumb.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nListView {\n    id: root\n    required property var directory\n    property var breadcrumbDirectory: \"\"\n    Component.onCompleted: breadcrumbDirectory = directory;\n    onDirectoryChanged: {\n        if (breadcrumbDirectory.startsWith(directory)) return;\n        breadcrumbDirectory = directory\n    }\n\n    signal navigateToDirectory(string path)\n\n    orientation: ListView.Horizontal\n    clip: true\n    spacing: 2\n\n    model: breadcrumbDirectory.split(\"/\")\n    delegate: SelectionGroupButton {\n        id: folderButton\n        required property var modelData\n        required property int index\n        buttonText: index === 0 ? \"/\" : modelData\n        toggled: {\n            if (directory.trim() === \"/\") return index === 0;\n            return index === directory.split(\"/\").length - 1\n        }\n        leftmost: index === 0\n        rightmost: index === breadcrumbDirectory.split(\"/\").length - 1\n\n        onClicked: {\n            root.navigateToDirectory(breadcrumbDirectory.split(\"/\").slice(0, index + 1).join(\"/\"))\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ButtonGroup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\n/**\n * A container that supports GroupButton children for bounciness.\n * See https://m3.material.io/components/button-groups/overview\n */\nRectangle {\n    id: root\n    default property alias groupData: rowLayout.data\n    property alias uniformCellSizes: rowLayout.uniformCellSizes\n    property real spacing: 5\n    property real padding: 0\n    property alias clickIndex: rowLayout.clickIndex\n    property alias childrenCount: rowLayout.childrenCount\n\n    property real contentWidth: {\n        let total = 0;\n        for (let i = 0; i < rowLayout.children.length; ++i) {\n            const child = rowLayout.children[i];\n            if (!child.visible) continue;\n            total += child.baseWidth ?? child.implicitWidth ?? child.width;\n        }\n        return total + rowLayout.spacing * (rowLayout.children.length - 1);\n    }\n\n    topLeftRadius: rowLayout.children.length > 0 ? (rowLayout.children[0].radius + padding) : \n        Appearance?.rounding?.small\n    bottomLeftRadius: topLeftRadius\n    topRightRadius: rowLayout.children.length > 0 ? (rowLayout.children[rowLayout.children.length - 1].radius + padding) : \n        Appearance?.rounding?.small\n    bottomRightRadius: topRightRadius\n\n    color: \"transparent\"\n    width: root.contentWidth + padding * 2\n    implicitHeight: rowLayout.implicitHeight + padding * 2\n    implicitWidth: root.contentWidth + padding * 2\n    \n    children: [RowLayout {\n        id: rowLayout\n        anchors.fill: parent\n        anchors.margins: root.padding\n        spacing: root.spacing\n        property int clickIndex: -1\n        property int childrenCount: children.length\n    }]\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/CalendarView.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQml\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n\n    // Expose delegate\n    property Component delegate: Text {\n        required property var model\n        text: model.day\n    }\n\n    // Configuration\n    property int paddingWeeks: 2 // 1 should be sufficient with proper clipping and no padding\n    property var locale: Qt.locale() // Should be of type Locale but QML is being funny\n\n    // Scrolling\n    function scrollMonthsAndSnap(x) { // Scroll x months and snap to month\n        const focusedDate = root.focusedDate;\n        const focusedMonth = focusedDate.getMonth();\n        const focusedYear = focusedDate.getFullYear();\n        const targetMonth = focusedMonth + x;\n        const targetDate = new Date(focusedYear, targetMonth, 1);\n        const currentFirstShownDate = new Date(root.dateInFirstWeek.getTime() + (root.paddingWeeks * root.millisPerWeek));\n        const diffMillis = targetDate.getTime() - currentFirstShownDate.getTime();\n        const diffWeeks = Math.round(diffMillis / root.millisPerWeek);\n        root.targetWeekDiff += diffWeeks;\n    }\n    property int weeksPerScroll: 1\n    property real targetWeekDiff: 0\n    property real weekDiff: targetWeekDiff\n    property int contentWeekDiff: weekDiff // whole part of weekDiff\n    property bool scrolling: false\n\n    Behavior on weekDiff {\n        id: weekScrollBehavior\n        animation: Looks.transition.scroll.createObject(this)\n    }\n    Timer {\n        id: scrollAnimationCheckTimer\n        interval: 30 // Should be plenty for 60fps\n        onTriggered: root.scrolling = false;\n    }\n    onWeekDiffChanged: {\n        scrolling = true;\n        scrollAnimationCheckTimer.restart();\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        onWheel: wheel => {\n            root.targetWeekDiff += wheel.angleDelta.y / 120 * -root.weeksPerScroll; // Reverse cuz scrolling down should advance\n        }\n    }\n\n    // Date calculations\n    readonly property int millisPerWeek: 7 * 24 * 60 * 60 * 1000\n    readonly property int totalWeeks: 6 + (paddingWeeks * 2)\n    readonly property int focusedWeekIndex: 2 // The third row, 0-indexed\n    readonly property int focusDayOfWeekIndex: 6\n    property date dateInFirstWeek: {\n        const currentDate = new Date();\n        const currentMonth = currentDate.getMonth();\n        const currentYear = currentDate.getFullYear();\n        const firstDayThisMonth = new Date(currentYear, currentMonth, 1);\n        return new Date(firstDayThisMonth.getTime() - (paddingWeeks * millisPerWeek) + contentWeekDiff * millisPerWeek);\n    }\n    property date focusedDate: {\n        // The last day of 3rd week shown is considered the focused month\n        const addedTime = (root.paddingWeeks + root.focusedWeekIndex) * root.millisPerWeek\n        const dateInTargetWeek = new Date(root.dateInFirstWeek.getTime() + addedTime);\n        return DateUtils.getIthDayDateOfSameWeek(dateInTargetWeek, root.focusDayOfWeekIndex - root.locale.firstDayOfWeek, root.locale.firstdayOfWeek); // 4 = Thursday\n    }\n    property int focusedMonth: focusedDate.getMonth() + 1 // 0-indexed -> 1-indexed\n\n    // Sizes\n    property real verticalPadding: 0\n    property real buttonSize: 40\n    property real buttonSpacing: 2\n    property real buttonVerticalSpacing: buttonSpacing\n    implicitHeight: (6 * buttonSize) + (5 * buttonVerticalSpacing) + (2 * verticalPadding)\n    implicitWidth: weeksColumn.implicitWidth\n    clip: true\n    \n    ColumnLayout {\n        id: weeksColumn\n        anchors {\n            left: parent.left\n            right: parent.right\n        }\n        y: {\n            const spacePerExtraRow = root.buttonSize + root.buttonVerticalSpacing;\n            const origin = -(spacePerExtraRow * root.paddingWeeks);\n            const diff = root.weekDiff * spacePerExtraRow;\n            return origin + (-diff % spacePerExtraRow) + root.verticalPadding;\n        }\n\n        spacing: root.buttonVerticalSpacing\n        \n        Repeater {\n            model: root.totalWeeks\n\n            WeekRow {\n                required property int index\n                locale: root.locale\n                date: new Date(root.dateInFirstWeek.getTime() + (index * root.millisPerWeek))\n                Layout.fillWidth: true\n                spacing: root.buttonSpacing\n                delegate: root.delegate\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/Circle.qml",
    "content": "import QtQuick\n\nRectangle {\n    property double diameter\n\n    implicitWidth: diameter\n    implicitHeight: diameter\n    radius: diameter / 2\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/CircularProgress.qml",
    "content": "import QtQuick\nimport QtQuick.Shapes\nimport qs.modules.common\n\n/**\n * Material 3 circular progress. See https://m3.material.io/components/progress-indicators/specs\n */\nItem {\n    id: root\n\n    property int implicitSize: 30\n    property int lineWidth: 2\n    property real value: 0\n    property color colPrimary: Appearance.m3colors.m3onSecondaryContainer\n    property color colSecondary: Appearance.colors.colSecondaryContainer\n    property real gapAngle: 360 / 18\n    property bool fill: false\n    property int fillOverflow: 2\n    property bool enableAnimation: true\n    property int animationDuration: 800\n    property var easingType: Easing.OutCubic\n\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    property real degree: value * 360\n    property real centerX: root.width / 2\n    property real centerY: root.height / 2\n    property real arcRadius: root.implicitSize / 2 - root.lineWidth\n    property real startAngle: -90\n\n    Behavior on degree {\n        enabled: root.enableAnimation\n        NumberAnimation {\n            duration: root.animationDuration\n            easing.type: root.easingType\n        }\n\n    }\n\n    Loader {\n        active: root.fill\n        anchors.fill: parent\n        \n        sourceComponent: Rectangle {\n            radius: 9999\n            color: root.colSecondary\n        }\n    }\n\n    Shape {\n        anchors.fill: parent\n        layer.enabled: true\n        layer.smooth: true\n        preferredRendererType: Shape.CurveRenderer\n        ShapePath {\n            id: secondaryPath\n            strokeColor: root.colSecondary\n            strokeWidth: root.lineWidth\n            capStyle: ShapePath.RoundCap\n            fillColor: \"transparent\"\n            PathAngleArc {\n                centerX: root.centerX\n                centerY: root.centerY\n                radiusX: root.arcRadius\n                radiusY: root.arcRadius\n                startAngle: root.startAngle - root.gapAngle\n                sweepAngle: -(360 - root.degree - 2 * root.gapAngle)\n            }\n        }\n        ShapePath {\n            id: primaryPath\n            strokeColor: root.colPrimary\n            strokeWidth: root.lineWidth\n            capStyle: ShapePath.RoundCap\n            fillColor: \"transparent\"\n            PathAngleArc {\n                centerX: root.centerX\n                centerY: root.centerY\n                radiusX: root.arcRadius\n                radiusY: root.arcRadius\n                startAngle: root.startAngle\n                sweepAngle: root.degree\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/CliphistImage.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nRectangle {\n    id: root\n    property string entry\n    property real maxWidth\n    property real maxHeight\n    property bool blur: false\n    property string blurText: \"Image hidden\"\n\n    property string imageDecodePath: Directories.cliphistDecode\n    property string imageDecodeFileName: `${entryNumber}`\n    property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`\n    property string source\n\n    property int entryNumber: {\n        if (!root.entry)\n            return 0;\n        const match = root.entry.match(/^(\\d+)\\t/);\n        return match ? parseInt(match[1]) : 0;\n    }\n    property int imageWidth: {\n        if (!root.entry)\n            return 0;\n        const match = root.entry.match(/(\\d+)x(\\d+)/);\n        return match ? parseInt(match[1]) : 0;\n    }\n    property int imageHeight: {\n        if (!root.entry)\n            return 0;\n        const match = root.entry.match(/(\\d+)x(\\d+)/);\n        return match ? parseInt(match[2]) : 0;\n    }\n    property real scale: {\n        return Math.min(root.maxWidth / imageWidth, root.maxHeight / imageHeight, 1);\n    }\n\n    color: Appearance.colors.colLayer1\n    radius: Appearance.rounding.small\n    implicitHeight: imageHeight * scale\n    implicitWidth: imageWidth * scale\n\n    Component.onCompleted: {\n        decodeImageProcess.running = true;\n    }\n\n    Process {\n        id: decodeImageProcess\n        command: [\"bash\", \"-c\", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(root.entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode === 0) {\n                root.source = imageDecodeFilePath;\n            } else {\n                console.error(\"[CliphistImage] Failed to decode image for entry:\", root.entry);\n                root.source = \"\";\n            }\n        }\n    }\n\n    Component.onDestruction: {\n        Quickshell.execDetached([\"bash\", \"-c\", `[ -f '${imageDecodeFilePath}' ] && rm -f '${imageDecodeFilePath}'`]);\n    }\n\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Rectangle {\n            width: image.width\n            height: image.height\n            radius: root.radius\n        }\n    }\n\n    StyledImage {\n        id: image\n        anchors.fill: parent\n\n        source: Qt.resolvedUrl(root.source)\n        fillMode: Image.PreserveAspectFit\n        antialiasing: true\n        asynchronous: true\n\n        width: root.imageWidth * root.scale\n        height: root.imageHeight * root.scale\n    }\n\n    Loader {\n        id: blurLoader\n        active: root.blur\n        anchors.fill: image\n        sourceComponent: GaussianBlur {\n            source: image\n            radius: 35\n            samples: radius * 2 + 1\n\n            Rectangle {\n                anchors.fill: parent\n                color: ColorUtils.transparentize(Appearance.colors.colLayer0, 0.5)\n\n                Column {\n                    anchors {\n                        left: parent.left\n                        right: parent.right\n                        verticalCenter: parent.verticalCenter\n                    }\n                    MaterialSymbol {\n                        visible: width <= image.width\n                        anchors.horizontalCenter: parent.horizontalCenter\n                        text: \"visibility_off\"\n                        font.pixelSize: 28\n                    }\n                    StyledText {\n                        visible: width <= image.width\n                        anchors.horizontalCenter: parent.horizontalCenter\n                        text: root.blurText\n                        color: Appearance.colors.colOnSurface\n                        font.pixelSize: Appearance.font.pixelSize.smallie\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ClippedFilledCircularProgress.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Shapes\nimport Qt5Compat.GraphicalEffects\n\nItem {\n    id: root\n\n    property int implicitSize: 18\n    property int lineWidth: 2\n    property real value: 0\n    property color colPrimary: Appearance?.colors.colOnSecondaryContainer ?? \"#685496\"\n    property color colSecondary: ColorUtils.transparentize(colPrimary, 0.5) ?? \"#F1D3F9\"\n    property real gapAngle: 360 / 18\n    property bool fill: true\n    property int fillOverflow: 2\n    property bool enableAnimation: true\n    property int animationDuration: 800\n    property var easingType: Easing.OutCubic\n    property bool accountForLightBleeding: true\n    default property Item textMask: Item {\n        width: implicitSize\n        height: implicitSize\n        StyledText {\n            anchors.centerIn: parent\n            text: Math.round(root.value * 100)\n            font.pixelSize: 12\n            font.weight: Font.Medium\n        }\n    }\n\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    property real degree: value * 360\n    property real centerX: root.width / 2\n    property real centerY: root.height / 2\n    property real arcRadius: root.implicitSize / 2 - root.lineWidth / 2 - (0.5 * root.accountForLightBleeding)\n    property real startAngle: -90\n\n    Behavior on degree {\n        enabled: root.enableAnimation\n        NumberAnimation {\n            duration: root.animationDuration\n            easing.type: root.easingType\n        }\n\n    }\n\n    Rectangle {\n        id: contentItem\n        anchors.fill: parent\n        radius: implicitSize / 2\n        color: root.colSecondary\n        visible: false\n        layer.enabled: true\n        layer.smooth: true\n\n        Shape {\n            anchors.fill: parent\n            preferredRendererType: Shape.CurveRenderer\n\n            ShapePath {\n                id: primaryPath\n                pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting\n                strokeColor: root.colPrimary\n                strokeWidth: root.lineWidth\n                capStyle: ShapePath.RoundCap\n                fillColor: root.colPrimary\n\n                startX: root.centerX\n                startY: root.centerY\n\n                PathAngleArc {\n                    moveToStart: false\n                    centerX: root.centerX\n                    centerY: root.centerY\n                    radiusX: root.arcRadius\n                    radiusY: root.arcRadius\n                    startAngle: root.startAngle\n                    sweepAngle: root.degree\n                }\n                PathLine {\n                    x: primaryPath.startX\n                    y: primaryPath.startY\n                }\n            }\n        }\n    }\n\n    OpacityMask {\n        anchors.fill: parent\n        source: contentItem\n        invert: true\n        maskSource: root.textMask\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ClippedProgressBar.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport Qt5Compat.GraphicalEffects\n\n/**\n * A progress bar with both ends rounded and text acts as clipping like OneUI 7's battery indicator.\n */\nProgressBar {\n    id: root\n    property bool vertical: false\n    property real valueBarWidth: 30\n    property real valueBarHeight: 18\n    property color highlightColor: Appearance?.colors.colOnSecondaryContainer ?? \"#685496\"\n    property color trackColor: ColorUtils.transparentize(highlightColor, 0.5) ?? \"#F1D3F9\"\n    property alias radius: contentItem.radius\n    property string text\n    default property Item textMask: Item {\n        width: valueBarWidth\n        height: valueBarHeight\n        StyledText {\n            anchors.centerIn: parent\n            font: root.font\n            text: root.text\n        }\n    }\n\n    text: Math.round(value * 100)\n    font {\n        pixelSize: 13\n        weight: text.length > 2 ? Font.Medium : Font.DemiBold\n    }\n\n    background: Item {\n        implicitHeight: valueBarHeight\n        implicitWidth: valueBarWidth\n    }\n\n    contentItem: Rectangle {\n        id: contentItem\n        anchors.fill: parent\n        radius: 9999\n        color: root.trackColor\n        visible: false\n\n        Rectangle {\n            id: progressFill\n            anchors {\n                top: parent.top\n                bottom: parent.bottom\n                left: parent.left\n                right: undefined\n            }\n            width: parent.width * root.visualPosition\n            height: parent.height\n\n            states: State {\n                name: \"vertical\"\n                when: root.vertical\n                AnchorChanges {\n                    target: progressFill\n                    anchors {\n                        top: undefined\n                        bottom: parent.bottom\n                        left: parent.left\n                        right: parent.right\n                    }\n                }\n                PropertyChanges {\n                    target: progressFill\n                    width: parent.width\n                    height: parent.height * root.visualPosition\n                }\n            }\n\n            radius: Appearance.rounding.unsharpen\n            color: root.highlightColor\n        }\n    }\n\n    OpacityMask {\n        id: roundingMask\n        visible: false\n        anchors.fill: parent\n        source: contentItem\n        maskSource: Rectangle {\n            width: contentItem.width\n            height: contentItem.height\n            radius: contentItem.radius\n        }\n    }\n\n    OpacityMask {\n        anchors.fill: parent\n        source: roundingMask\n        invert: true\n        maskSource: root.textMask\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ConfigRow.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\n\nRowLayout {\n    property bool uniform: false\n    spacing: 4\n    uniformCellSizes: uniform\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ConfigSelectionArray.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nFlow {\n    id: root\n    Layout.fillWidth: true\n    spacing: 2\n    property list<var> options: [\n        {\n            \"displayName\": \"Option 1\",\n            \"icon\": \"check\",\n            \"value\": 1\n        },\n        {\n            \"displayName\": \"Option 2\",\n            \"icon\": \"close\",\n            \"value\": 2\n        },\n    ]\n    property var currentValue: null\n\n    signal selected(var newValue)\n\n    Repeater {\n        model: root.options\n        delegate: SelectionGroupButton {\n            id: paletteButton\n            required property var modelData\n            required property int index\n            onYChanged: {\n                if (index === 0) {\n                    paletteButton.leftmost = true\n                } else {\n                    var prev = root.children[index - 1]\n                    var thisIsOnNewLine = prev && prev.y !== paletteButton.y\n                    paletteButton.leftmost = thisIsOnNewLine\n                    prev.rightmost = thisIsOnNewLine\n                }\n            }\n            leftmost: index === 0\n            rightmost: index === root.options.length - 1\n            buttonIcon: modelData.icon || \"\"\n            buttonText: modelData.displayName\n            toggled: root.currentValue == modelData.value\n            onClicked: {\n                root.selected(modelData.value);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ConfigSlider.qml",
    "content": "import qs.modules.common.widgets\nimport qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\nimport qs.services\n\nRowLayout {\n    id: root\n    spacing: 10\n    Layout.leftMargin: 8\n    Layout.rightMargin: 8\n\n    property string text: \"\"\n    property string buttonIcon: \"\"\n    property alias value: slider.value\n    property alias stopIndicatorValues: slider.stopIndicatorValues\n    property bool usePercentTooltip: true\n    property real from: slider.from\n    property real to: slider.to\n    property real textWidth: 120\n\n    RowLayout {\n        id: row\n        spacing: 10\n\n        OptionalMaterialSymbol {\n            id: iconWidget\n            icon: root.buttonIcon\n            iconSize: Appearance.font.pixelSize.larger\n        }\n        StyledText {\n            id: labelWidget\n            Layout.preferredWidth: root.textWidth\n            text: root.text\n            color: Appearance.colors.colOnSecondaryContainer\n        }\n    }\n    \n    StyledSlider {\n        id: slider\n        configuration: StyledSlider.Configuration.XS\n        usePercentTooltip: root.usePercentTooltip\n        value: root.value\n        from: root.from\n        to: root.to\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ConfigSpinBox.qml",
    "content": "import qs.modules.common.widgets\nimport qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\n\nRowLayout {\n    id: root\n    property string text: \"\"\n    property string icon\n    property alias value: spinBoxWidget.value\n    property alias stepSize: spinBoxWidget.stepSize\n    property alias from: spinBoxWidget.from\n    property alias to: spinBoxWidget.to\n    spacing: 10\n    Layout.leftMargin: 8\n    Layout.rightMargin: 8\n\n    RowLayout {\n        spacing: 10\n        OptionalMaterialSymbol {\n            icon: root.icon\n            opacity: root.enabled ? 1 : 0.4\n        }\n        StyledText {\n            id: labelWidget\n            Layout.fillWidth: true\n            text: root.text\n            color: Appearance.colors.colOnSecondaryContainer\n            opacity: root.enabled ? 1 : 0.4\n        }\n    }\n\n    StyledSpinBox {\n        id: spinBoxWidget\n        Layout.fillWidth: false\n        value: root.value\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ConfigSwitch.qml",
    "content": "import qs.modules.common.widgets\nimport qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\nimport QtQuick.Controls\n\nRippleButton {\n    id: root\n    property string buttonIcon\n    property alias iconSize: iconWidget.iconSize\n\n    Layout.fillWidth: true\n    implicitHeight: contentItem.implicitHeight + 8 * 2\n    font.pixelSize: Appearance.font.pixelSize.small\n    \n    onClicked: checked = !checked\n\n    contentItem: RowLayout {\n        spacing: 10\n        OptionalMaterialSymbol {\n            id: iconWidget\n            icon: root.buttonIcon\n            opacity: root.enabled ? 1 : 0.4\n            iconSize: Appearance.font.pixelSize.larger\n        }\n        StyledText {\n            id: labelWidget\n            Layout.fillWidth: true\n            text: root.text\n            font: root.font\n            color: Appearance.colors.colOnSecondaryContainer\n            opacity: root.enabled ? 1 : 0.4\n        }\n        StyledSwitch {\n            id: switchWidget\n            down: root.down\n            Layout.fillWidth: false\n            checked: root.checked\n            onClicked: root.clicked()\n        }\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ContentPage.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nStyledFlickable {\n    id: root\n    property real baseWidth: 600\n    property bool forceWidth: false\n    property real bottomContentPadding: 100\n\n    default property alias contentData: contentColumn.data\n\n    clip: true\n    contentHeight: contentColumn.implicitHeight + root.bottomContentPadding // Add some padding at the bottom\n    implicitWidth: contentColumn.implicitWidth\n    \n    ColumnLayout {\n        id: contentColumn\n        width: root.forceWidth ? root.baseWidth : Math.max(root.baseWidth, implicitWidth)\n        anchors {\n            top: parent.top\n            horizontalCenter: parent.horizontalCenter\n            margins: 20\n        }\n        spacing: 30\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ContentSection.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nColumnLayout {\n    id: root\n    property string title\n    property string icon: \"\"\n    default property alias contentData: sectionContent.data\n\n    Layout.fillWidth: true\n    spacing: 6\n\n    RowLayout {\n        spacing: 6\n        OptionalMaterialSymbol {\n            icon: root.icon\n            iconSize: Appearance.font.pixelSize.hugeass\n        }\n        StyledText {\n            text: root.title\n            font.pixelSize: Appearance.font.pixelSize.larger\n            font.weight: Font.Medium\n            color: Appearance.colors.colOnSecondaryContainer\n        }\n    }\n\n    ColumnLayout {\n        id: sectionContent\n        Layout.fillWidth: true\n        spacing: 4\n\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ContentSubsection.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nColumnLayout {\n    id: root\n    property string title: \"\"\n    property string tooltip: \"\"\n    default property alias contentData: sectionContent.data\n\n    Layout.fillWidth: true\n    Layout.topMargin: 4\n    spacing: 2\n\n    RowLayout {\n        ContentSubsectionLabel {\n            visible: root.title && root.title.length > 0\n            text: root.title\n        }\n        MaterialSymbol {\n            visible: root.tooltip && root.tooltip.length > 0\n            text: \"info\"\n            iconSize: Appearance.font.pixelSize.large\n            \n            color: Appearance.colors.colSubtext\n            MouseArea {\n                id: infoMouseArea\n                anchors.fill: parent\n                hoverEnabled: true\n                cursorShape: Qt.WhatsThisCursor\n                StyledToolTip {\n                    extraVisibleCondition: false\n                    alternativeVisibleCondition: infoMouseArea.containsMouse\n                    text: root.tooltip\n                }\n            }\n        }\n        Item { Layout.fillWidth: true }\n    }\n    ColumnLayout {\n        id: sectionContent\n        Layout.fillWidth: true\n        spacing: 2\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ContentSubsectionLabel.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nStyledText {\n    text: \"Subsection\"\n    color: Appearance.colors.colSubtext\n    Layout.leftMargin: 2\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/CustomIcon.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Widgets\nimport Qt5Compat.GraphicalEffects\n\nItem {\n    id: root\n    \n    property bool colorize: false\n    property color color\n    property string source: \"\"\n    property string iconFolder: Qt.resolvedUrl(Quickshell.shellPath(\"assets/icons\"))  // The folder to check first\n    width: 30\n    height: 30\n    \n    IconImage {\n        id: iconImage\n        anchors.fill: parent\n        source: {\n            const fullPathWhenSourceIsIconName = iconFolder + \"/\" + root.source;\n            if (iconFolder && fullPathWhenSourceIsIconName) {\n                return fullPathWhenSourceIsIconName\n            }\n            return root.source\n        }\n        implicitSize: root.height\n    }\n\n    Loader {\n        active: root.colorize\n        anchors.fill: iconImage\n        sourceComponent: ColorOverlay {\n            source: iconImage\n            color: root.color\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/DashedBorder.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\n\nCanvas {\n    id: root\n    property color color: \"#ffffff\"\n    property int dashLength: 6\n    property int gapLength: 4\n    property int borderWidth: 1\n\n    onDashLengthChanged: requestPaint()\n    onGapLengthChanged: requestPaint()\n    onWidthChanged: requestPaint()\n    onHeightChanged: requestPaint()\n    onPaint: {\n        var ctx = getContext(\"2d\");\n        ctx.clearRect(0, 0, width, height);\n        ctx.save();\n        ctx.strokeStyle = root.color;\n        ctx.lineWidth = root.borderWidth;\n        if (root.gapLength > 0) {\n            ctx.setLineDash([root.dashLength, root.gapLength]); // Set dash pattern\n        }\n        ctx.strokeRect(root.borderWidth / 2, root.borderWidth / 2, width - root.borderWidth, height - root.borderWidth); // Draw it\n        ctx.restore();\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/DialogButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\n\n/**\n * Material 3 dialog button. See https://m3.material.io/components/dialogs/overview\n */\nRippleButton {\n    id: root\n\n    property string buttonText\n    padding: 14\n    implicitHeight: 36\n    implicitWidth: buttonTextWidget.implicitWidth + padding * 2\n    buttonRadius: Appearance?.rounding.full ?? 9999\n\n    property color colEnabled: Appearance?.colors.colPrimary ?? \"#65558F\"\n    property color colDisabled: Appearance?.m3colors.m3outline ?? \"#8D8C96\"\n    colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)\n    colBackgroundHover: Appearance.colors.colLayer3Hover\n    colRipple: Appearance.colors.colLayer3Active\n    property alias colText: buttonTextWidget.color\n\n    contentItem: StyledText {\n        id: buttonTextWidget\n        anchors.fill: parent\n        anchors.leftMargin: root.padding\n        anchors.rightMargin: root.padding\n        text: buttonText\n        horizontalAlignment: Text.AlignHCenter\n        font.pixelSize: Appearance?.font.pixelSize.small ?? 12\n        color: root.enabled ? root.colEnabled : root.colDisabled\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/DialogListItem.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\n\nRippleButton {\n    id: root\n    property bool active: false\n\n    horizontalPadding: Appearance.rounding.large\n    verticalPadding: 12\n\n    clip: true\n    pointingHandCursor: !active    \n    implicitWidth: contentItem.implicitWidth + horizontalPadding * 2\n    implicitHeight: contentItem.implicitHeight + verticalPadding * 2\n    Behavior on implicitHeight {\n        animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n    }\n\n    colBackground: ColorUtils.transparentize(Appearance.colors.colLayer3)\n    colBackgroundHover: active ? colBackground : Appearance.colors.colLayer3Hover\n    colRipple: Appearance.colors.colLayer3Active\n    buttonRadius: 0\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/DirectoryIcon.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common\nimport qs.modules.common.functions\n\n// From https://github.com/caelestia-dots/shell with modifications.\n// License: GPLv3\n\nStyledImage {\n    id: root\n    required property var fileModelData\n    asynchronous: true\n    fillMode: Image.PreserveAspectFit\n\n    source: {\n        if (!fileModelData.fileIsDir)\n            return Quickshell.iconPath(\"application-x-zerosize\");\n\n        if ([Directories.documents, Directories.downloads, Directories.music, Directories.pictures, Directories.videos].some(dir => FileUtils.trimFileProtocol(dir) === fileModelData.filePath))\n            return Quickshell.iconPath(`folder-${fileModelData.fileName.toLowerCase()}`);\n\n        return Quickshell.iconPath(\"inode-directory\");\n    }\n\n    onStatusChanged: {\n        if (status === Image.Error)\n            source = Quickshell.iconPath(\"error\");\n    }\n\n    Process {\n        running: !fileModelData.fileIsDir\n        command: [\"file\", \"--mime\", \"-b\", fileModelData.filePath]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                const mime = text.split(\";\")[0].replace(\"/\", \"-\");\n                root.source = Images.validImageTypes.some(t => mime === `image-${t}`) ? fileModelData.fileUrl : Quickshell.iconPath(mime, \"image-missing\");\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/DragManager.qml",
    "content": "import qs.modules.common\nimport qs.services\nimport QtQuick\n\n/**\n * A convenience MouseArea for handling drag events.\n */\nMouseArea {\n    id: root\n    hoverEnabled: true\n    acceptedButtons: Qt.LeftButton\n\n    property bool interactive: true\n    property bool automaticallyReset: true\n    readonly property real dragDiffX: _dragDiffX\n    readonly property real dragDiffY: _dragDiffY\n    property real startX: 0\n    property real startY: 0\n    property real regionTopLeftX: Math.min(startX, startX + _dragDiffX)\n    property real regionTopLeftY: Math.min(startY, startY + _dragDiffY)\n    property real regionWidth: Math.abs(_dragDiffX)\n    property real regionHeight: Math.abs(_dragDiffY)\n\n    signal dragPressed(diffX: real, diffY: real)\n    signal dragReleased(diffX: real, diffY: real)\n    \n    property bool dragging: false\n    property real _dragDiffX: 0\n    property real _dragDiffY: 0\n\n    function resetDrag() {\n        _dragDiffX = 0\n        _dragDiffY = 0\n    }\n\n    onPressed: (mouse) => {\n        if (!root.interactive) {\n            if (mouse.button === Qt.LeftButton) {\n                mouse.accepted = false;\n            }\n            return;\n        }\n        if (mouse.button === Qt.LeftButton) {\n            startX = mouse.x\n            startY = mouse.y\n        }\n    }\n    onReleased: (mouse) => {\n        if (!root.interactive) {\n            return;\n        }\n        dragging = false\n        root.dragReleased(_dragDiffX, _dragDiffY);\n        if (root.automaticallyReset) {\n            root.resetDrag();\n        }\n    }\n    onPositionChanged: (mouse) => {\n        if (!root.interactive) {\n            return;\n        }\n        if (mouse.buttons & Qt.LeftButton) {\n            root._dragDiffX = mouse.x - startX\n            root._dragDiffY = mouse.y - startY\n            const dist = Math.sqrt(root._dragDiffX * root._dragDiffX + root._dragDiffY * root._dragDiffY);\n            root.dragPressed(_dragDiffX, _dragDiffY);\n            root.dragging = true;\n        }\n    }\n    onCanceled: (mouse) => {\n        if (!root.interactive) {\n            return;\n        }\n        released(mouse);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ErrorShakeAnimation.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\n\nSequentialAnimation {\n    id: root\n\n    required property Item target\n    property real distance: 30\n\n    NumberAnimation { target: root.target; property: \"Layout.leftMargin\"; to: -root.distance; duration: 50 }\n    NumberAnimation { target: root.target; property: \"Layout.leftMargin\"; to: root.distance; duration: 50 }\n    NumberAnimation { target: root.target; property: \"Layout.leftMargin\"; to: -root.distance / 2; duration: 40 }\n    NumberAnimation { target: root.target; property: \"Layout.leftMargin\"; to: root.distance / 2; duration: 40 }\n    NumberAnimation { target: root.target; property: \"Layout.leftMargin\"; to: 0; duration: 30 }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/FadeLoader.qml",
    "content": "import QtQuick\n\nimport qs.modules.common\n\nLoader {\n    id: root\n    property bool shown: true\n    property alias fade: opacityBehavior.enabled\n    property alias animation: opacityBehavior.animation\n    opacity: shown ? 1 : 0\n    visible: opacity > 0\n    active: opacity > 0\n\n    Behavior on opacity {\n        id: opacityBehavior\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/Favicon.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport Quickshell.Io\nimport Quickshell.Widgets\n\nIconImage {\n    id: root\n    property string url\n    property string displayText\n\n    property real size: 32\n    property string downloadUserAgent: Config.options?.networking.userAgent ?? \"\"\n    property string faviconDownloadPath: Directories.favicons\n    property string domainName: url.includes(\"vertexaisearch\") ? displayText : StringUtils.getDomain(url)\n    property string faviconUrl: `https://www.google.com/s2/favicons?domain=${domainName}&sz=32`\n    property string fileName: `${domainName}.ico`\n    property string faviconFilePath: `${faviconDownloadPath}/${fileName}`\n    property string urlToLoad\n\n    Process {\n        id: faviconDownloadProcess\n        running: false\n        command: [\"bash\", \"-c\", `[ -f ${faviconFilePath} ] || curl -s '${root.faviconUrl}' -o '${faviconFilePath}' -L -H 'User-Agent: ${downloadUserAgent}'`]\n        onExited: (exitCode, exitStatus) => {\n            root.urlToLoad = root.faviconFilePath\n        }\n    }\n\n    Component.onCompleted: {\n        faviconDownloadProcess.running = true\n    }\n\n    source: Qt.resolvedUrl(root.urlToLoad)\n    implicitSize: root.size\n\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Rectangle {\n            width: root.implicitSize\n            height: root.implicitSize\n            radius: Appearance.rounding.full\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/FloatingActionButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\n/**\n * Material 3 FAB.\n */\nRippleButton {\n    id: root\n    property string iconText: \"add\"\n    property bool expanded: false\n    property real baseSize: 56\n    property real elementSpacing: 5\n    implicitWidth: expanded ? (Math.max(contentRowLayout.implicitWidth + 10 * 2, baseSize)) : baseSize\n    implicitHeight: baseSize\n    buttonRadius: baseSize / 14 * 4\n    colBackground: Appearance.colors.colPrimaryContainer\n    colBackgroundHover: Appearance.colors.colPrimaryContainerHover\n    colRipple: Appearance.colors.colPrimaryContainerActive\n    property color colOnBackground: Appearance.colors.colOnPrimaryContainer\n    contentItem: Row {\n        id: contentRowLayout\n        property real horizontalMargins: (root.baseSize - icon.width) / 2\n        anchors {\n            verticalCenter: parent?.verticalCenter\n            left: parent?.left\n            leftMargin: contentRowLayout.horizontalMargins\n        }\n        spacing: 0\n\n        MaterialSymbol {\n            id: icon\n            anchors.verticalCenter: parent.verticalCenter\n            horizontalAlignment: Text.AlignHCenter\n            verticalAlignment: Text.AlignVCenter\n            iconSize: 26\n            color: root.colOnBackground\n            text: root.iconText\n        }\n        Loader {\n            anchors.verticalCenter: parent.verticalCenter\n            visible: root.buttonText?.length > 0\n            active: true\n            sourceComponent: Revealer {\n                visible: root.expanded || implicitWidth > 0\n                reveal: root.expanded\n                implicitWidth: reveal ? (buttonText.implicitWidth + root.elementSpacing + contentRowLayout.horizontalMargins) : 0\n                StyledText {\n                    id: buttonText\n                    anchors {\n                        left: parent.left\n                        leftMargin: root.elementSpacing\n                        verticalCenter: parent.verticalCenter\n                    }\n                    text: root.buttonText\n                    color: Appearance.colors.colOnPrimaryContainer\n                    font.pixelSize: 14\n                    font.weight: 450\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/FlowButtonGroup.qml",
    "content": "import QtQuick\n\n/** \n * This is just to make sure `RippleButton`s can be used in a Flow layout.\n */\nFlow {\n    property int clickIndex: -1\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/FocusedScrollMouseArea.qml",
    "content": "import QtQuick\n\nMouseArea { // Right side | scroll to change volume\n    id: root\n\n    signal scrollUp(delta: int)\n    signal scrollDown(delta: int)\n    signal movedAway()\n\n    property bool hovered: false\n    property real lastScrollX: 0\n    property real lastScrollY: 0\n    property bool trackingScroll: false\n    property real moveThreshold: 20\n\n    acceptedButtons: Qt.LeftButton\n    hoverEnabled: true\n\n    onEntered: {\n        root.hovered = true;\n    }\n\n    onExited: {\n        root.hovered = false;\n        root.trackingScroll = false;\n    }\n\n    onWheel: event => {\n        if (event.angleDelta.y < 0)\n            root.scrollDown(event.angleDelta.y);\n        else if (event.angleDelta.y > 0)\n            root.scrollUp(event.angleDelta.y);\n        // Store the mouse position and start tracking\n        root.lastScrollX = event.x;\n        root.lastScrollY = event.y;\n        root.trackingScroll = true;\n    }\n\n    onPositionChanged: mouse => {\n        if (root.trackingScroll) {\n            const dx = mouse.x - root.lastScrollX;\n            const dy = mouse.y - root.lastScrollY;\n            if (Math.sqrt(dx * dx + dy * dy) > root.moveThreshold) {\n                root.movedAway();\n                root.trackingScroll = false;\n            }\n        }\n    }\n\n    onContainsMouseChanged: {\n        if (!root.containsMouse && root.trackingScroll) {\n            root.movedAway();\n            root.trackingScroll = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/FullscreenPolkitWindow.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Wayland\n\nScope {\n    id: root\n    required property Component contentComponent\n    \n    Loader {\n        active: PolkitService.active\n        sourceComponent: Variants {\n            model: Quickshell.screens\n            delegate: PanelWindow {\n                id: panelWindow\n                required property var modelData\n                screen: modelData\n                \n                anchors {\n                    top: true\n                    left: true\n                    right: true\n                    bottom: true\n                }\n\n                color: \"transparent\"\n                WlrLayershell.namespace: \"quickshell:polkit\"\n                WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n                WlrLayershell.layer: WlrLayer.Overlay\n                exclusionMode: ExclusionMode.Ignore\n\n                Loader {\n                    anchors.fill: parent\n                    sourceComponent: root.contentComponent\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/Graph.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\n\n/*\n * Simple one value line graph\n */\nCanvas {\n    id: root\n\n    enum Alignment { Left, Right }\n\n    required property list<real> values\n    property int points: values.length\n    property color color: Appearance.colors.colPrimary\n    property real fillOpacity: 0.5\n    property var alignment: Graph.Alignment.Left\n\n    onValuesChanged: root.requestPaint()\n    onPaint: {\n        var ctx = getContext(\"2d\")\n        ctx.clearRect(0, 0, width, height)\n        if (!root.values || root.values.length < 2)\n            return\n\n        var n = root.points\n        var dx = width / (n - 1)\n        ctx.strokeStyle = root.color\n        ctx.fillStyle = ColorUtils.transparentize(root.color, 1 - root.fillOpacity)\n        ctx.lineWidth = 2\n        ctx.beginPath()\n        for (var i = 0; i < n; ++i) {\n            var valueIndex = (root.alignment === Graph.Alignment.Right) ? root.values.length - n + i : i\n            if (valueIndex < 0 || valueIndex >= root.values.length) {\n                continue; // No data for this point\n            }\n            var x = i * dx\n            var norm = root.values[valueIndex] // already in 0-1 range\n            var y = height - norm * height\n            if (valueIndex === 0) {\n                ctx.moveTo(x, height)\n                ctx.lineTo(x, y)\n            } else {\n                ctx.lineTo(x, y)\n            }\n        }\n        ctx.stroke()\n        ctx.lineTo(width, height)\n        ctx.fill()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/GroupButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\n/**\n * Material 3 button with expressive bounciness. \n * See https://m3.material.io/components/button-groups/overview\n */\nButton {\n    id: root\n    property bool toggled\n    property string buttonText\n    property real buttonRadius: Appearance?.rounding?.small ?? 8\n    property real buttonRadiusPressed: Appearance?.rounding?.small ?? 6\n    property var downAction // When left clicking (down)\n    property var releaseAction // When left clicking (release)\n    property var altAction // When right clicking\n    property var middleClickAction // When middle clicking\n    property bool bounce: true\n    property real baseWidth: contentItem.implicitWidth + horizontalPadding * 2\n    property real baseHeight: contentItem.implicitHeight + verticalPadding * 2\n    property bool enableImplicitWidthAnimation: true\n    property bool enableImplicitHeightAnimation: true\n    property real clickedWidth: baseWidth + (isAtSide ? 10 : 20)\n    property real clickedHeight: baseHeight\n    property var parentGroup: root.parent\n    property int indexInParent: parentGroup?.children.indexOf(root) ?? -1\n    property int clickIndex: parentGroup?.clickIndex ?? -1\n    property bool isAtSide: indexInParent === 0 || indexInParent === (parentGroup?.childrenCount - 1)\n\n    Layout.fillWidth: (clickIndex - 1 <= indexInParent && indexInParent <= clickIndex + 1)\n    Layout.fillHeight: (clickIndex - 1 <= indexInParent && indexInParent <= clickIndex + 1)\n    implicitWidth: (root.down && bounce) ? clickedWidth : baseWidth\n    implicitHeight: (root.down && bounce) ? clickedHeight : baseHeight\n\n    property color colBackground: ColorUtils.transparentize(colBackgroundHover, 1) || \"transparent\"\n    property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? \"#E5DFED\"\n    property color colBackgroundActive: Appearance?.colors.colLayer1Active ?? \"#D6CEE2\"\n    property color colBackgroundToggled: Appearance?.colors.colPrimary ?? \"#65558F\"\n    property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? \"#77699C\"\n    property color colBackgroundToggledActive: Appearance?.colors.colPrimaryActive ?? \"#D6CEE2\"\n\n    property real radius: root.down ? root.buttonRadiusPressed : root.buttonRadius\n    property real leftRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius\n    property real rightRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius\n    property color color: root.enabled ? (root.toggled ? \n        (root.down ? colBackgroundToggledActive : \n            root.hovered ? colBackgroundToggledHover : \n            colBackgroundToggled) :\n        (root.down ? colBackgroundActive : \n            root.hovered ? colBackgroundHover : \n            colBackground)) : colBackground\n\n    onDownChanged: {\n        if (root.down) {\n            if (root.parent.clickIndex !== undefined) {\n                root.parent.clickIndex = parent.children.indexOf(root)\n            }\n        }\n    }\n\n    Behavior on implicitWidth {\n        enabled: root.enableImplicitWidthAnimation\n        animation: Appearance.animation.clickBounce.numberAnimation.createObject(this)\n    }\n\n    Behavior on implicitHeight {\n        enabled: root.enableImplicitHeightAnimation\n        animation: Appearance.animation.clickBounce.numberAnimation.createObject(this)\n    }\n\n    Behavior on leftRadius {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n    Behavior on rightRadius {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    property alias mouseArea: buttonMouseArea\n    MouseArea {\n        id: buttonMouseArea\n        anchors.fill: parent\n        cursorShape: Qt.PointingHandCursor\n        acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton\n        onPressed: (event) => { \n            if(event.button === Qt.RightButton) {\n                if (root.altAction) root.altAction();\n                return;\n            }\n            if(event.button === Qt.MiddleButton) {\n                if (root.middleClickAction) root.middleClickAction();\n                return;\n            }\n            root.down = true\n            if (root.downAction) root.downAction();\n        }\n        onReleased: (event) => {\n            root.down = false\n            if (event.button != Qt.LeftButton) return;\n            if (root.releaseAction) root.releaseAction();\n        }\n        onClicked: (event) => {\n            if (event.button != Qt.LeftButton) return;\n            root.click()\n        }\n        onCanceled: (event) => {\n            root.down = false\n        }\n\n        onPressAndHold: () => {\n            altAction(); \n            root.down = false; \n            root.clicked = false;\n        };\n    }\n\n    property bool tabbedTo: root.focus && (focusReason === Qt.TabFocusReason || focusReason === Qt.BacktabFocusReason)\n    background: Rectangle {\n        id: buttonBackground\n        topLeftRadius: root.leftRadius\n        topRightRadius: root.rightRadius\n        bottomLeftRadius: root.leftRadius\n        bottomRightRadius: root.rightRadius\n        implicitHeight: 50\n\n        color: root.color\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        border.width: root.tabbedTo ? 2 : 0\n        border.color: Appearance.colors.colSecondary\n    }\n\n    contentItem: StyledText {\n        text: root.buttonText\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/IconAndTextToolbarButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\n\nToolbarButton {\n    id: iconBtn\n    required property string iconText\n\n    colBackgroundToggled: Appearance.colors.colSecondaryContainer\n    colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n    colRippleToggled: Appearance.colors.colSecondaryContainerActive\n    property color colText: toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant\n\n    contentItem: Row {\n        anchors.centerIn: parent\n        spacing: 4\n\n        MaterialSymbol {\n            anchors.verticalCenter: parent.verticalCenter\n            horizontalAlignment: Text.AlignHCenter\n            verticalAlignment: Text.AlignVCenter\n            iconSize: 22\n            text: iconBtn.iconText\n            color: iconBtn.colText\n        }\n        StyledText {\n            visible: iconBtn.iconText.length > 0 && iconBtn.text.length > 0\n            anchors.verticalCenter: parent.verticalCenter\n            color: iconBtn.colText\n            text: iconBtn.text\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/IconToolbarButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\n\nToolbarButton {\n    id: iconBtn\n    implicitWidth: height\n\n    colBackgroundToggled: Appearance.colors.colSecondaryContainer\n    colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n    colRippleToggled: Appearance.colors.colSecondaryContainerActive\n    property color colText: toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant\n\n    contentItem: MaterialSymbol {\n        anchors.centerIn: parent\n        horizontalAlignment: Text.AlignHCenter\n        verticalAlignment: Text.AlignVCenter\n        iconSize: 22\n        text: iconBtn.text\n        color: iconBtn.colText\n        animateChange: true\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/KeyboardKey.qml",
    "content": "import qs.modules.common\nimport QtQuick\n\nRectangle {\n    id: root\n    property string key\n\n    property real horizontalPadding: 6\n    property real verticalPadding: 1\n    property real borderWidth: 1\n    property real extraBottomBorderWidth: 2\n    property color borderColor: Appearance.colors.colOnLayer0\n    property real borderRadius: 5\n    property real pixelSize: Appearance.font.pixelSize.smaller\n    property color keyColor: Appearance.m3colors.m3surfaceContainerLow\n    implicitWidth: keyFace.implicitWidth + borderWidth * 2\n    implicitHeight: keyFace.implicitHeight + borderWidth * 2 + extraBottomBorderWidth\n    radius: borderRadius\n    color: borderColor\n\n    Rectangle {\n        id: keyFace\n        anchors {\n            fill: parent\n            topMargin: borderWidth\n            leftMargin: borderWidth\n            rightMargin: borderWidth\n            bottomMargin: extraBottomBorderWidth + borderWidth\n        }\n        implicitWidth: keyText.implicitWidth + horizontalPadding * 2\n        implicitHeight: keyText.implicitHeight + verticalPadding * 2\n        color: keyColor\n        radius: borderRadius - borderWidth\n\n        StyledText {\n            id: keyText\n            anchors.centerIn: parent\n            font.family: Appearance.font.family.monospace\n            font.pixelSize: root.pixelSize\n            text: key\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/LightDarkPreferenceButton.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\n\nRippleButton {\n    id: lightDarkButtonRoot\n    required property bool dark\n    property color previewBg: dark ? ColorUtils.colorWithHueOf(\"#3f3838\", Appearance.m3colors.m3primary) : \n        ColorUtils.colorWithHueOf(\"#F7F9FF\", Appearance.m3colors.m3primary)\n    property color previewFg: dark ? Qt.lighter(previewBg, 2.2) : ColorUtils.mix(previewBg, \"#292929\", 0.85)\n    padding: 5\n    Layout.fillWidth: true\n    colBackground: Appearance.colors.colLayer2\n    toggled: Appearance.m3colors.darkmode === dark\n    onClicked: {\n        Quickshell.execDetached([\"bash\", \"-c\", `${Directories.wallpaperSwitchScriptPath} --mode ${dark ? \"dark\" : \"light\"} --noswitch`])\n    }\n    contentItem: Item {\n        anchors.centerIn: parent\n        implicitWidth: buttonContentLayout.implicitWidth\n        implicitHeight: buttonContentLayout.implicitHeight\n        ColumnLayout {\n            id: buttonContentLayout\n            anchors.centerIn: parent\n            Rectangle {\n                Layout.alignment: Qt.AlignHCenter\n                implicitWidth: 250\n                implicitHeight: skeletonColumnLayout.implicitHeight + 10 * 2\n                radius: lightDarkButtonRoot.buttonRadius - lightDarkButtonRoot.padding\n                color: lightDarkButtonRoot.previewBg\n                border {\n                    width: 1\n                    color: Appearance.m3colors.m3outlineVariant\n                }\n\n                // Some skeleton items\n                ColumnLayout {\n                    id: skeletonColumnLayout\n                    anchors.fill: parent\n                    anchors.margins: 10\n                    spacing: 10\n                    RowLayout {\n                        Rectangle {\n                            radius: Appearance.rounding.full\n                            color: lightDarkButtonRoot.previewFg\n                            implicitWidth: 50\n                            implicitHeight: 50\n                        }\n                        ColumnLayout {\n                            spacing: 4\n                            Rectangle {\n                                radius: Appearance.rounding.unsharpenmore\n                                color: lightDarkButtonRoot.previewFg\n                                Layout.fillWidth: true\n                                implicitHeight: 22\n                            }\n                            Rectangle {\n                                radius: Appearance.rounding.unsharpenmore\n                                color: lightDarkButtonRoot.previewFg\n                                Layout.fillWidth: true\n                                Layout.rightMargin: 45\n                                implicitHeight: 18\n                            }\n                        }\n                    }\n                    StyledProgressBar {\n                        Layout.topMargin: 5\n                        Layout.bottomMargin: 5\n                        Layout.fillWidth: true\n                        value: 0.7\n                        wavy: true\n                        animateWave: lightDarkButtonRoot.toggled\n                        highlightColor: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg\n                        trackColor: ColorUtils.mix(lightDarkButtonRoot.previewBg, lightDarkButtonRoot.previewFg, 0.5)\n                    }\n                    RowLayout {\n                        spacing: 2\n                        Rectangle {\n                            radius: Appearance.rounding.full\n                            color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3primary : lightDarkButtonRoot.previewFg\n                            Layout.fillWidth: true\n                            implicitHeight: 30\n                            MaterialSymbol {\n                                visible: lightDarkButtonRoot.toggled\n                                anchors.centerIn: parent\n                                horizontalAlignment: Text.AlignHCenter\n                                text: \"check\"\n                                iconSize: 20\n                                color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : lightDarkButtonRoot.previewBg\n                            }\n                        }\n                        Rectangle {\n                            radius: Appearance.rounding.unsharpenmore\n                            color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg\n                            Layout.fillWidth: true\n                            implicitHeight: 30\n                        }\n                        Rectangle {\n                            topLeftRadius: Appearance.rounding.unsharpenmore\n                            bottomLeftRadius: Appearance.rounding.unsharpenmore\n                            topRightRadius: Appearance.rounding.full\n                            bottomRightRadius: Appearance.rounding.full\n                            color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3secondaryContainer : lightDarkButtonRoot.previewFg\n                            Layout.fillWidth: true\n                            implicitHeight: 30\n                        }\n                    }\n                }\n            }\n            StyledText {\n                Layout.fillWidth: true\n                text: dark ? Translation.tr(\"Dark\") : Translation.tr(\"Light\")\n                color: lightDarkButtonRoot.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2\n                horizontalAlignment: Text.AlignHCenter\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaskMultiEffect.qml",
    "content": "import QtQuick\nimport QtQuick.Effects\n\n// Note: You still have to set sizes yourself\nMultiEffect {\n    maskEnabled: true\n    maskThresholdMin: 0.5\n    maskSpreadAtMin: 1\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialCookie.qml",
    "content": "import QtQuick\nimport QtQuick.Shapes\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.widgets.shapes\nimport \"shapes/geometry/offset.js\" as Offset\nimport \"shapes/shapes/corner-rounding.js\" as CornerRounding\nimport \"shapes/shapes/rounded-polygon.js\" as RoundedPolygon\nimport \"shapes/material-shapes.js\" as MaterialShapes\n\nItem {\n    id: root\n    property int sides: 12  \n    property int implicitSize: 100\n    property alias color: shapeCanvas.color\n\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    property var cornerRounding: new CornerRounding.CornerRounding((sides < 17 ? 1.5 : 1.1) / Math.max(sides, 1))\n\n    ShapeCanvas {\n        id: shapeCanvas\n        anchors.fill: parent\n        roundedPolygon: switch(sides) {\n            case 0: return MaterialShapes.getCircle();\n            case 1: return MaterialShapes.getCircle();\n            case 4: return MaterialShapes.getCookie4Sided();\n            case 6: return MaterialShapes.getCookie6Sided();\n            case 7: return MaterialShapes.getCookie7Sided();\n            case 9: return MaterialShapes.getCookie9Sided();\n            case 12: return MaterialShapes.getCookie12Sided();\n            default: return RoundedPolygon.RoundedPolygon.star(sides, 1, 0.8, root.cornerRounding)\n                .transformed((x, y) => MaterialShapes.rotate30.map(new Offset.Offset(x, y)))\n                .normalized();\n        }\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialLoadingIndicator.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nRectangle {\n    id: root\n\n    property bool loading: true\n    property double pullProgress: 0\n\n    // Size, color\n    property double implicitSize: 48\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n    radius: Math.min(width, height) / 2\n    color: Appearance.colors.colPrimaryContainer\n    property double baseShapeSize: root.implicitSize * 0.7\n    property double leapZoomSize: root.baseShapeSize * 1.2\n    property double leapZoomProgress: 0\n\n    // Shape\n    property list<var> shapes: [\n        MaterialShape.Shape.SoftBurst,\n        MaterialShape.Shape.Cookie9Sided,\n        MaterialShape.Shape.Pentagon,\n        MaterialShape.Shape.Pill,\n        MaterialShape.Shape.Sunny,\n        MaterialShape.Shape.Cookie4Sided,\n        MaterialShape.Shape.Oval,\n    ]\n    property int shapeIndex: 0\n    property double pullRotation: root.loading ? 0 : -(root.pullProgress * 360)\n    property double continuousRotation: 0\n    property double leapRotation: 0\n    rotation: pullRotation + continuousRotation + leapRotation\n\n    RotationAnimation on continuousRotation {\n        running: root.loading\n        duration: 12000\n        easing.type: Easing.Linear\n        loops: Animation.Infinite\n        from: 0\n        to: 360\n    }\n    Timer {\n        interval: 800\n        running: root.loading\n        repeat: true\n        onTriggered: leapAnimation.start()\n    }\n    ParallelAnimation {\n        id: leapAnimation\n        PropertyAction { target: root; property: \"shapeIndex\"; value: (root.shapeIndex + 1) % root.shapes.length }\n        RotationAnimation {\n            target: root\n            direction: RotationAnimation.Shortest\n            property: \"leapRotation\"\n            to: (root.leapRotation + 90) % 360\n            duration: 350\n            easing.type: Easing.InOutQuad\n        }\n        NumberAnimation {\n            target: root\n            property: \"leapZoomProgress\"\n            from: 0\n            to: 1\n            duration: 750\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Appearance.animationCurves.standard\n        }\n    }\n\n    MaterialShape {\n        id: shape\n        anchors.centerIn: parent\n        shape: root.shapes[root.shapeIndex]\n        implicitSize: {\n            const leapZoomDiff = root.leapZoomSize - root.baseShapeSize\n            const progressFirstHalf = Math.min(root.leapZoomProgress, 0.5) * 2;\n            const progressSecondHalf = Math.max(root.leapZoomProgress - 0.5, 0) * 2;\n            return root.baseShapeSize + leapZoomDiff * progressFirstHalf - leapZoomDiff * progressSecondHalf;\n        }\n        color: Appearance.colors.colOnPrimaryContainer\n\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialShape.qml",
    "content": "import qs.modules.common.widgets.shapes\nimport \"shapes/material-shapes.js\" as MaterialShapes\n\nShapeCanvas {\n    id: root\n    enum Shape {\n        Circle,\n        Square,\n        Slanted,\n        Arch,\n        Fan,\n        Arrow,\n        SemiCircle,\n        Oval,\n        Pill,\n        Triangle,\n        Diamond,\n        ClamShell,\n        Pentagon,\n        Gem,\n        Sunny,\n        VerySunny,\n        Cookie4Sided,\n        Cookie6Sided,\n        Cookie7Sided,\n        Cookie9Sided,\n        Cookie12Sided,\n        Ghostish,\n        Clover4Leaf,\n        Clover8Leaf,\n        Burst,\n        SoftBurst,\n        Boom,\n        SoftBoom,\n        Flower,\n        Puffy,\n        PuffyDiamond,\n        PixelCircle,\n        PixelTriangle,\n        Bun,\n        Heart\n    }\n    required property var shape\n    property double implicitSize\n    implicitHeight: implicitSize\n    implicitWidth: implicitSize\n    polygonIsNormalized: true\n    roundedPolygon: {\n        switch (root.shape) {\n            case MaterialShape.Shape.Circle: return MaterialShapes.getCircle();\n            case MaterialShape.Shape.Square: return MaterialShapes.getSquare();\n            case MaterialShape.Shape.Slanted: return MaterialShapes.getSlanted();\n            case MaterialShape.Shape.Arch: return MaterialShapes.getArch();\n            case MaterialShape.Shape.Fan: return MaterialShapes.getFan();\n            case MaterialShape.Shape.Arrow: return MaterialShapes.getArrow();\n            case MaterialShape.Shape.SemiCircle: return MaterialShapes.getSemiCircle();\n            case MaterialShape.Shape.Oval: return MaterialShapes.getOval();\n            case MaterialShape.Shape.Pill: return MaterialShapes.getPill();\n            case MaterialShape.Shape.Triangle: return MaterialShapes.getTriangle();\n            case MaterialShape.Shape.Diamond: return MaterialShapes.getDiamond();\n            case MaterialShape.Shape.ClamShell: return MaterialShapes.getClamShell();\n            case MaterialShape.Shape.Pentagon: return MaterialShapes.getPentagon();\n            case MaterialShape.Shape.Gem: return MaterialShapes.getGem();\n            case MaterialShape.Shape.Sunny: return MaterialShapes.getSunny();\n            case MaterialShape.Shape.VerySunny: return MaterialShapes.getVerySunny();\n            case MaterialShape.Shape.Cookie4Sided: return MaterialShapes.getCookie4Sided();\n            case MaterialShape.Shape.Cookie6Sided: return MaterialShapes.getCookie6Sided();\n            case MaterialShape.Shape.Cookie7Sided: return MaterialShapes.getCookie7Sided();\n            case MaterialShape.Shape.Cookie9Sided: return MaterialShapes.getCookie9Sided();\n            case MaterialShape.Shape.Cookie12Sided: return MaterialShapes.getCookie12Sided();\n            case MaterialShape.Shape.Ghostish: return MaterialShapes.getGhostish();\n            case MaterialShape.Shape.Clover4Leaf: return MaterialShapes.getClover4Leaf();\n            case MaterialShape.Shape.Clover8Leaf: return MaterialShapes.getClover8Leaf();\n            case MaterialShape.Shape.Burst: return MaterialShapes.getBurst();\n            case MaterialShape.Shape.SoftBurst: return MaterialShapes.getSoftBurst();\n            case MaterialShape.Shape.Boom: return MaterialShapes.getBoom();\n            case MaterialShape.Shape.SoftBoom: return MaterialShapes.getSoftBoom();\n            case MaterialShape.Shape.Flower: return MaterialShapes.getFlower();\n            case MaterialShape.Shape.Puffy: return MaterialShapes.getPuffy();\n            case MaterialShape.Shape.PuffyDiamond: return MaterialShapes.getPuffyDiamond();\n            case MaterialShape.Shape.PixelCircle: return MaterialShapes.getPixelCircle();\n            case MaterialShape.Shape.PixelTriangle: return MaterialShapes.getPixelTriangle();\n            case MaterialShape.Shape.Bun: return MaterialShapes.getBun();\n            case MaterialShape.Shape.Heart: return MaterialShapes.getHeart();\n            default: return MaterialShapes.getCircle();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialShapeWrappedMaterialSymbol.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nMaterialShape {\n    id: root\n    property alias text: symbol.text\n    property alias iconSize: symbol.iconSize\n    property alias font: symbol.font\n    property alias colSymbol: symbol.color\n    property real padding: 6\n\n    color: Appearance.colors.colSecondaryContainer\n    colSymbol: Appearance.colors.colOnSecondaryContainer\n    shape: MaterialShape.Shape.Clover4Leaf\n    implicitSize: Math.max(symbol.implicitWidth, symbol.implicitHeight) + padding * 2\n\n    MaterialSymbol {\n        id: symbol\n        anchors.centerIn: parent\n        color: root.colSymbol\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialSymbol.qml",
    "content": "import qs.modules.common\nimport QtQuick\n\nStyledText {\n    id: root\n    property real iconSize: Appearance?.font.pixelSize.small ?? 16\n    property real fill: 0\n    property real truncatedFill: fill.toFixed(1) // Reduce memory consumption spikes from constant font remapping\n    renderType: Text.NativeRendering\n    font {\n        hintingPreference: Font.PreferNoHinting\n        family: Appearance?.font.family.iconMaterial ?? \"Material Symbols Rounded\"\n        pixelSize: iconSize\n        weight: Font.Normal + (Font.DemiBold - Font.Normal) * truncatedFill\n        variableAxes: { \n            \"FILL\": truncatedFill,\n            // \"wght\": font.weight,\n            // \"GRAD\": 0,\n            \"opsz\": iconSize,\n        }\n    }\n\n    Behavior on fill { // Leaky leaky, no good\n        NumberAnimation {\n            duration: Appearance?.animation.elementMoveFast.duration ?? 200\n            easing.type: Appearance?.animation.elementMoveFast.type ?? Easing.BezierSpline\n            easing.bezierCurve: Appearance?.animation.elementMoveFast.bezierCurve ?? [0.34, 0.80, 0.34, 1.00, 1, 1]\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialTextArea.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls.Material\nimport QtQuick.Controls\n\n/**\n * Material 3 styled TextArea (filled style)\n * https://m3.material.io/components/text-fields/overview\n * Note: We don't use NativeRendering because it makes the small placeholder text look weird\n */\nTextArea {\n    id: root\n    Material.theme: Material.System\n    Material.accent: Appearance.m3colors.m3primary\n    Material.primary: Appearance.m3colors.m3primary\n    Material.background: Appearance.m3colors.m3surface\n    Material.foreground: Appearance.m3colors.m3onSurface\n    Material.containerStyle: Material.Filled\n    renderType: Text.QtRendering\n\n    selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n    selectionColor: Appearance.colors.colSecondaryContainer\n    placeholderTextColor: Appearance.m3colors.m3outline\n\n    background: Rectangle {\n        implicitHeight: 56\n        color: Appearance.m3colors.m3surface\n        topLeftRadius: 4\n        topRightRadius: 4\n        Rectangle {\n            anchors {\n                left: parent.left\n                right: parent.right\n                bottom: parent.bottom\n            }\n            height: 1\n            color: root.focus ? Appearance.m3colors.m3primary : \n                root.hovered ? Appearance.m3colors.m3outline : Appearance.m3colors.m3outlineVariant\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n        }\n    }\n\n    font {\n        family: Appearance.font.family.main\n        pixelSize: Appearance?.font.pixelSize.small ?? 15\n        hintingPreference: Font.PreferFullHinting\n        variableAxes: Appearance.font.variableAxes.main\n    }\n    wrapMode: TextEdit.Wrap\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MaterialTextField.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls.Material\nimport QtQuick.Controls\n\n/**\n * Material 3 styled TextField (filled style)\n * https://m3.material.io/components/text-fields/overview\n * Note: We don't use NativeRendering because it makes the small placeholder text look weird\n */\nTextField {\n    id: root\n    Material.theme: Material.System\n    Material.accent: Appearance.m3colors.m3primary\n    Material.primary: Appearance.m3colors.m3primary\n    Material.background: Appearance.m3colors.m3surface\n    Material.foreground: Appearance.m3colors.m3onSurface\n    Material.containerStyle: Material.Outlined\n    renderType: Text.QtRendering\n\n    selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n    selectionColor: Appearance.colors.colSecondaryContainer\n    placeholderTextColor: Appearance.m3colors.m3outline\n    clip: true\n\n    font {\n        family: Appearance.font.family.main\n        pixelSize: Appearance?.font.pixelSize.small ?? 15\n        hintingPreference: Font.PreferFullHinting\n        variableAxes: Appearance.font.variableAxes.main\n    }\n    wrapMode: TextEdit.Wrap\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.NoButton\n        hoverEnabled: true\n        cursorShape: Qt.IBeamCursor\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/MenuButton.qml",
    "content": "import qs.modules.common\nimport QtQuick\n\nRippleButton {\n    id: root\n\n    buttonRadius: 0\n    implicitHeight: 36\n    implicitWidth: buttonTextWidget.implicitWidth + 14 * 2\n\n    contentItem: StyledText {\n        id: buttonTextWidget\n        anchors.fill: parent\n        anchors.leftMargin: 14\n        anchors.rightMargin: 14\n        text: root.buttonText\n        horizontalAlignment: Text.AlignLeft\n        font.pixelSize: Appearance.font.pixelSize.small\n        color: root.enabled ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3outline\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NavigationRail.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nColumnLayout { // Window content with navigation rail and content pane\n    id: root\n    property bool expanded: true\n    property int currentIndex: 0\n    spacing: 5\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NavigationRailButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nTabButton {\n    id: root\n\n    property bool toggled: TabBar.tabBar.currentIndex === TabBar.index\n    property string buttonIcon\n    property real buttonIconRotation: 0\n    property string buttonText\n    property bool expanded: false\n    property bool showToggledHighlight: true\n    readonly property real visualWidth: root.expanded ? root.baseSize + 20 + itemText.implicitWidth : root.baseSize\n\n    property real baseSize: 56\n    property real baseHighlightHeight: 32\n    property real highlightCollapsedTopMargin: 8\n    padding: 0\n\n    // The navigation item’s target area always spans the full width of the\n    // nav rail, even if the item container hugs its contents.\n    Layout.fillWidth: true\n    // implicitWidth: contentItem.implicitWidth\n    implicitHeight: baseSize\n\n    background: null\n    PointingHandInteraction {}\n\n    // Real stuff\n    contentItem: Item {\n        id: buttonContent\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n            left: parent.left\n            right: undefined\n        }\n        \n        implicitWidth: root.visualWidth\n        implicitHeight: root.expanded ? itemIconBackground.implicitHeight : itemIconBackground.implicitHeight + itemText.implicitHeight \n\n        Rectangle {\n            id: itemBackground\n            anchors.top: itemIconBackground.top\n            anchors.left: itemIconBackground.left\n            anchors.bottom: itemIconBackground.bottom\n            implicitWidth: root.visualWidth\n            radius: Appearance.rounding.full\n            color: toggled ? \n                root.showToggledHighlight ?\n                    (root.down ? Appearance.colors.colSecondaryContainerActive : root.hovered ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer)\n                    : ColorUtils.transparentize(Appearance.colors.colSecondaryContainer) :\n                (root.down ? Appearance.colors.colLayer1Active : root.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1))\n\n            states: State {\n                name: \"expanded\"\n                when: root.expanded\n                AnchorChanges {\n                    target: itemBackground\n                    anchors.top: buttonContent.top\n                    anchors.left: buttonContent.left\n                    anchors.bottom: buttonContent.bottom\n                }\n                PropertyChanges {\n                    target: itemBackground\n                    implicitWidth: root.visualWidth\n                }\n            }\n            transitions: Transition {\n                AnchorAnimation {\n                    duration: Appearance.animation.elementMoveFast.duration\n                    easing.type: Appearance.animation.elementMoveFast.type\n                    easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                }\n                PropertyAnimation {\n                    target: itemBackground\n                    property: \"implicitWidth\"\n                    duration: Appearance.animation.elementMove.duration\n                    easing.type: Appearance.animation.elementMove.type\n                    easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n                }\n            }\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n        }\n\n        Item {\n            id: itemIconBackground\n            implicitWidth: root.baseSize\n            implicitHeight: root.baseHighlightHeight\n            anchors {\n                left: parent.left\n                verticalCenter: parent.verticalCenter\n            }\n            MaterialSymbol {\n                id: navRailButtonIcon\n                rotation: root.buttonIconRotation\n                anchors.centerIn: parent\n                iconSize: 24\n                fill: toggled ? 1 : 0\n                font.weight: (toggled || root.hovered) ? Font.DemiBold : Font.Normal\n                text: buttonIcon\n                color: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer1\n\n                Behavior on color {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n            }\n        }\n\n        StyledText {\n            id: itemText\n            anchors {\n                top: itemIconBackground.bottom\n                topMargin: 2\n                horizontalCenter: itemIconBackground.horizontalCenter\n            }\n            states: State {\n                name: \"expanded\"\n                when: root.expanded\n                AnchorChanges {\n                    target: itemText\n                    anchors {\n                        top: undefined\n                        horizontalCenter: undefined\n                        left: itemIconBackground.right\n                        verticalCenter: itemIconBackground.verticalCenter\n                    }\n                }\n            }\n            transitions: Transition {\n                AnchorAnimation {\n                    duration: Appearance.animation.elementMoveFast.duration\n                    easing.type: Appearance.animation.elementMoveFast.type\n                    easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                }\n            }\n            text: buttonText\n            font.pixelSize: 14\n            color: Appearance.colors.colOnLayer1\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NavigationRailExpandButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRippleButton {\n    id: root\n    Layout.alignment: Qt.AlignLeft\n    implicitWidth: 40\n    implicitHeight: 40\n    Layout.leftMargin: 8\n    downAction: () => {\n        parent.expanded = !parent.expanded;\n    }\n    buttonRadius: Appearance.rounding.full\n\n    rotation: root.parent.expanded ? 0 : -180\n    Behavior on rotation {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    contentItem: MaterialSymbol {\n        id: icon\n        anchors.centerIn: parent\n        horizontalAlignment: Text.AlignHCenter\n        iconSize: 24\n        color: Appearance.colors.colOnLayer1\n        text: root.parent.expanded ? \"menu_open\" : \"menu\"\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NavigationRailTabArray.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    property int currentIndex: 0\n    property bool expanded: false\n    default property alias tabData: tabBarColumn.data  \n    implicitHeight: tabBarColumn.implicitHeight\n    implicitWidth: tabBarColumn.implicitWidth\n    Layout.topMargin: 25\n\n    Rectangle {\n        property real itemHeight: tabBarColumn.children[0]?.baseSize ?? 56\n        property real baseHighlightHeight: tabBarColumn.children[0]?.baseHighlightHeight ?? 56\n        anchors {\n            top: tabBarColumn.top\n            left: tabBarColumn.left\n            topMargin: itemHeight * root.currentIndex + (root.expanded ? 0 : ((itemHeight - baseHighlightHeight) / 2))\n        }\n        radius: Appearance.rounding.full\n        color: Appearance.colors.colSecondaryContainer\n        implicitHeight: root.expanded ? itemHeight : baseHighlightHeight\n        implicitWidth: tabBarColumn?.children[root.currentIndex]?.visualWidth ?? 100\n\n        Behavior on anchors.topMargin {\n            NumberAnimation {\n                duration: Appearance.animationCurves.expressiveFastSpatialDuration\n                easing.type: Appearance.animation.elementMove.type\n                easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n            }\n        }\n    }\n\n    ColumnLayout {\n        id: tabBarColumn\n        anchors.fill: parent\n        spacing: 0\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NoticeBox.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nRectangle {\n    id: root\n    property alias materialIcon: icon.text\n    property alias text: noticeText.text\n    default property alias boxData: buttonRow.data\n\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colPrimaryContainer\n    implicitWidth: mainRowLayout.implicitWidth + mainRowLayout.anchors.margins * 2\n    implicitHeight: mainRowLayout.implicitHeight + mainRowLayout.anchors.margins * 2\n\n    RowLayout {\n        id: mainRowLayout\n        anchors.fill: parent\n        anchors.margins: 8\n        spacing: 8\n\n        MaterialSymbol {\n            id: icon\n            Layout.fillWidth: false\n            Layout.alignment: Qt.AlignTop\n            text: \"info\"\n            iconSize: Appearance.font.pixelSize.huge\n            color: Appearance.colors.colOnPrimaryContainer\n        }\n\n        ColumnLayout {\n            Layout.fillWidth: true\n            spacing: 4\n\n            StyledText {\n                id: noticeText\n                Layout.fillWidth: true\n                text: \"Notice message\"\n                color: Appearance.colors.colOnPrimaryContainer\n                wrapMode: Text.WordWrap\n            }\n\n            RowLayout {\n                id: buttonRow\n                visible: children.length > 0\n                Layout.fillWidth: true \n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NotificationActionButton.qml",
    "content": "import qs.modules.common\nimport qs.services\nimport QtQuick\nimport Quickshell.Services.Notifications\n\nRippleButton {\n    id: button\n    property string buttonText\n    property string urgency\n\n    implicitHeight: 34\n    leftPadding: 15\n    rightPadding: 15\n    buttonRadius: Appearance.rounding.small\n    colBackground: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainer : Appearance.colors.colLayer4\n    colBackgroundHover: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colLayer4Hover\n    colRipple: (urgency == NotificationUrgency.Critical) ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colLayer4Active\n\n    contentItem: StyledText {\n        horizontalAlignment: Text.AlignHCenter\n        text: buttonText\n        color: (urgency == NotificationUrgency.Critical) ? Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NotificationAppIcon.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport Quickshell\nimport Quickshell.Widgets\nimport Quickshell.Services.Notifications\n\nMaterialShape { // App icon\n    id: root\n    property var appIcon: \"\"\n    property var summary: \"\"\n    property var urgency: NotificationUrgency.Normal\n    property bool isUrgent: urgency === NotificationUrgency.Critical\n    property var image: \"\"\n    property real materialIconScale: 0.57\n    property real appIconScale: 0.8\n    property real smallAppIconScale: 0.49\n    property real materialIconSize: implicitSize * materialIconScale\n    property real appIconSize: implicitSize * appIconScale\n    property real smallAppIconSize: implicitSize * smallAppIconScale\n\n    implicitSize: 38 * scale\n    property list<var> urgentShapes: [\n        MaterialShape.Shape.VerySunny,\n        MaterialShape.Shape.SoftBurst,\n    ]\n    shape: isUrgent ? urgentShapes[Math.floor(Math.random() * urgentShapes.length)] : MaterialShape.Shape.Circle\n\n    color: isUrgent ? Appearance.colors.colPrimaryContainer : Appearance.colors.colSecondaryContainer\n    Loader {\n        id: materialSymbolLoader\n        active: root.appIcon == \"\" && root.image == \"\"\n        anchors.fill: parent\n        sourceComponent: MaterialSymbol {\n            text: {\n                const defaultIcon = NotificationUtils.findSuitableMaterialSymbol(\"\")\n                const guessedIcon = NotificationUtils.findSuitableMaterialSymbol(root.summary)\n                return (root.urgency == NotificationUrgency.Critical && guessedIcon === defaultIcon) ?\n                    \"priority_high\" : guessedIcon\n            }\n            anchors.fill: parent\n            color: isUrgent ? Appearance.colors.colOnPrimaryContainer : Appearance.colors.colOnSecondaryContainer\n            iconSize: root.materialIconSize\n            horizontalAlignment: Text.AlignHCenter\n            verticalAlignment: Text.AlignVCenter\n        }\n    }\n    Loader {\n        id: appIconLoader\n        active: root.image == \"\" && root.appIcon != \"\"\n        anchors.centerIn: parent\n        sourceComponent: IconImage {\n            id: appIconImage\n            implicitSize: root.appIconSize\n            asynchronous: true\n            source: Quickshell.iconPath(root.appIcon, \"image-missing\")\n        }\n    }\n    Loader {\n        id: notifImageLoader\n        active: root.image != \"\"\n        anchors.fill: parent\n        sourceComponent: Item {\n            anchors.fill: parent\n            StyledImage {\n                id: notifImage\n                anchors.fill: parent\n                readonly property int size: parent.width\n\n                source: root.image\n                fillMode: Image.PreserveAspectCrop\n                cache: false\n                antialiasing: true\n                asynchronous: true\n\n                layer.enabled: true\n                layer.effect: OpacityMask {\n                    maskSource: Rectangle {\n                        width: notifImage.size\n                        height: notifImage.size\n                        radius: Appearance.rounding.full\n                    }\n                }\n            }\n            Loader {\n                id: notifImageAppIconLoader\n                active: root.appIcon != \"\"\n                anchors.bottom: parent.bottom\n                anchors.right: parent.right\n                sourceComponent: IconImage {\n                    implicitSize: root.smallAppIconSize\n                    asynchronous: true\n                    source: Quickshell.iconPath(root.appIcon, \"image-missing\")\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NotificationGroup.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Notifications\n\n/**\n * A group of notifications from the same app.\n * Similar to Android's notifications\n */\nMouseArea { // Notification group area\n    id: root\n    property var notificationGroup\n    property var notifications: notificationGroup?.notifications ?? []\n    property int notificationCount: notifications.length\n    property bool multipleNotifications: notificationCount > 1\n    property bool expanded: false\n    property bool popup: false\n    property real padding: 10\n    implicitHeight: background.implicitHeight\n\n    property real dragConfirmThreshold: 70 // Drag further to discard notification\n    property real dismissOvershoot: 20 // Account for gaps and bouncy animations\n    property var qmlParent: root?.parent?.parent // There's something between this and the parent ListView\n    property var parentDragIndex: qmlParent?.dragIndex\n    property var parentDragDistance: qmlParent?.dragDistance\n    property var dragIndexDiff: Math.abs(parentDragIndex - index)\n    property real xOffset: dragIndexDiff == 0 ? parentDragDistance : \n        Math.abs(parentDragDistance) > dragConfirmThreshold ? 0 :\n        dragIndexDiff == 1 ? (parentDragDistance * 0.3) :\n        dragIndexDiff == 2 ? (parentDragDistance * 0.1) : 0\n\n    function destroyWithAnimation(left = false) {\n        root.qmlParent.resetDrag()\n        background.anchors.leftMargin = background.anchors.leftMargin; // Break binding\n        destroyAnimation.left = left;\n        destroyAnimation.running = true;\n    }\n\n    hoverEnabled: true\n    onContainsMouseChanged: {\n        if (!root.popup) return;\n        if (root.containsMouse) root.notifications.forEach(notif => {\n            Notifications.cancelTimeout(notif.notificationId);\n        });\n        else root.notifications.forEach(notif => {\n            Notifications.timeoutNotification(notif.notificationId);\n        });\n    }\n\n    SequentialAnimation { // Drag finish animation\n        id: destroyAnimation\n        property bool left: true\n        running: false\n\n        NumberAnimation {\n            target: background.anchors\n            property: \"leftMargin\"\n            to: (root.width + root.dismissOvershoot) * (destroyAnimation.left ? -1 : 1)\n            duration: Appearance.animation.elementMove.duration\n            easing.type: Appearance.animation.elementMove.type\n            easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n        }\n        onFinished: () => {\n            root.notifications.forEach((notif) => {\n                Qt.callLater(() => {\n                    Notifications.discardNotification(notif.notificationId);\n                });\n            });\n        }\n    }\n\n    function toggleExpanded() {\n        if (expanded) implicitHeightAnim.enabled = true;\n        else implicitHeightAnim.enabled = false;\n        root.expanded = !root.expanded;\n    }\n\n    DragManager { // Drag manager\n        id: dragManager\n        anchors.fill: parent\n        interactive: !expanded\n        automaticallyReset: false\n        acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton\n\n        onPressed: {\n            if (mouse.button === Qt.RightButton) \n                root.toggleExpanded();\n        }\n\n        onClicked: (mouse) => {\n            if (mouse.button === Qt.MiddleButton) \n                root.destroyWithAnimation();\n        }\n\n        onDraggingChanged: () => {\n            if (dragging) {\n                root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);\n            }\n        }\n\n        onDragDiffXChanged: () => {\n            root.qmlParent.dragDistance = dragDiffX;\n        }\n\n        onDragReleased: (diffX, diffY) => {\n            if (Math.abs(diffX) > root.dragConfirmThreshold)\n                root.destroyWithAnimation(diffX < 0);\n            else \n                dragManager.resetDrag();\n        }\n    }\n\n    StyledRectangularShadow {\n        target: background\n        visible: popup\n    }\n    Rectangle { // Background of the notification\n        id: background\n        anchors.left: parent.left\n        width: parent.width\n        color: popup ? Appearance.colors.colBackgroundSurfaceContainer : Appearance.colors.colLayer2\n        radius: Appearance.rounding.normal\n        anchors.leftMargin: root.xOffset\n\n        Behavior on anchors.leftMargin {\n            enabled: !dragManager.dragging\n            NumberAnimation {\n                duration: Appearance.animation.elementMove.duration\n                easing.type: Appearance.animation.elementMove.type\n                easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n            }\n        }\n        \n        clip: true\n        implicitHeight: root.expanded ? \n            row.implicitHeight + padding * 2 :\n            Math.min(80, row.implicitHeight + padding * 2)\n\n        Behavior on implicitHeight {\n            id: implicitHeightAnim\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        RowLayout { // Left column for icon, right column for content\n            id: row\n            anchors.top: parent.top\n            anchors.left: parent.left\n            anchors.right: parent.right\n            anchors.margins: root.padding\n            spacing: 10\n\n            NotificationAppIcon { // Icons\n                Layout.alignment: Qt.AlignTop\n                Layout.fillWidth: false\n                image: root?.multipleNotifications ? \"\" : notificationGroup?.notifications[0]?.image ?? \"\"\n                appIcon: root.notificationGroup?.appIcon\n                summary: root.notificationGroup?.notifications[root.notificationCount - 1]?.summary\n                urgency: root.notifications.some(n => n.urgency === NotificationUrgency.Critical.toString()) ? \n                    NotificationUrgency.Critical : NotificationUrgency.Normal\n            }\n\n            ColumnLayout { // Content\n                Layout.fillWidth: true\n                spacing: expanded ? (root.multipleNotifications ? \n                    (notificationGroup?.notifications[root.notificationCount - 1].image != \"\") ? 35 : \n                    5 : 0) : 0\n                // spacing: 00\n                Behavior on spacing {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n\n                Item { // App name (or summary when there's only 1 notif) and time\n                    id: topRow\n                    // spacing: 0\n                    Layout.fillWidth: true\n                    property real fontSize: Appearance.font.pixelSize.smaller\n                    property bool showAppName: root.multipleNotifications\n                    implicitHeight: Math.max(topTextRow.implicitHeight, expandButton.implicitHeight)\n\n                    RowLayout {\n                        id: topTextRow\n                        anchors.left: parent.left\n                        anchors.right: expandButton.left\n                        anchors.verticalCenter: parent.verticalCenter\n                        spacing: 5\n                        StyledText {\n                            id: appName\n                            elide: Text.ElideRight\n                            Layout.fillWidth: true\n                            text: (topRow.showAppName ?\n                                notificationGroup?.appName :\n                                notificationGroup?.notifications[0]?.summary) || \"\"\n                            font.pixelSize: topRow.showAppName ?\n                                topRow.fontSize :\n                                Appearance.font.pixelSize.small\n                            color: topRow.showAppName ?\n                                Appearance.colors.colSubtext :\n                                Appearance.colors.colOnLayer2\n                        }\n                        StyledText {\n                            id: timeText\n                            // Layout.fillWidth: true\n                            Layout.rightMargin: 10\n                            horizontalAlignment: Text.AlignLeft\n                            text: NotificationUtils.getFriendlyNotifTimeString(notificationGroup?.time)\n                            font.pixelSize: topRow.fontSize\n                            color: Appearance.colors.colSubtext\n                        }\n                    }\n                    NotificationGroupExpandButton {\n                        id: expandButton\n                        anchors.right: parent.right\n                        anchors.verticalCenter: parent.verticalCenter\n                        count: root.notificationCount\n                        expanded: root.expanded\n                        fontSize: topRow.fontSize\n                        onClicked: { root.toggleExpanded() }\n                        altAction: () => { root.toggleExpanded() }\n\n                        StyledToolTip {\n                            text: Translation.tr(\"Tip: right-clicking a group\\nalso expands it\")\n                        }\n                    }\n                }\n\n                StyledListView { // Notification body (expanded)\n                    id: notificationsColumn\n                    implicitHeight: contentHeight\n                    Layout.fillWidth: true\n                    spacing: expanded ? 5 : 3\n                    // clip: true\n                    interactive: false\n                    Behavior on spacing {\n                        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                    }\n                    model: ScriptModel {\n                        values: root.expanded ? root.notifications.slice().reverse() : \n                            root.notifications.slice().reverse().slice(0, 2)\n                    }\n                    delegate: NotificationItem {\n                        required property int index\n                        required property var modelData\n                        notificationObject: modelData\n                        expanded: root.expanded\n                        onlyNotification: (root.notificationCount === 1)\n                        opacity: (!root.expanded && index == 1 && root.notificationCount > 2) ? 0.5 : 1\n                        visible: root.expanded || (index < 2)\n                        anchors.left: parent?.left\n                        anchors.right: parent?.right\n                    }\n                }\n\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NotificationGroupExpandButton.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton { // Expand button\n    id: root\n    required property int count\n    required property bool expanded\n    property real fontSize: Appearance?.font.pixelSize.small ?? 12\n    property real iconSize: Appearance?.font.pixelSize.normal ?? 16\n    implicitHeight: fontSize + 4 * 2\n    implicitWidth: Math.max(contentItem.implicitWidth + 5 * 2, 30)\n    Layout.alignment: Qt.AlignVCenter\n    Layout.fillHeight: false\n\n    buttonRadius: Appearance.rounding.full\n    colBackground: ColorUtils.mix(Appearance?.colors.colLayer2, Appearance?.colors.colLayer2Hover, 0.5)\n    colBackgroundHover: Appearance?.colors.colLayer2Hover ?? \"#E5DFED\"\n    colRipple: Appearance?.colors.colLayer2Active ?? \"#D6CEE2\"\n\n    contentItem: Item {\n        anchors.centerIn: parent\n        implicitWidth: contentRow.implicitWidth\n        RowLayout {\n            id: contentRow\n            anchors.centerIn: parent\n            spacing: 3\n            StyledText {\n                Layout.leftMargin: 4\n                visible: root.count > 1\n                text: root.count\n                font.pixelSize: root.fontSize\n            }\n            MaterialSymbol {\n                text: \"keyboard_arrow_down\"\n                iconSize: root.iconSize\n                color: Appearance.colors.colOnLayer2\n                rotation: expanded ? 180 : 0\n                Behavior on rotation {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NotificationItem.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.services\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Hyprland\nimport Quickshell.Services.Notifications\n\nItem { // Notification item area\n    id: root\n    property var notificationObject\n    property bool expanded: false\n    property bool onlyNotification: false\n    property real fontSize: Appearance.font.pixelSize.small\n    property real padding: onlyNotification ? 0 : 8\n    property real summaryElideRatio: 0.85\n\n    property real dragConfirmThreshold: 70 // Drag further to discard notification\n    property real dismissOvershoot: notificationIcon.implicitWidth + 20 // Account for gaps and bouncy animations\n    property var qmlParent: root?.parent?.parent // There's something between this and the parent ListView\n    property var parentDragIndex: qmlParent?.dragIndex ?? -1\n    property var parentDragDistance: qmlParent?.dragDistance ?? 0\n    property var dragIndexDiff: Math.abs(parentDragIndex - index)\n    property real xOffset: dragIndexDiff == 0 ? parentDragDistance : \n        Math.abs(parentDragDistance) > dragConfirmThreshold ? 0 :\n        dragIndexDiff == 1 ? (parentDragDistance * 0.3) :\n        dragIndexDiff == 2 ? (parentDragDistance * 0.1) : 0\n\n    implicitHeight: background.implicitHeight\n\n    function destroyWithAnimation(left = false) {\n        root.qmlParent.resetDrag()\n        background.anchors.leftMargin = background.anchors.leftMargin; // Break binding\n        destroyAnimation.left = left;\n        destroyAnimation.running = true;\n    }\n\n    TextMetrics {\n        id: summaryTextMetrics\n        font.pixelSize: root.fontSize\n        text: root.notificationObject.summary || \"\"\n    }\n\n    SequentialAnimation { // Drag finish animation\n        id: destroyAnimation\n        property bool left: true\n        running: false\n\n        NumberAnimation {\n            target: background.anchors\n            property: \"leftMargin\"\n            to: (root.width + root.dismissOvershoot) * (destroyAnimation.left ? -1 : 1)\n            duration: Appearance.animation.elementMove.duration\n            easing.type: Appearance.animation.elementMove.type\n            easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n        }\n        onFinished: () => {\n            Notifications.discardNotification(notificationObject.notificationId);\n        }\n    }\n\n    DragManager { // Drag manager\n        id: dragManager\n        anchors.fill: root\n        anchors.leftMargin: root.expanded ? -notificationIcon.implicitWidth : 0\n        interactive: expanded\n        automaticallyReset: false\n        acceptedButtons: Qt.LeftButton | Qt.MiddleButton\n\n        onClicked: (mouse) => {\n            if (mouse.button === Qt.MiddleButton) {\n                root.destroyWithAnimation();\n            }\n        }\n\n        onDraggingChanged: () => {\n            if (dragging) {\n                root.qmlParent.dragIndex = root.index ?? root.parent.children.indexOf(root);\n            }\n        }\n\n        onDragDiffXChanged: () => {\n            root.qmlParent.dragDistance = dragDiffX;\n        }\n\n        onDragReleased: (diffX, diffY) => {\n            if (Math.abs(diffX) > root.dragConfirmThreshold)\n                root.destroyWithAnimation(diffX < 0);\n            else \n                dragManager.resetDrag();\n        }\n    }\n\n    NotificationAppIcon { // App icon\n        id: notificationIcon\n        opacity: (!onlyNotification && notificationObject.image != \"\" && expanded) ? 1 : 0\n        visible: opacity > 0\n\n        Behavior on opacity {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        image: notificationObject.image\n        anchors.right: background.left\n        anchors.top: background.top\n        anchors.rightMargin: 10\n    }\n\n    Rectangle { // Background of notification item\n        id: background\n        width: parent.width\n        anchors.left: parent.left\n        radius: Appearance.rounding.small\n        anchors.leftMargin: root.xOffset\n\n        Behavior on anchors.leftMargin {\n            enabled: !dragManager.dragging\n            NumberAnimation {\n                duration: Appearance.animation.elementMove.duration\n                easing.type: Appearance.animation.elementMove.type\n                easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n            }\n        }\n\n        color: (expanded && !onlyNotification) ? \n            (notificationObject.urgency == NotificationUrgency.Critical) ? \n                ColorUtils.mix(Appearance.colors.colSecondaryContainer, Appearance.colors.colLayer2, 0.35) :\n                (Appearance.colors.colLayer3) :\n            ColorUtils.transparentize(Appearance.colors.colLayer3)\n\n        implicitHeight: expanded ? (contentColumn.implicitHeight + padding * 2) : summaryRow.implicitHeight\n        Behavior on implicitHeight {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n\n        ColumnLayout { // Content column\n            id: contentColumn\n            anchors.fill: parent\n            anchors.margins: expanded ? root.padding : 0\n            spacing: 3\n\n            Behavior on anchors.margins {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n\n            RowLayout { // Summary row\n                id: summaryRow\n                visible: !root.onlyNotification || !root.expanded\n                Layout.fillWidth: true\n                implicitHeight: summaryText.implicitHeight\n                StyledText {\n                    id: summaryText\n                    Layout.fillWidth: summaryTextMetrics.width >= summaryRow.implicitWidth * root.summaryElideRatio\n                    visible: !root.onlyNotification\n                    font.pixelSize: root.fontSize\n                    color: Appearance.colors.colOnLayer3\n                    elide: Text.ElideRight\n                    text: root.notificationObject.summary || \"\"\n                }\n                StyledText {\n                    opacity: !root.expanded ? 1 : 0\n                    visible: opacity > 0\n                    Layout.fillWidth: true\n                    Behavior on opacity {\n                        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                    }\n                    font.pixelSize: root.fontSize\n                    color: Appearance.colors.colSubtext\n                    elide: Text.ElideRight\n                    wrapMode: Text.Wrap // Needed for proper eliding????\n                    maximumLineCount: 1\n                    textFormat: Text.StyledText\n                    text: {\n                        return NotificationUtils.processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\\n/g, \"<br/>\")\n                    }\n                }\n            }\n\n            ColumnLayout { // Expanded content\n                id: expandedContentColumn\n                Layout.fillWidth: true\n                opacity: root.expanded ? 1 : 0\n                visible: opacity > 0\n\n                StyledText { // Notification body (expanded)\n                    id: notificationBodyText\n                    Behavior on opacity {\n                        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                    }\n                    Layout.fillWidth: true\n                    font.pixelSize: root.fontSize\n                    color: Appearance.colors.colSubtext\n                    wrapMode: Text.Wrap\n                    elide: Text.ElideRight\n                    textFormat: Text.RichText\n                    text: {\n                        return `<style>img{max-width:${expandedContentColumn.width}px;}</style>` + \n                            `${NotificationUtils.processNotificationBody(notificationObject.body, notificationObject.appName || notificationObject.summary).replace(/\\n/g, \"<br/>\")}`\n                    }\n\n                    onLinkActivated: (link) => {\n                        Qt.openUrlExternally(link)\n                        GlobalStates.sidebarRightOpen = false\n                    }\n                    \n                    PointingHandLinkHover {}\n                }\n\n                Item {\n                    Layout.fillWidth: true\n                    implicitWidth: actionsFlickable.implicitWidth\n                    implicitHeight: actionsFlickable.implicitHeight\n\n                    layer.enabled: true\n                    layer.effect: OpacityMask {\n                        maskSource: Rectangle {\n                            width: actionsFlickable.width\n                            height: actionsFlickable.height\n                            radius: Appearance.rounding.small\n                        }\n                    }\n\n                    ScrollEdgeFade {\n                        target: actionsFlickable\n                        vertical: false\n                    }\n\n                    StyledFlickable { // Notification actions\n                        id: actionsFlickable\n                        anchors.fill: parent\n                        implicitHeight: actionRowLayout.implicitHeight\n                        contentWidth: actionRowLayout.implicitWidth\n\n                        Behavior on opacity {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        Behavior on height {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        Behavior on implicitHeight {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n\n                        RowLayout {\n                            id: actionRowLayout\n                            Layout.alignment: Qt.AlignBottom\n\n                            NotificationActionButton {\n                                Layout.fillWidth: true\n                                buttonText: Translation.tr(\"Close\")\n                                urgency: notificationObject.urgency\n                                implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : \n                                    (contentItem.implicitWidth + leftPadding + rightPadding)\n\n                                onClicked: {\n                                    root.destroyWithAnimation()\n                                }\n\n                                contentItem: MaterialSymbol {\n                                    iconSize: Appearance.font.pixelSize.larger\n                                    horizontalAlignment: Text.AlignHCenter\n                                    color: (notificationObject.urgency == NotificationUrgency.Critical) ? \n                                        Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface\n                                    text: \"close\"\n                                }\n                            }\n\n                            Repeater {\n                                id: actionRepeater\n                                model: notificationObject.actions\n                                NotificationActionButton {\n                                    id: notifAction\n                                    required property var modelData\n                                    Layout.fillWidth: true\n                                    buttonText: modelData.text\n                                    urgency: notificationObject.urgency\n                                    onClicked: {\n                                        Notifications.attemptInvokeAction(notificationObject.notificationId, modelData.identifier);\n                                    }\n                                }\n                            }\n\n                            NotificationActionButton {\n                                Layout.fillWidth: true\n                                urgency: notificationObject.urgency\n                                implicitWidth: (notificationObject.actions.length == 0) ? ((actionsFlickable.width - actionRowLayout.spacing) / 2) : \n                                    (contentItem.implicitWidth + leftPadding + rightPadding)\n\n                                onClicked: {\n                                    Quickshell.clipboardText = notificationObject.body\n                                    copyIcon.text = \"inventory\"\n                                    copyIconTimer.restart()\n                                }\n\n                                Timer {\n                                    id: copyIconTimer\n                                    interval: 1500\n                                    repeat: false\n                                    onTriggered: {\n                                        copyIcon.text = \"content_copy\"\n                                    }\n                                }\n\n                                contentItem: MaterialSymbol {\n                                    id: copyIcon\n                                    iconSize: Appearance.font.pixelSize.larger\n                                    horizontalAlignment: Text.AlignHCenter\n                                    color: (notificationObject.urgency == NotificationUrgency.Critical) ? \n                                        Appearance.m3colors.m3onSurfaceVariant : Appearance.m3colors.m3onSurface\n                                    text: \"content_copy\"\n                                }\n                            }\n                            \n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/NotificationListView.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nStyledListView { // Scrollable window\n    id: root\n    property bool popup: false\n\n    spacing: 3\n\n    model: ScriptModel {\n        values: root.popup ? Notifications.popupAppNameList : Notifications.appNameList\n    }\n    delegate: NotificationGroup {\n        required property int index\n        required property var modelData\n        popup: root.popup\n        width: ListView.view.width // https://doc.qt.io/qt-6/qml-qtquick-listview.html\n        notificationGroup: popup ? \n            Notifications.popupGroupsByAppName[modelData] :\n            Notifications.groupsByAppName[modelData]\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/OptionalMaterialSymbol.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nLoader {\n    id: root\n    required property string icon\n    property real iconSize: Appearance.font.pixelSize.larger\n    Layout.alignment: Qt.AlignVCenter\n\n    active: root.icon && root.icon.length > 0\n    visible: active\n\n    sourceComponent: Item {\n        implicitWidth: materialSymbol.implicitWidth\n\n        MaterialSymbol {\n            id: materialSymbol\n            anchors.centerIn: parent\n\n            iconSize: root.iconSize\n            color: root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer\n            text: root.icon\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/PagePlaceholder.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nItem {\n    id: root\n\n    property bool shown: true\n    property alias icon: shapeWidget.text\n    property alias title: widgetNameText.text\n    property alias description: widgetDescriptionText.text\n    property alias shape: shapeWidget.shape\n    property alias descriptionHorizontalAlignment: widgetDescriptionText.horizontalAlignment\n\n    opacity: shown ? 1 : 0\n    visible: opacity > 0\n    anchors {\n        fill: parent\n        topMargin: -30 * (1 - opacity)\n        bottomMargin: 30 * (1 - opacity)\n    }\n\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n\n    ColumnLayout {\n        anchors.centerIn: parent\n        spacing: 5\n\n        MaterialShapeWrappedMaterialSymbol {\n            id: shapeWidget\n            Layout.alignment: Qt.AlignHCenter\n            padding: 12\n            iconSize: 56\n            rotation: -30 * (1 - root.opacity)\n        }\n        StyledText {\n            id: widgetNameText\n            visible: title !== \"\"\n            Layout.alignment: Qt.AlignHCenter\n            font {\n                family: Appearance.font.family.title\n                pixelSize: Appearance.font.pixelSize.larger\n                variableAxes: Appearance.font.variableAxes.title\n            }\n            color: Appearance.m3colors.m3outline\n            horizontalAlignment: Text.AlignHCenter\n        }\n        StyledText {\n            id: widgetDescriptionText\n            visible: description !== \"\"\n            Layout.fillWidth: true\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.m3colors.m3outline\n            horizontalAlignment: Text.AlignLeft\n            wrapMode: Text.Wrap\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/PointingHandInteraction.qml",
    "content": "import QtQuick\n\nMouseArea {\n    anchors.fill: parent\n    onPressed: (mouse) => mouse.accepted = false\n    cursorShape: Qt.PointingHandCursor\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/PointingHandLinkHover.qml",
    "content": "import QtQuick\n\nMouseArea {\n    anchors.fill: parent\n    acceptedButtons: Qt.NoButton // Only for hover\n    hoverEnabled: true\n    cursorShape: parent.hoveredLink !== \"\" ? Qt.PointingHandCursor : Qt.ArrowCursor\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/PopupToolTip.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: root\n    property string text: \"\"\n    property bool extraVisibleCondition: true\n    property bool alternativeVisibleCondition: false\n    property real horizontalPadding: 10\n    property real verticalPadding: 5\n    property real horizontalMargin: horizontalPadding\n    property real verticalMargin: verticalPadding\n    \n    function updateAnchor() {\n        tooltipLoader.item?.anchor.updateAnchor();\n    }\n\n    readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition\n    property var anchorEdges: Edges.Top\n    property var anchorGravity: anchorEdges\n\n    property Item contentItem: StyledToolTipContent {\n        id: contentItem\n        anchors.centerIn: parent\n        text: root.text\n        shown: false\n        Component.onCompleted: shown = true\n        horizontalPadding: root.horizontalPadding\n        verticalPadding: root.verticalPadding\n    }\n\n    Loader {\n        id: tooltipLoader\n        anchors.fill: parent\n        active: root.internalVisibleCondition\n        sourceComponent: PopupWindow {\n            visible: true\n            anchor {\n                window: root.QsWindow.window\n                item: root.parent\n                edges: root.anchorEdges\n                gravity: root.anchorGravity\n            }\n            mask: Region {\n                item: null\n            }\n\n            color: \"transparent\"\n            implicitWidth: root.contentItem.implicitWidth + root.horizontalMargin * 2\n            implicitHeight: root.contentItem.implicitHeight + root.verticalMargin * 2\n\n            data: [root.contentItem]\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/Revealer.qml",
    "content": "import qs.modules.common\nimport QtQuick\n\n/**\n * Recreation of GTK revealer. Expects one single child.\n */\nItem {\n    id: root\n    property bool reveal\n    property bool vertical: false\n    clip: true\n\n    implicitWidth: (reveal || vertical) ? childrenRect.width : 0\n    implicitHeight: (reveal || !vertical) ? childrenRect.height : 0\n    visible: reveal || (implicitWidth > 0 && !vertical) || (implicitHeight > 0 && vertical)\n\n    Behavior on implicitWidth {\n        enabled: !vertical\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n    Behavior on implicitHeight {\n        enabled: vertical\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/RippleButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\n\n/**\n * A button with ripple effect similar to in Material Design.\n */\nButton {\n    id: root\n    property bool toggled\n    property string buttonText\n    property bool pointingHandCursor: true\n    property real buttonRadius: Appearance?.rounding?.small ?? 4\n    property real buttonRadiusPressed: buttonRadius\n    property real buttonEffectiveRadius: root.down ? root.buttonRadiusPressed : root.buttonRadius\n    property int rippleDuration: 1200\n    property bool rippleEnabled: true\n    property var downAction // When left clicking (down)\n    property var releaseAction // When left clicking (release)\n    property var altAction // When right clicking\n    property var middleClickAction // When middle clicking\n\n    property color colBackground: ColorUtils.transparentize(Appearance?.colors.colLayer1Hover, 1) || \"transparent\"\n    property color colBackgroundHover: Appearance?.colors.colLayer1Hover ?? \"#E5DFED\"\n    property color colBackgroundToggled: Appearance?.colors.colPrimary ?? \"#65558F\"\n    property color colBackgroundToggledHover: Appearance?.colors.colPrimaryHover ?? \"#77699C\"\n    property color colRipple: Appearance?.colors.colLayer1Active ?? \"#D6CEE2\"\n    property color colRippleToggled: Appearance?.colors.colPrimaryActive ?? \"#D6CEE2\"\n\n    opacity: root.enabled ? 1 : 0.4\n    property color buttonColor: ColorUtils.transparentize(root.toggled ? \n        (root.hovered ? colBackgroundToggledHover : \n            colBackgroundToggled) :\n        (root.hovered ? colBackgroundHover : \n            colBackground), root.enabled ? 0 : 1)\n    property color rippleColor: root.toggled ? colRippleToggled : colRipple\n\n    function startRipple(x, y) {\n        const stateY = buttonBackground.y;\n        rippleAnim.x = x;\n        rippleAnim.y = y - stateY;\n\n        const dist = (ox,oy) => ox*ox + oy*oy\n        const stateEndY = stateY + buttonBackground.height\n        rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))\n\n        rippleFadeAnim.complete();\n        rippleAnim.restart();\n    }\n\n    component RippleAnim: NumberAnimation {\n        duration: rippleDuration\n        easing.type: Appearance?.animation.elementMoveEnter.type\n        easing.bezierCurve: Appearance?.animationCurves.standardDecel\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        cursorShape: root.pointingHandCursor ? Qt.PointingHandCursor : Qt.ArrowCursor\n        acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton\n        onPressed: (event) => { \n            if(event.button === Qt.RightButton) {\n                if (root.altAction) root.altAction(event);\n                return;\n            }\n            if(event.button === Qt.MiddleButton) {\n                if (root.middleClickAction) root.middleClickAction();\n                return;\n            }\n            root.down = true\n            if (root.downAction) root.downAction();\n            if (!root.rippleEnabled) return;\n            const {x,y} = event\n            startRipple(x, y)\n        }\n        onReleased: (event) => {\n            root.down = false\n            if (event.button != Qt.LeftButton) return;\n            if (root.releaseAction) root.releaseAction();\n            root.click() // Because the MouseArea already consumed the event\n            if (!root.rippleEnabled) return;\n            rippleFadeAnim.restart();\n        }\n        onCanceled: (event) => {\n            root.down = false\n            if (!root.rippleEnabled) return;\n            rippleFadeAnim.restart();\n        }\n    }\n\n    RippleAnim {\n        id: rippleFadeAnim\n        duration: rippleDuration * 2\n        target: ripple\n        property: \"opacity\"\n        to: 0\n    }\n\n    SequentialAnimation {\n        id: rippleAnim\n\n        property real x\n        property real y\n        property real radius\n\n        PropertyAction {\n            target: ripple\n            property: \"x\"\n            value: rippleAnim.x\n        }\n        PropertyAction {\n            target: ripple\n            property: \"y\"\n            value: rippleAnim.y\n        }\n        PropertyAction {\n            target: ripple\n            property: \"opacity\"\n            value: 1\n        }\n        ParallelAnimation {\n            RippleAnim {\n                target: ripple\n                properties: \"implicitWidth,implicitHeight\"\n                from: 0\n                to: rippleAnim.radius * 2\n            }\n        }\n    }\n\n    background: Rectangle {\n        id: buttonBackground\n        radius: root.buttonEffectiveRadius\n        implicitHeight: 30\n\n        color: root.buttonColor\n        Behavior on color {\n            animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        layer.enabled: true\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                width: buttonBackground.width\n                height: buttonBackground.height\n                radius: root.buttonEffectiveRadius\n            }\n        }\n\n        Item {\n            id: ripple\n            width: ripple.implicitWidth\n            height: ripple.implicitHeight\n            opacity: 0\n            visible: width > 0 && height > 0\n\n            property real implicitWidth: 0\n            property real implicitHeight: 0\n\n            Behavior on opacity {\n                animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n\n            RadialGradient {\n                anchors.fill: parent\n                gradient: Gradient {\n                    GradientStop { position: 0.0; color: root.rippleColor }\n                    GradientStop { position: 0.3; color: root.rippleColor }\n                    GradientStop { position: 0.5; color: Qt.rgba(root.rippleColor.r, root.rippleColor.g, root.rippleColor.b, 0) }\n                }\n            }\n\n            transform: Translate {\n                x: -ripple.width / 2\n                y: -ripple.height / 2\n            }\n        }\n    }\n\n    contentItem: StyledText {\n        text: root.buttonText\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/RippleButtonWithIcon.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRippleButton {\n    id: buttonWithIconRoot\n    property string nerdIcon\n    property string materialIcon\n    property bool materialIconFill: true\n    property string mainText: \"Button text\"\n    property Component mainContentComponent: Component {\n        StyledText {\n            visible: text !== \"\"\n            text: buttonWithIconRoot.mainText\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnSecondaryContainer\n        }\n    }\n    implicitHeight: 35\n    horizontalPadding: 10\n    buttonRadius: Appearance.rounding.small\n    colBackground: Appearance.colors.colLayer2\n\n    contentItem: RowLayout {\n        Item {\n            Layout.fillWidth: false\n            implicitWidth: Math.max(materialIconLoader.implicitWidth, nerdIconLoader.implicitWidth)\n            Loader {\n                id: materialIconLoader\n                anchors.centerIn: parent\n                active: !nerdIcon\n                sourceComponent: MaterialSymbol {\n                    text: buttonWithIconRoot.materialIcon\n                    iconSize: Appearance.font.pixelSize.larger\n                    color: Appearance.colors.colOnSecondaryContainer\n                    fill: buttonWithIconRoot.materialIconFill ? 1 : 0\n                }\n            }\n            Loader {\n                id: nerdIconLoader\n                anchors.centerIn: parent\n                active: nerdIcon\n                sourceComponent: StyledText {\n                    text: buttonWithIconRoot.nerdIcon\n                    font.pixelSize: Appearance.font.pixelSize.larger\n                    font.family: Appearance.font.family.iconNerd\n                    color: Appearance.colors.colOnSecondaryContainer\n                }\n            }\n        }\n        Loader {\n            Layout.fillWidth: true\n            sourceComponent: buttonWithIconRoot.mainContentComponent\n            Layout.alignment: Qt.AlignVCenter\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/RoundCorner.qml",
    "content": "import QtQuick\nimport QtQuick.Shapes\n\nItem {\n    id: root\n\n    enum CornerEnum { TopLeft, TopRight, BottomLeft, BottomRight }\n    property var corner: RoundCorner.CornerEnum.TopLeft\n    property alias leftVisualMargin: shape.anchors.leftMargin\n    property alias topVisualMargin: shape.anchors.topMargin\n    property alias rightVisualMargin: shape.anchors.rightMargin\n    property alias bottomVisualMargin: shape.anchors.bottomMargin\n\n    property int implicitSize: 25\n    property color color: \"#000000\"\n\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    property bool isTopLeft: corner === RoundCorner.CornerEnum.TopLeft\n    property bool isBottomLeft: corner === RoundCorner.CornerEnum.BottomLeft\n    property bool isTopRight: corner === RoundCorner.CornerEnum.TopRight\n    property bool isBottomRight: corner === RoundCorner.CornerEnum.BottomRight\n    property bool isTop: isTopLeft || isTopRight\n    property bool isBottom: isBottomLeft || isBottomRight\n    property bool isLeft: isTopLeft || isBottomLeft\n    property bool isRight: isTopRight || isBottomRight\n\n    Shape {\n        id: shape\n        anchors {\n            top: root.isTop ? parent.top : undefined\n            bottom: root.isBottom ? parent.bottom : undefined\n            left: root.isLeft ? parent.left : undefined\n            right: root.isRight ? parent.right : undefined\n        }\n        layer.enabled: true\n        layer.smooth: true\n        preferredRendererType: Shape.CurveRenderer\n\n        ShapePath {\n            id: shapePath\n            strokeWidth: 0\n            fillColor: root.color\n            pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting\n\n            startX: switch (root.corner) {\n                case RoundCorner.CornerEnum.TopLeft:\n                case RoundCorner.CornerEnum.BottomLeft: return 0;\n                case RoundCorner.CornerEnum.TopRight:\n                case RoundCorner.CornerEnum.BottomRight: return root.implicitSize;\n            }\n            startY: switch (root.corner) {\n                case RoundCorner.CornerEnum.TopLeft:\n                case RoundCorner.CornerEnum.TopRight: return 0;\n                case RoundCorner.CornerEnum.BottomLeft:\n                case RoundCorner.CornerEnum.BottomRight: return root.implicitSize;\n            }\n            PathAngleArc {\n                moveToStart: false\n                centerX: root.implicitSize - shapePath.startX\n                centerY: root.implicitSize - shapePath.startY\n                radiusX: root.implicitSize\n                radiusY: root.implicitSize\n                startAngle: switch (root.corner) {\n                    case RoundCorner.CornerEnum.TopLeft: return 180;\n                    case RoundCorner.CornerEnum.TopRight: return -90;\n                    case RoundCorner.CornerEnum.BottomLeft: return 90;\n                    case RoundCorner.CornerEnum.BottomRight: return 0;\n                }\n                sweepAngle: 90\n            }\n            PathLine {\n                x: shapePath.startX\n                y: shapePath.startY\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ScrollEdgeFade.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\n\nItem {\n    id: root\n    z: 99\n    required property Item target\n    property real fadeSize: Appearance.m3colors.darkmode ? 40 : 20\n    property color color: Appearance.colors.colLayer1Base\n    property bool vertical: true\n\n    anchors.fill: target\n\n    EndGradient {\n        anchors {\n            top: parent.top\n            left: parent.left\n            right: vertical ? parent.right : undefined\n            bottom: vertical ? undefined : parent.bottom\n        }\n        shown: !(root.vertical ? root.target.atYBeginning : root.target.atXBeginning)\n    }\n\n    EndGradient {\n        anchors {\n            bottom: parent.bottom\n            right: parent.right\n            left: vertical ? parent.left : undefined\n            top: vertical ? undefined : parent.top\n        }\n        shown: !(root.vertical ? root.target.atYEnd : root.target.atXEnd)\n        rotation: 180\n    }\n\n    component EndGradient: Rectangle {\n        required property bool shown\n        height: vertical ? root.fadeSize : parent.height\n        width: vertical ? parent.width : root.fadeSize\n\n        opacity: shown ? 1 : 0\n        visible: opacity > 0\n        Behavior on opacity {\n            animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        gradient: Gradient {\n            orientation: root.vertical ? Gradient.Vertical : Gradient.Horizontal\n            GradientStop {\n                position: 0.0\n                color: root.color\n            }\n            GradientStop {\n                position: 1.0\n                color: ColorUtils.transparentize(root.color)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabBar.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.models\n\nTabBar {\n    id: root\n    property real indicatorPadding: 8\n    Layout.fillWidth: true\n\n    background: Item {\n        WheelHandler {\n            onWheel: (event) => {\n                if (event.angleDelta.y < 0) root.incrementCurrentIndex();\n                else if (event.angleDelta.y > 0) root.decrementCurrentIndex();\n            }\n            acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad\n        }\n\n        Rectangle {\n            id: activeIndicator\n            z: 9999\n            anchors.bottom: parent.bottom\n            topLeftRadius: height\n            topRightRadius: height\n            bottomLeftRadius: 0\n            bottomRightRadius: 0\n            color: Appearance.colors.colPrimary\n            // Animation\n            property real baseWidth: root.width / root.count\n            AnimatedTabIndexPair {\n                id: idxPair\n                index: root.currentIndex\n            }\n            height: 3\n            x: Math.min(idxPair.idx1, idxPair.idx2) * baseWidth + root.indicatorPadding\n            width: ((Math.max(idxPair.idx1, idxPair.idx2) + 1) * baseWidth - root.indicatorPadding) - x\n        }\n\n        Rectangle { // Tabbar bottom border\n            id: tabBarBottomBorder\n            z: 9998\n            anchors.bottom: parent.bottom\n            height: 1\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            color: Appearance.colors.colOutlineVariant\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/SecondaryTabButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nTabButton {\n    id: root\n    property string buttonText\n    property string buttonIcon\n    property int rippleDuration: 1200\n    property int tabContentWidth: buttonBackground.width - buttonBackground.radius*2\n\n    property color colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)\n    property color colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, root.checked ? 1 : 0.95)\n    property color colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95)\n\n    PointingHandInteraction {}\n\n    component RippleAnim: NumberAnimation {\n        duration: rippleDuration\n        easing.type: Appearance.animation.elementMoveEnter.type\n        easing.bezierCurve: Appearance.animationCurves.standardDecel\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        cursorShape: Qt.PointingHandCursor\n        onPressed: (event) => {\n            root.click() // Because the MouseArea already consumed the event\n            const {x,y} = event\n            const stateY = buttonBackground.y;\n            rippleAnim.x = x;\n            rippleAnim.y = y - stateY;\n\n            const dist = (ox,oy) => ox*ox + oy*oy\n            const stateEndY = stateY + buttonBackground.height\n            rippleAnim.radius = Math.sqrt(Math.max(dist(0, stateY), dist(0, stateEndY), dist(width, stateY), dist(width, stateEndY)))\n\n            rippleFadeAnim.complete();\n            rippleAnim.restart();\n        }\n        onReleased: (event) => {\n            rippleFadeAnim.restart();\n        }\n    }\n\n    RippleAnim {\n        id: rippleFadeAnim\n        duration: rippleDuration * 2\n        target: ripple\n        property: \"opacity\"\n        to: 0\n    }\n\n    SequentialAnimation {\n        id: rippleAnim\n\n        property real x\n        property real y\n        property real radius\n\n        PropertyAction {\n            target: ripple\n            property: \"x\"\n            value: rippleAnim.x\n        }\n        PropertyAction {\n            target: ripple\n            property: \"y\"\n            value: rippleAnim.y\n        }\n        PropertyAction {\n            target: ripple\n            property: \"opacity\"\n            value: 1\n        }\n        ParallelAnimation {\n            RippleAnim {\n                target: ripple\n                properties: \"implicitWidth,implicitHeight\"\n                from: 0\n                to: rippleAnim.radius * 2\n            }\n        }\n    }\n\n    background: Rectangle {\n        id: buttonBackground\n        anchors {\n            fill: parent\n            margins: 3\n        }\n        radius: Appearance?.rounding.normal\n        implicitHeight: 42\n        color: (root.hovered ? root.colBackgroundHover : root.colBackground)\n        layer.enabled: true\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                width: buttonBackground.width\n                height: buttonBackground.height\n                radius: buttonBackground.radius\n            }\n        }\n        \n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        Item {\n            id: ripple\n            width: ripple.implicitWidth\n            height: ripple.implicitHeight\n            opacity: 0\n\n            property real implicitWidth: 0\n            property real implicitHeight: 0\n            visible: width > 0 && height > 0\n\n            Behavior on opacity {\n                animation: Appearance?.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n\n            RadialGradient {\n                anchors.fill: parent\n                gradient: Gradient {\n                    GradientStop { position: 0.0; color: root.colRipple }\n                    GradientStop { position: 0.3; color: root.colRipple }\n                    GradientStop { position: 0.5 ; color: Qt.rgba(root.colRipple.r, root.colRipple.g, root.colRipple.b, 0) }\n                }\n            }\n\n            transform: Translate {\n                x: -ripple.width / 2\n                y: -ripple.height / 2\n            }\n        }\n    }\n\n    contentItem: Item {\n        anchors.centerIn: buttonBackground\n        RowLayout {\n            anchors.centerIn: parent\n            spacing: 0\n            \n            Loader {\n                id: iconLoader\n                active: buttonIcon?.length > 0\n                sourceComponent: buttonIcon?.length > 0 ? materialSymbolComponent : null\n                Layout.rightMargin: 5\n            }\n\n            Component {\n                id: materialSymbolComponent\n                MaterialSymbol {\n                    verticalAlignment: Text.AlignVCenter\n                    text: buttonIcon\n                    iconSize: Appearance.font.pixelSize.huge\n                    fill: root.checked ? 1 : 0\n                    color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1\n                    Behavior on color {\n                        animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                    }\n                }\n            }\n            StyledText {\n                id: buttonTextWidget\n                verticalAlignment: Text.AlignVCenter\n                font.pixelSize: Appearance.font.pixelSize.small\n                color: root.checked ? Appearance.colors.colPrimary : Appearance.colors.colOnLayer1\n                text: buttonText\n                Behavior on color {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/SelectionDialog.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: root\n    property real dialogPadding: 15\n    property real dialogMargin: 30\n    property string titleText: \"Selection Dialog\"\n    property alias items: choiceModel.values\n    property int selectedId: choiceListView.currentIndex\n    property var defaultChoice\n\n    signal canceled();\n    signal selected(var result);\n\n    Rectangle { // Scrim\n        id: scrimOverlay\n        anchors.fill: parent\n        radius: Appearance.rounding.small\n        color: Appearance.colors.colScrim\n        MouseArea {\n            hoverEnabled: true\n            anchors.fill: parent\n            preventStealing: true\n            propagateComposedEvents: false\n        }\n    }\n\n    Rectangle { // The dialog\n        id: dialog\n        color: Appearance.m3colors.m3surfaceContainerHigh\n        radius: Appearance.rounding.normal\n        anchors.fill: parent\n        anchors.margins: dialogMargin\n        implicitHeight: dialogColumnLayout.implicitHeight\n        \n        ColumnLayout {\n            id: dialogColumnLayout\n            anchors.fill: parent\n            spacing: 16\n\n            StyledText {\n                id: dialogTitle\n                Layout.topMargin: dialogPadding\n                Layout.leftMargin: dialogPadding\n                Layout.rightMargin: dialogPadding\n                Layout.alignment: Qt.AlignLeft\n                color: Appearance.m3colors.m3onSurface\n                font.pixelSize: Appearance.font.pixelSize.larger\n                text: root.titleText\n            }\n\n            Rectangle {\n                color: Appearance.m3colors.m3outline\n                implicitHeight: 1\n                Layout.fillWidth: true\n                Layout.leftMargin: dialogPadding\n                Layout.rightMargin: dialogPadding\n            }\n\n            StyledListView {\n                id: choiceListView\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                clip: true\n                currentIndex: root.defaultChoice !== undefined ? root.items.indexOf(root.defaultChoice) : -1\n                spacing: 6\n\n                model: ScriptModel {\n                    id: choiceModel\n                }\n\n                delegate: StyledRadioButton {\n                    id: radioButton\n                    required property var modelData\n                    required property int index\n                    anchors {\n                        left: parent?.left\n                        right: parent?.right\n                        leftMargin: root.dialogPadding\n                        rightMargin: root.dialogPadding\n                    }\n\n                    description: modelData.toString()\n                    checked: index === choiceListView.currentIndex\n\n                    onCheckedChanged: {\n                        if (checked) {\n                            choiceListView.currentIndex = index;\n                        }\n                    }\n                }\n            }\n\n            Rectangle {\n                color: Appearance.m3colors.m3outline\n                implicitHeight: 1\n                Layout.fillWidth: true\n                Layout.leftMargin: dialogPadding\n                Layout.rightMargin: dialogPadding\n            }\n\n            RowLayout {\n                id: dialogButtonsRowLayout\n                Layout.bottomMargin: dialogPadding\n                Layout.leftMargin: dialogPadding\n                Layout.rightMargin: dialogPadding\n                Layout.alignment: Qt.AlignRight\n\n                DialogButton {\n                    buttonText: Translation.tr(\"Cancel\")\n                    onClicked: root.canceled()\n                }\n                DialogButton {\n                    buttonText: Translation.tr(\"OK\")\n                    onClicked: root.selected(\n                        root.selectedId === -1 ? null :\n                        root.items[root.selectedId]\n                    )\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/SelectionGroupButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nGroupButton {\n    id: root\n    horizontalPadding: 12\n    verticalPadding: 8\n    bounce: false\n    property string buttonIcon\n    property bool leftmost: false\n    property bool rightmost: false\n    leftRadius: (toggled || leftmost) ? (height / 2) : Appearance.rounding.unsharpenmore\n    rightRadius: (toggled || rightmost) ? (height / 2) : Appearance.rounding.unsharpenmore\n    colBackground: Appearance.colors.colSecondaryContainer\n    colBackgroundHover: Appearance.colors.colSecondaryContainerHover\n    colBackgroundActive: Appearance.colors.colSecondaryContainerActive\n\n    contentItem: RowLayout {\n        spacing: 4 * (root.buttonText?.length > 0)\n\n        Loader {\n            Layout.alignment: Qt.AlignVCenter\n            active: root.buttonIcon && root.buttonIcon.length > 0\n            visible: active\n            sourceComponent: Item {\n                implicitWidth: materialSymbol.implicitWidth\n                MaterialSymbol {\n                    id: materialSymbol\n                    anchors.centerIn: parent\n                    text: root.buttonIcon\n                    iconSize: Appearance.font.pixelSize.larger\n                    color: root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer\n                }\n            }\n        }\n\n        Item {\n            implicitWidth: root.buttonText?.length > 0 ? textItem.implicitWidth : 0\n            implicitHeight: textMetrics.height // Force height to that of regular text\n\n            TextMetrics {\n                id: textMetrics\n                font.family: Appearance.font.family.main\n                text: \"Abc\"\n            }\n\n            StyledText {\n                id: textItem\n                anchors.centerIn: parent\n                color: root.toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer\n                text: root.buttonText\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/SineCookie.qml",
    "content": "import QtQuick\nimport QtQuick.Shapes\nimport Quickshell\nimport qs.modules.common\n\nItem {\n    id: root\n    \n    property real sides: 12  \n    property int implicitSize: 100\n    property real amplitude: implicitSize / 50\n    property int renderPoints: 360\n    property color color: \"#605790\"\n    property alias strokeWidth: shapePath.strokeWidth\n    property bool constantlyRotate: false\n\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    property real shapeRotation: 0\n\n    Loader {\n        active: constantlyRotate\n        sourceComponent: FrameAnimation {\n            running: true\n            onTriggered: {\n                shapeRotation += 0.05\n            }\n        }\n    }\n\n    Behavior on sides {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    Shape {\n        id: shape\n        anchors.fill: parent\n        preferredRendererType: Shape.CurveRenderer\n\n        ShapePath {\n            id: shapePath\n            strokeWidth: 0\n            fillColor: root.color\n            pathHints: ShapePath.PathSolid & ShapePath.PathNonIntersecting\n\n            PathPolyline {\n                property var pointsList: {\n                    var points = []\n                    var cx = shape.width / 2   // center x\n                    var cy = shape.height / 2  // center y\n                    var steps = root.renderPoints\n                    var radius = root.implicitSize / 2 - root.amplitude\n                    for (var i = 0; i <= steps; i++) {\n                        var angle = (i / steps) * 2 * Math.PI\n                        var rotatedAngle = angle * root.sides + Math.PI/2 + (root.shapeRotation * root.constantlyRotate)\n                        var wave = Math.sin(rotatedAngle) * root.amplitude\n                        var x = Math.cos(angle) * (radius + wave) + cx\n                        var y = Math.sin(angle) * (radius + wave) + cy\n                        points.push(Qt.point(x, y))\n                    }\n                    return points\n                }\n\n                path: pointsList\n            }\n            \n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/SqueezedAnnotationStyledText.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs.modules.common\n\n// Annotation similar to how Google Lens does it.\nItem {\n    id: root\n\n    property real scaleFactor: 1.0\n    property alias font: textWidget.font\n    property alias color: textWidget.color\n    property string text: \"\"\n\n    property bool rotate90: false\n    property real maxFontPixelSize: 100\n    visible: false\n\n    Component.onCompleted: updateText()\n    onTextChanged: updateText()\n\n    property bool searching: false\n    property real searchPixelSize: Appearance.font.pixelSize.small\n    property real renderPixelSize: Appearance.font.pixelSize.small\n    font.pixelSize: searching ? searchPixelSize : (renderPixelSize * scaleFactor)\n\n    function updateText() {\n        // Do we rotate?\n\n        root.rotate90 = false;\n        const textAspectRatio = textMetrics.width / textMetrics.height\n        const areaAspectRatio = root.width / root.height\n        if ((textAspectRatio > 1 && areaAspectRatio < 1) || (textAspectRatio < 1 && areaAspectRatio > 1)) {\n            root.rotate90 = true;\n        }\n        const targetWidth = (root.rotate90 ? root.height : root.width) / root.scaleFactor;\n        const targetHeight = (root.rotate90 ? root.width : root.height) / root.scaleFactor;\n\n        // Binary search to find the correct font size\n        var lower = 0\n        var upper = maxFontPixelSize\n        root.searching = true;\n        while (upper - lower > 0.00001) {\n            var mid = (lower + upper) / 2;\n            // print(\"bin searching\", mid, \"target\", targetWidth, targetHeight, \"actual\", textWidget.contentWidth, textWidget.contentHeight);\n            root.searchPixelSize = mid\n            if (textWidget.contentHeight > targetHeight) {\n                upper = mid\n            } else {\n                lower = mid\n            }\n        }\n        root.renderPixelSize = lower\n        root.searching = false;\n        root.visible = true\n    }\n\n    TextMetrics {\n        id: textMetrics\n        text: root.text\n        font: root.font\n    }\n\n    StyledText {\n        id: textWidget\n\n        anchors.centerIn: parent\n        width: root.rotate90 ? parent.height : parent.width\n        text: root.text\n        rotation: root.rotate90 ? 90 : 0\n\n        renderType: Text.QtRendering\n        wrapMode: Text.Wrap\n    }    \n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledBlurEffect.qml",
    "content": "import QtQuick\nimport QtQuick.Effects\n\nMultiEffect {\n    id: root\n    source: wallpaper\n    anchors.fill: source\n    saturation: 0.2\n    blurEnabled: true\n    blurMax: 100\n    blur: 1\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledComboBox.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nComboBox {\n    id: root\n\n    property string buttonIcon: \"\"\n    property real buttonRadius: height / 2\n    property color colBackground: Appearance.colors.colSecondaryContainer\n    property color colBackgroundHover: Appearance.colors.colSecondaryContainerHover\n    property color colBackgroundActive: Appearance.colors.colSecondaryContainerActive\n\n    implicitHeight: 40\n    Layout.fillWidth: true\n\n    background: Rectangle {\n        radius: root.buttonRadius\n        color: (root.down && !root.popup.visible) ? root.colBackgroundActive : root.hovered ? root.colBackgroundHover : root.colBackground\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        MouseArea {\n            anchors.fill: parent\n            acceptedButtons: Qt.NoButton\n            cursorShape: Qt.PointingHandCursor\n        }\n    }\n\n    indicator: MaterialSymbol {\n        x: root.width - width - 16\n        y: root.height / 2 - height / 2\n        text: \"keyboard_arrow_down\"\n        iconSize: Appearance.font.pixelSize.larger\n        color: Appearance.colors.colOnSecondaryContainer\n\n        rotation: root.popup.visible ? 180 : 0\n        Behavior on rotation {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n    }\n\n    contentItem: Item {\n        implicitWidth: buttonLayout.implicitWidth\n        implicitHeight: buttonLayout.implicitHeight\n\n        RowLayout {\n            id: buttonLayout\n            anchors.fill: parent\n            spacing: 8\n            anchors.leftMargin: 16\n            anchors.rightMargin: 16\n\n            Loader {\n                Layout.alignment: Qt.AlignVCenter\n                active: root.buttonIcon.length > 0 || (root.currentIndex >= 0 && typeof root.model[root.currentIndex] === 'object' && root.model[root.currentIndex]?.icon)\n                visible: active\n                sourceComponent: MaterialSymbol {\n                    text: {\n                        if (root.currentIndex >= 0 && typeof root.model[root.currentIndex] === 'object' && root.model[root.currentIndex]?.icon) {\n                            return root.model[root.currentIndex].icon;\n                        }\n                        return root.buttonIcon;\n                    }\n                    iconSize: Appearance.font.pixelSize.larger\n                    color: Appearance.colors.colOnSecondaryContainer\n                }\n            }\n\n            StyledText {\n                Layout.fillWidth: true\n                Layout.alignment: Qt.AlignVCenter\n                color: Appearance.colors.colOnSecondaryContainer\n                text: root.displayText\n                elide: Text.ElideRight\n                verticalAlignment: Text.AlignVCenter\n            }\n        }\n    }\n\n    delegate: ItemDelegate {\n        id: itemDelegate\n        width: ListView.view ? ListView.view.width : root.width\n        implicitHeight: 40\n\n        required property var model\n        required property int index\n        property color color: {\n            if (root.currentIndex === itemDelegate.index) {\n                if (itemDelegate.down) return Appearance.colors.colSecondaryContainerActive;\n                if (itemDelegate.hovered) return Appearance.colors.colSecondaryContainerHover;\n                return Appearance.colors.colSecondaryContainer;\n            } else {\n                if (itemDelegate.down) return Appearance.colors.colLayer3Active;\n                if (itemDelegate.hovered) return Appearance.colors.colLayer3Hover;\n                return ColorUtils.transparentize(Appearance.colors.colLayer3);\n            }\n        }\n        property color colText: (root.currentIndex === itemDelegate.index) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer3\n\n        background: Rectangle {\n            anchors.fill: parent\n            radius: Appearance.rounding.small\n            color: itemDelegate.color\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n\n            MouseArea {\n                anchors.fill: parent\n                acceptedButtons: Qt.NoButton\n                cursorShape: Qt.PointingHandCursor\n            }\n        }\n\n        contentItem: RowLayout {\n            spacing: 8\n            anchors.leftMargin: 12\n            anchors.rightMargin: 12\n\n            Loader {\n                Layout.alignment: Qt.AlignVCenter\n                Layout.preferredHeight: Appearance.font.pixelSize.larger\n                active: typeof itemDelegate.model === 'object' && itemDelegate.model?.icon?.length > 0\n                visible: active\n\n                sourceComponent: Item {\n                    implicitWidth: icon.implicitWidth\n                    implicitHeight: Appearance.font.pixelSize.larger\n\n                    MaterialSymbol {\n                        id: icon\n                        anchors.centerIn: parent\n                        text: itemDelegate.model?.icon ?? \"\"\n                        iconSize: Appearance.font.pixelSize.larger\n                        color: itemDelegate.colText\n                    }\n                }\n            }\n\n            StyledText {\n                Layout.fillWidth: true\n                Layout.preferredHeight: Appearance.font.pixelSize.larger\n                color: itemDelegate.colText\n                text: itemDelegate.model[root.textRole]\n                elide: Text.ElideRight\n                verticalAlignment: Text.AlignVCenter\n            }\n        }\n    }\n\n    popup: Popup {\n        y: root.height + 4\n        width: root.width\n        height: Math.min(listView.contentHeight + topPadding + bottomPadding, 300)\n        padding: 8\n\n        enter: Transition {\n            PropertyAnimation {\n                properties: \"opacity\"\n                to: 1\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n            }\n        }\n\n        exit: Transition {\n            PropertyAnimation {\n                properties: \"opacity\"\n                to: 0\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n            }\n        }\n\n        background: Item {\n            StyledRectangularShadow {\n                target: popupBackground\n            }\n\n            Rectangle {\n                id: popupBackground\n                anchors.fill: parent\n                radius: Appearance.rounding.normal\n                color: Appearance.m3colors.m3surfaceContainerHigh\n            }\n        }\n\n        contentItem: StyledListView {\n            id: listView\n            clip: true\n            implicitHeight: contentHeight\n            spacing: 2\n            model: root.popup.visible ? root.delegateModel : null\n            currentIndex: root.highlightedIndex\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledDropShadow.qml",
    "content": "import QtQuick\nimport Qt5Compat.GraphicalEffects\nimport qs.modules.common\n\nDropShadow {\n    required property var target\n    source: target\n    anchors.fill: source\n    radius: 8\n    samples: radius * 2 + 1\n    color: Appearance.colors.colShadow\n    transparentBorder: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledFlickable.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport qs.modules.common\n\nFlickable {\n    id: root\n    maximumFlickVelocity: 3500\n    boundsBehavior: Flickable.DragOverBounds\n\n    property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100\n    property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50\n    property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120\n    // Accumulated scroll destination so wheel deltas stack while animating\n    property real scrollTargetY: 0\n\n    ScrollBar.vertical: StyledScrollBar {}\n\n    MouseArea {\n        visible: Config?.options.interactions.scrolling.fasterTouchpadScroll\n        anchors.fill: parent\n        acceptedButtons: Qt.NoButton\n        onWheel: function(wheelEvent) {\n            const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold;\n            // The angleDelta.y of a touchpad is usually small and continuous,\n            // while that of a mouse wheel is typically in multiples of ±120.\n            var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor;\n\n            const maxY = Math.max(0, root.contentHeight - root.height);\n            const base = scrollAnim.running ? root.scrollTargetY : root.contentY;\n            var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY));\n\n            root.scrollTargetY = targetY;\n            root.contentY = targetY;\n            wheelEvent.accepted = true;\n        }\n    }\n\n    Behavior on contentY {\n        NumberAnimation {\n            id: scrollAnim\n            duration: Appearance.animation.scroll.duration\n            easing.type: Appearance.animation.scroll.type\n            easing.bezierCurve: Appearance.animation.scroll.bezierCurve\n        }\n    }\n\n    // Keep target synced when not animating (e.g., drag/flick or programmatic changes)\n    onContentYChanged: {\n        if (!scrollAnim.running) {\n            root.scrollTargetY = root.contentY;\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledImage.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nImage {\n    asynchronous: true\n    retainWhileLoading: true\n    visible: opacity > 0\n    opacity: (status === Image.Ready) ? 1 : 0\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n\n    property list<string> fallbacks: []\n    property int currentFallbackIndex: 0\n\n    onStatusChanged: {\n        if (status === Image.Error && currentFallbackIndex < fallbacks.length) {\n            source = fallbacks[currentFallbackIndex];\n            currentFallbackIndex += 1;\n        }\n    }\n\n    sourceSize: {\n        const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;\n        return Qt.size(width * dpr, height * dpr);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledIndeterminateProgressBar.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls.Material\nimport QtQuick.Controls\n\nProgressBar {\n    indeterminate: true\n    Material.accent: Appearance.colors.colPrimary\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledListView.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\n\n/**\n * A ListView with animations.\n */\nListView {\n    id: root\n    spacing: 5\n    property real removeOvershoot: 20 // Account for gaps and bouncy animations\n    property int dragIndex: -1\n    property real dragDistance: 0\n    property bool popin: true\n    property bool animateAppearance: true\n    property bool animateMovement: false\n    // Accumulated scroll destination so wheel deltas stack while animating\n    property real scrollTargetY: 0\n\n    property real touchpadScrollFactor: Config?.options.interactions.scrolling.touchpadScrollFactor ?? 100\n    property real mouseScrollFactor: Config?.options.interactions.scrolling.mouseScrollFactor ?? 50\n    property real mouseScrollDeltaThreshold: Config?.options.interactions.scrolling.mouseScrollDeltaThreshold ?? 120\n\n    function resetDrag() {\n        root.dragIndex = -1\n        root.dragDistance = 0\n    }\n\n    maximumFlickVelocity: 3500\n    boundsBehavior: Flickable.DragOverBounds\n    ScrollBar.vertical: StyledScrollBar {}\n\n    MouseArea {\n        visible: Config?.options.interactions.scrolling.fasterTouchpadScroll\n        anchors.fill: parent\n        acceptedButtons: Qt.NoButton\n        onWheel: function(wheelEvent) {\n            const delta = wheelEvent.angleDelta.y / root.mouseScrollDeltaThreshold;\n            // The angleDelta.y of a touchpad is usually small and continuous,\n            // while that of a mouse wheel is typically in multiples of ±120.\n            var scrollFactor = Math.abs(wheelEvent.angleDelta.y) >= root.mouseScrollDeltaThreshold ? root.mouseScrollFactor : root.touchpadScrollFactor;\n\n            const maxY = Math.max(0, root.contentHeight - root.height);\n            const base = scrollAnim.running ? root.scrollTargetY : root.contentY;\n            var targetY = Math.max(0, Math.min(base - delta * scrollFactor, maxY));\n\n            root.scrollTargetY = targetY;\n            root.contentY = targetY;\n            wheelEvent.accepted = true;\n        }\n    }\n\n    Behavior on contentY {\n        NumberAnimation {\n            id: scrollAnim\n            alwaysRunToEnd: true\n            duration: Appearance.animation.scroll.duration\n            easing.type: Appearance.animation.scroll.type\n            easing.bezierCurve: Appearance.animation.scroll.bezierCurve\n        }\n    }\n\n    // Keep target synced when not animating (e.g., drag/flick or programmatic changes)\n    onContentYChanged: {\n        if (!scrollAnim.running) {\n            root.scrollTargetY = root.contentY;\n        }\n    }\n\n    add: Transition {\n        animations: animateAppearance ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                properties: popin ? \"opacity,scale\" : \"opacity\",\n                from: 0,\n                to: 1,\n            }),\n        ] : []\n    }\n\n    addDisplaced: Transition {\n        animations: animateAppearance ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"y\",\n            }),\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                properties: popin ? \"opacity,scale\" : \"opacity\",\n                to: 1,\n            }),\n        ] : []\n    }\n    \n    displaced: Transition {\n        animations: root.animateMovement ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"y\",\n            }),\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                properties: \"opacity,scale\",\n                to: 1,\n            }),\n        ] : []\n    }\n\n    move: Transition {\n        animations: root.animateMovement ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"y\",\n            }),\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                properties: \"opacity,scale\",\n                to: 1,\n            }),\n        ] : []\n    }\n    moveDisplaced: Transition {\n        animations: root.animateMovement ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"y\",\n            }),\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                properties: \"opacity,scale\",\n                to: 1,\n            }),\n        ] : []\n    }\n\n    remove: Transition {\n        animations: animateAppearance ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"x\",\n                to: root.width + root.removeOvershoot,\n            }),\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"opacity\",\n                to: 0,\n            })\n        ] : []\n    }\n\n    // This is movement when something is removed, not removing animation!\n    removeDisplaced: Transition { \n        animations: animateAppearance ? [\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                property: \"y\",\n            }),\n            Appearance?.animation.elementMove.numberAnimation.createObject(this, {\n                properties: \"opacity,scale\",\n                to: 1,\n            }),\n        ] : []\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledProgressBar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\n\n\n/**\n * Material 3 progress bar. See https://m3.material.io/components/progress-indicators/overview\n */\nProgressBar {\n    id: root\n    property real valueBarWidth: 120\n    property real valueBarHeight: 4\n    property real valueBarGap: 4\n    property color highlightColor: Appearance?.colors.colPrimary ?? \"#685496\"\n    property color trackColor: Appearance?.m3colors.m3secondaryContainer ?? \"#F1D3F9\"\n    property bool wavy: false // If true, the progress bar will have a wavy fill effect\n    property bool animateWave: true\n    property real waveAmplitudeMultiplier: wavy ? 0.5 : 0\n    property real waveFrequency: 6\n    property real waveFps: 60\n\n    Behavior on waveAmplitudeMultiplier {\n        animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    Behavior on value {\n        animation: Appearance?.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n    \n    background: Item {\n        implicitHeight: valueBarHeight\n        implicitWidth: valueBarWidth\n    }\n\n    contentItem: Item {\n        id: contentItem\n        anchors.fill: parent\n\n        Loader {\n            anchors {\n                left: parent.left\n                verticalCenter: parent.verticalCenter\n            }\n            active: root.wavy\n            sourceComponent: WavyLine {\n                id: wavyFill\n                frequency: root.waveFrequency\n                color: root.highlightColor\n                amplitudeMultiplier: root.wavy ? 0.5 : 0\n                height: contentItem.height * 6\n                width: contentItem.width * root.visualPosition\n                lineWidth: contentItem.height\n                fullLength: root.width\n                Connections {\n                    target: root\n                    function onValueChanged() { wavyFill.requestPaint(); }\n                    function onHighlightColorChanged() { wavyFill.requestPaint(); }\n                }\n                FrameAnimation {\n                    running: root.animateWave\n                    onTriggered: {\n                        wavyFill.requestPaint()\n                    }\n                }\n            }\n        }\n\n        Loader {\n            active: !root.wavy\n            sourceComponent: Rectangle {\n                anchors.left: parent.left\n                width: contentItem.width * root.visualPosition\n                height: contentItem.height\n                radius: height / 2\n                color: root.highlightColor\n            }\n        }\n        \n        Rectangle { // Right remaining part fill\n            anchors.right: parent.right\n            width: (1 - root.visualPosition) * parent.width - valueBarGap\n            height: parent.height\n            radius: height / 2\n            color: root.trackColor\n        }\n        \n        Rectangle { // Stop point\n            anchors.right: parent.right\n            width: valueBarGap\n            height: valueBarGap\n            radius: height / 2\n            color: root.highlightColor\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledRadioButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Widgets\nimport Quickshell.Services.Pipewire\n\nRadioButton {\n    id: root\n    padding: 4\n    implicitHeight: contentItem.implicitHeight + padding * 2\n    property string description\n    property color activeColor: Appearance?.colors.colPrimary ?? \"#685496\"\n    property color inactiveColor: Appearance?.m3colors.m3onSurfaceVariant ?? \"#45464F\"\n\n    PointingHandInteraction {}\n\n    indicator: Item{}\n    \n    contentItem: RowLayout {\n        id: contentItem\n        Layout.fillWidth: true\n        spacing: 12\n        Rectangle {\n            id: radio\n            Layout.fillWidth: false\n            Layout.alignment: Qt.AlignVCenter\n            width: 20\n            height: 20\n            radius: Appearance?.rounding.full\n            border.color: checked ? root.activeColor : root.inactiveColor\n            border.width: 2\n            color: \"transparent\"\n\n            // Checked indicator\n            Rectangle {\n                anchors.centerIn: parent\n                width: checked ? 10 : 4\n                height: checked ? 10 : 4\n                radius: Appearance?.rounding.full\n                color: Appearance?.colors.colPrimary\n                opacity: checked ? 1 : 0\n\n                Behavior on opacity {\n                    animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                Behavior on width {\n                    animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)\n                }\n                Behavior on height {\n                    animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)\n                }\n\n            }\n\n            // Hover\n            Rectangle {\n                anchors.centerIn: parent\n                width: root.hovered ? 40 : 20\n                height: root.hovered ? 40 : 20\n                radius: Appearance?.rounding.full\n                color: Appearance?.m3colors.m3onSurface\n                opacity: root.hovered ? 0.1 : 0\n\n                Behavior on opacity {\n                    animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                Behavior on width {\n                    animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)\n                }\n                Behavior on height {\n                    animation: Appearance?.animation.elementMove.numberAnimation.createObject(this)\n                }\n            }\n        }\n\n        StyledText {\n            text: root.description\n            Layout.alignment: Qt.AlignVCenter\n            Layout.fillWidth: true\n            wrapMode: Text.Wrap\n            color: Appearance?.m3colors.m3onSurface\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledRectangularShadow.qml",
    "content": "import QtQuick\nimport QtQuick.Effects\nimport qs.modules.common\n\nRectangularShadow {\n    required property var target\n    anchors.fill: target\n    radius: target.radius\n    blur: 0.9 * Appearance.sizes.elevationMargin\n    offset: Qt.vector2d(0.0, 1.0)\n    spread: 1\n    color: Appearance.colors.colShadow\n    cached: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledScrollBar.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport qs.modules.common\nimport qs.modules.common.functions\n\nScrollBar {\n    id: root\n\n    policy: ScrollBar.AsNeeded\n    topPadding: Appearance.rounding.normal\n    bottomPadding: Appearance.rounding.normal\n    active: hovered || pressed\n\n    contentItem: Rectangle {\n        implicitWidth: 4\n        implicitHeight: root.visualSize\n        radius: width / 2\n        color: Appearance.colors.colOnSurfaceVariant\n        \n        opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0\n        Behavior on opacity {\n            NumberAnimation {\n                duration: 350\n                easing.type: Appearance.animation.elementMoveFast.type\n                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledSlider.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Widgets\n\n/**\n * Material 3 slider. See https://m3.material.io/components/sliders/overview\n * It doesn't exactly match the spec because it does not make sense to have stuff on a computer that fucking huge.\n * Should be at 3/4 scale...\n */\n\nSlider {\n    id: root\n\n    property list<real> stopIndicatorValues: [1]\n    property list<real> dividerValues: []\n    enum Configuration {\n        Wavy = 4,\n        XS = 12,\n        S = 18,\n        M = 30,\n        L = 42,\n        XL = 72\n    }\n\n    property var configuration: StyledSlider.Configuration.S\n\n    property real handleDefaultWidth: 3\n    property real handlePressedWidth: 1.5\n    property color highlightColor: Appearance.colors.colPrimary\n    property color trackColor: Appearance.colors.colSecondaryContainer\n    property color handleColor: Appearance.colors.colPrimary\n    property color dotColor: Appearance.m3colors.m3onSecondaryContainer\n    property color dotColorHighlighted: Appearance.m3colors.m3onPrimary\n    property real unsharpenRadius: Appearance.rounding.unsharpen\n    property real trackWidth: configuration\n    property real trackRadius: trackWidth >= StyledSlider.Configuration.XL ? 21\n        : trackWidth >= StyledSlider.Configuration.L ? 12\n        : trackWidth >= StyledSlider.Configuration.M ? 9\n        : trackWidth >= StyledSlider.Configuration.S ? 6\n        : height / 2\n    property real handleHeight: (configuration === StyledSlider.Configuration.Wavy) ? 24 : Math.max(33, trackWidth + 9)\n    property real handleWidth: root.pressed ? handlePressedWidth : handleDefaultWidth\n    property real handleMargins: 4\n    property real dividerMargins: 2\n    property real trackDotSize: 3\n    property bool usePercentTooltip: true\n    property string tooltipContent: usePercentTooltip ? `${Math.round(((value - from) / (to - from)) * 100)}%` : `${Math.round(value)}`\n    property bool wavy: configuration === StyledSlider.Configuration.Wavy // If true, the progress bar will have a wavy fill effect\n    property bool animateWave: true\n    property real waveAmplitudeMultiplier: wavy ? 0.5 : 0\n    property real waveFrequency: 6\n    property real waveFps: 60\n\n    leftPadding: handleMargins\n    rightPadding: handleMargins\n    property real effectiveDraggingWidth: width - leftPadding - rightPadding\n\n    Layout.fillWidth: true\n    from: 0\n    to: 1\n\n    Behavior on value { // This makes the adjusted value (like volume) shift smoothly\n        SmoothedAnimation {\n            velocity: Appearance.animation.elementMoveFast.velocity\n        }\n    }\n\n    Behavior on handleMargins {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    component TrackDot: Rectangle {\n        required property real value\n        property real normalizedValue: (value - root.from) / (root.to - root.from)\n        anchors.verticalCenter: parent.verticalCenter\n        x: root.handleMargins + (normalizedValue * root.effectiveDraggingWidth) - (root.trackDotSize / 2)\n        width: root.trackDotSize\n        height: root.trackDotSize\n        radius: Appearance.rounding.full\n        color: normalizedValue > root.visualPosition ? root.dotColor : root.dotColorHighlighted\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        onPressed: (mouse) => mouse.accepted = false\n        cursorShape: root.pressed ? Qt.ClosedHandCursor : Qt.PointingHandCursor \n    }\n\n    background: Item {\n        id: background\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.horizontalCenter: parent.horizontalCenter\n        width: root.width\n        implicitHeight: trackWidth\n        property var normalized: root.dividerValues.map(v => (v - root.from) / (root.to - root.from))\n        property var filtered: normalized.filter(v => Math.abs(v - root.visualPosition) * effectiveDraggingWidth > handleMargins + handleWidth / 2 - dividerMargins)\n        property var leftValues: [0, ...filtered.filter(v => v < root.visualPosition), root.visualPosition]\n        property var rightValues: [root.visualPosition, ...filtered.filter(v => v > root.visualPosition), 1]\n        property var leftWidths: leftValues.map((v, i, a) => a[i + 1] - v).slice(0, -1)\n        property var rightWidths: rightValues.map((v, i, a) => a[i + 1] - v).slice(0, -1)\n\n        // Fill left\n        Repeater {\n            model: background.leftWidths.length\n\n            Loader {\n                required property real index\n                anchors.verticalCenter: background.verticalCenter\n                property real leftMargin: index > 0 ? root.dividerMargins : 0\n                property real rightMargin: index < background.leftWidths.length - 1 ? root.dividerMargins : root.handleMargins\n                x: background.leftValues[index] * root.effectiveDraggingWidth + leftMargin + (index > 0 ? leftPadding : 0)\n                width: background.leftWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === background.leftWidths.length - 1 ? handleWidth / 2 : 0) + (index === 0 ? leftPadding : 0)\n                height: root.trackWidth\n                active: !root.wavy\n                sourceComponent: Rectangle {\n                    color: root.highlightColor\n                    topLeftRadius: index === 0 ? root.trackRadius : root.unsharpenRadius\n                    bottomLeftRadius: index === 0 ? root.trackRadius : root.unsharpenRadius\n                    topRightRadius: root.unsharpenRadius\n                    bottomRightRadius: root.unsharpenRadius\n                }\n            }\n        }\n\n        Repeater {\n            model: background.leftWidths.length\n\n            Loader {\n                required property int index\n                anchors.verticalCenter: background.verticalCenter\n                property real leftMargin: index > 0 ? root.dividerMargins : 0\n                property real rightMargin: index < background.leftWidths.length - 1 ? root.dividerMargins : root.handleMargins\n                x: background.leftValues[index] * root.effectiveDraggingWidth + leftMargin + (index > 0 ? leftPadding : 0)\n                width: background.leftWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === background.leftWidths.length - 1 ? handleWidth / 2 : 0) + (index === 0 ? leftPadding : 0)\n                height: root.height\n                active: root.wavy\n                sourceComponent: WavyLine {\n                    id: wavyFill\n                    frequency: root.waveFrequency\n                    fullLength: root.width\n                    color: root.highlightColor\n                    amplitudeMultiplier: root.wavy ? 0.5 : 0\n                    width: parent.width\n                    height: root.trackWidth\n                    Connections {\n                        target: root\n                        function onValueChanged() { wavyFill.requestPaint(); }\n                        function onHighlightColorChanged() { wavyFill.requestPaint(); }\n                    }\n                    FrameAnimation {\n                        running: root.animateWave\n                        onTriggered: {\n                            wavyFill.requestPaint()\n                        }\n                    }\n                }\n            }\n        }\n\n        // Fill right\n        Repeater {\n            model: background.rightWidths.length\n\n            Rectangle {\n                required property int index\n                anchors.verticalCenter: background.verticalCenter\n                property real leftMargin: index > 0 ? root.dividerMargins : root.handleMargins\n                property real rightMargin: index < background.rightWidths.length - 1 ? root.dividerMargins : 0\n                x: background.rightValues[index] * root.effectiveDraggingWidth + leftMargin + (index === 0 ? handleWidth / 2 : 0) + leftPadding\n                width: background.rightWidths[index] * root.effectiveDraggingWidth - leftMargin - rightMargin - (index === 0 ? handleWidth / 2 : 0) + (index === background.rightWidths.length - 1 ? rightPadding : 0)\n                height: trackWidth\n                color: root.trackColor\n                topRightRadius: index === background.rightWidths.length - 1 ? root.trackRadius : root.unsharpenRadius\n                bottomRightRadius: index === background.rightWidths.length - 1 ? root.trackRadius : root.unsharpenRadius\n                topLeftRadius: root.unsharpenRadius\n                bottomLeftRadius: root.unsharpenRadius\n            }\n        }\n\n        // Stop indicators\n        Repeater {\n            model: root.stopIndicatorValues\n            TrackDot {\n                required property real modelData\n                value: modelData\n                anchors.verticalCenter: parent?.verticalCenter\n            }\n        }\n    }\n\n    handle: Rectangle {\n        id: handle\n\n        implicitWidth: root.handleWidth\n        implicitHeight: root.handleHeight\n        x: root.leftPadding + (root.visualPosition * root.effectiveDraggingWidth) - (root.handleWidth / 2)\n        anchors.verticalCenter: parent.verticalCenter\n        radius: Appearance.rounding.full\n        color: root.handleColor\n\n        Behavior on implicitWidth {\n            animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        StyledToolTip {\n            extraVisibleCondition: root.pressed\n            text: root.tooltipContent\n            font {\n                family: Appearance.font.family.numbers\n                variableAxes: Appearance.font.variableAxes.numbers\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledSpinBox.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\n\n/**\n * Material 3 styled SpinBox component.\n */\nSpinBox {\n    id: root\n\n    property real baseHeight: 35\n    property real radius: Appearance.rounding.small\n    property real innerButtonRadius: Appearance.rounding.unsharpen\n    editable: true\n\n    opacity: root.enabled ? 1 : 0.4\n\n    background: Rectangle {\n        color: Appearance.colors.colLayer2\n        radius: root.radius\n    }\n\n    contentItem: Item {\n        implicitHeight: root.baseHeight\n        implicitWidth: Math.max(labelText.implicitWidth, 40)\n\n        StyledTextInput {\n            id: labelText\n            anchors.centerIn: parent\n            text: root.value // displayText would make the numbers weird like 1,000 instead of 1000\n            color: Appearance.colors.colOnLayer2\n            font.family: Appearance.font.family.numbers\n            font.variableAxes: Appearance.font.variableAxes.numbers\n            font.pixelSize: Appearance.font.pixelSize.small\n            validator: root.validator\n            onTextChanged: {\n                root.value = parseFloat(text);\n            }\n        }\n    }\n\n    down.indicator: Rectangle {\n        anchors {\n            verticalCenter: parent.verticalCenter\n            left: parent.left\n        }\n        implicitHeight: root.baseHeight\n        implicitWidth: root.baseHeight\n        topLeftRadius: root.radius\n        bottomLeftRadius: root.radius\n        topRightRadius: root.innerButtonRadius\n        bottomRightRadius: root.innerButtonRadius\n\n        color: root.down.pressed ? Appearance.colors.colLayer2Active : \n            root.down.hovered ? Appearance.colors.colLayer2Hover : \n            ColorUtils.transparentize(Appearance.colors.colLayer2)\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        MaterialSymbol {\n            anchors.centerIn: parent\n            text: \"remove\"\n            iconSize: 20\n            color: Appearance.colors.colOnLayer2\n        }\n    }\n\n    up.indicator: Rectangle {\n        anchors {\n            verticalCenter: parent.verticalCenter\n            right: parent.right\n        }\n        implicitHeight: root.baseHeight\n        implicitWidth: root.baseHeight\n        topRightRadius: root.radius\n        bottomRightRadius: root.radius\n        topLeftRadius: root.innerButtonRadius\n        bottomLeftRadius: root.innerButtonRadius\n\n        color: root.up.pressed ? Appearance.colors.colLayer2Active : \n            root.up.hovered ? Appearance.colors.colLayer2Hover : \n            ColorUtils.transparentize(Appearance.colors.colLayer2)\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        MaterialSymbol {\n            anchors.centerIn: parent\n            text: \"add\"\n            iconSize: 20\n            color: Appearance.colors.colOnLayer2\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledSwitch.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls\n\n/**\n * Material 3 switch. See https://m3.material.io/components/switch/overview\n */\nSwitch {\n    id: root\n    property real scale: 0.75 // Default in m3 spec is huge af\n    implicitHeight: 32 * root.scale\n    implicitWidth: 52 * root.scale\n    property color activeColor: Appearance?.colors.colPrimary ?? \"#685496\"\n    property color inactiveColor: Appearance?.colors.colSurfaceContainerHighest ?? \"#45464F\"\n\n    PointingHandInteraction {}\n\n    // Custom track styling\n    background: Rectangle {\n        width: parent.width\n        height: parent.height\n        radius: Appearance?.rounding.full ?? 9999\n        color: root.checked ? root.activeColor : root.inactiveColor\n        border.width: 2 * root.scale\n        border.color: root.checked ? root.activeColor : Appearance.m3colors.m3outline\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n        Behavior on border.color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n    // Custom thumb styling\n    indicator: Rectangle {\n        width: (root.pressed || root.down) ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale)\n        height: (root.pressed || root.down) ? (28 * root.scale) : root.checked ? (24 * root.scale) : (16 * root.scale)\n        radius: Appearance.rounding.full\n        color: root.checked ? Appearance.m3colors.m3onPrimary : Appearance.m3colors.m3outline\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.left: parent.left\n        anchors.leftMargin: root.checked ? ((root.pressed || root.down) ? (22 * root.scale) : 24 * root.scale) : ((root.pressed || root.down) ? (2 * root.scale) : 8 * root.scale)\n\n        Behavior on anchors.leftMargin {\n            NumberAnimation {\n                duration: Appearance.animationCurves.expressiveFastSpatialDuration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n            }\n        }\n        Behavior on width {\n            NumberAnimation {\n                duration: Appearance.animationCurves.expressiveFastSpatialDuration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n            }\n        }\n        Behavior on height {\n            NumberAnimation {\n                duration: Appearance.animationCurves.expressiveFastSpatialDuration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n            }\n        }\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledText.qml",
    "content": "import qs.modules.common\nimport QtQuick\n\nText {\n    id: root\n    property bool animateChange: false\n    property real animationDistanceX: 0\n    property real animationDistanceY: 6\n\n    renderType: Text.NativeRendering\n    verticalAlignment: Text.AlignVCenter\n    property bool shouldUseNumberFont: /^\\d+$/.test(root.text)\n    property var defaultFont: shouldUseNumberFont ? Appearance.font.family.numbers : Appearance.font.family.main\n    \n    font {\n        hintingPreference: Font.PreferDefaultHinting\n        family: defaultFont\n        pixelSize: Appearance?.font.pixelSize.small ?? 15\n        variableAxes: shouldUseNumberFont ? ({}) : Appearance.font.variableAxes.main\n    }\n    color: Appearance?.m3colors.m3onBackground ?? \"black\"\n    linkColor: Appearance?.m3colors.m3primary\n\n    component Anim: NumberAnimation {\n        target: root\n        duration: 300 / 2\n        easing.type: Easing.BezierSpline\n        easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n    }\n\n    Component.onCompleted: {\n        textAnimationBehavior.originalX = root.x;\n        textAnimationBehavior.originalY = root.y;\n    }\n\n    Behavior on text {\n        id: textAnimationBehavior\n        property real originalX: root.x\n        property real originalY: root.y\n        enabled: root.animateChange\n\n        SequentialAnimation {\n            alwaysRunToEnd: true\n            ParallelAnimation {\n                Anim {\n                    property: \"x\"\n                    to: textAnimationBehavior.originalX - root.animationDistanceX\n                    easing.type: Easing.InSine\n                }\n                Anim {\n                    property: \"y\"\n                    to: textAnimationBehavior.originalY - root.animationDistanceY\n                    easing.type: Easing.InSine\n                }\n                Anim {\n                    property: \"opacity\"\n                    to: 0\n                    easing.type: Easing.InSine\n                }\n            }\n            PropertyAction {} // Tie the text update to this point (we don't want it to happen during the first slide+fade)\n            PropertyAction {\n                target: root\n                property: \"x\"\n                value: textAnimationBehavior.originalX + root.animationDistanceX\n            }\n            PropertyAction {\n                target: root\n                property: \"y\"\n                value: textAnimationBehavior.originalY + root.animationDistanceY\n            }\n            ParallelAnimation {\n                Anim {\n                    property: \"x\"\n                    to: textAnimationBehavior.originalX\n                    easing.type: Easing.OutSine\n                }\n                Anim {\n                    property: \"y\"\n                    to: textAnimationBehavior.originalY\n                    easing.type: Easing.OutSine\n                }\n                Anim {\n                    property: \"opacity\"\n                    to: 1\n                    easing.type: Easing.OutSine\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledTextArea.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls\n\n/**\n * Does not include visual layout, but includes the easily neglected colors.\n */\nTextArea {\n    renderType: Text.NativeRendering\n    selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n    selectionColor: Appearance.colors.colSecondaryContainer\n    placeholderTextColor: Appearance.m3colors.m3outline\n    color: Appearance.colors.colOnLayer0\n    font {\n        family: Appearance.font.family.main\n        pixelSize: Appearance?.font.pixelSize.small ?? 15\n        hintingPreference: Font.PreferFullHinting\n        variableAxes: Appearance.font.variableAxes.main\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledTextInput.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls\n\n/**\n * Does not include visual layout, but includes the easily neglected colors.\n */\nTextInput {\n    color: Appearance.colors.colOnLayer1\n    renderType: Text.NativeRendering\n    selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n    selectionColor: Appearance.colors.colSecondaryContainer\n    font {\n        family: Appearance.font.family.main\n        pixelSize: Appearance?.font.pixelSize.small ?? 15\n        hintingPreference: Font.PreferFullHinting\n        variableAxes: Appearance.font.variableAxes.main\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledToolTip.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nToolTip {\n    id: root\n    property bool extraVisibleCondition: true\n    property bool alternativeVisibleCondition: false\n\n    readonly property bool internalVisibleCondition: (extraVisibleCondition && (parent.hovered === undefined || parent?.hovered)) || alternativeVisibleCondition\n    verticalPadding: 5\n    horizontalPadding: 10\n    background: null\n    font {\n        family: Appearance.font.family.main\n        variableAxes: Appearance.font.variableAxes.main\n        pixelSize: Appearance?.font.pixelSize.smaller ?? 14\n        hintingPreference: Font.PreferNoHinting // Prevent shaky text\n    }\n\n    delay: 0\n    visible: internalVisibleCondition\n    \n    contentItem: StyledToolTipContent {\n        id: contentItem\n        font: root.font\n        text: root.text\n        shown: root.internalVisibleCondition\n        horizontalPadding: root.horizontalPadding\n        verticalPadding: root.verticalPadding\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/StyledToolTipContent.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    required property string text\n    property bool shown: false\n    property real horizontalPadding: 10\n    property real verticalPadding: 5\n    property alias font: tooltipTextObject.font\n    implicitWidth: tooltipTextObject.implicitWidth + 2 * root.horizontalPadding\n    implicitHeight: tooltipTextObject.implicitHeight + 2 * root.verticalPadding\n\n    property bool isVisible: backgroundRectangle.implicitHeight > 0\n\n    Rectangle {\n        id: backgroundRectangle\n        anchors {\n            bottom: root.bottom\n            horizontalCenter: root.horizontalCenter\n        }\n        color: Appearance?.colors.colTooltip ?? \"#3C4043\"\n        radius: Appearance?.rounding.verysmall ?? 7\n        opacity: shown ? 1 : 0\n        implicitWidth: shown ? (tooltipTextObject.implicitWidth + 2 * root.horizontalPadding) : 0\n        implicitHeight: shown ? (tooltipTextObject.implicitHeight + 2 * root.verticalPadding) : 0\n        clip: true\n\n        Behavior on implicitWidth {\n            animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n        Behavior on implicitHeight {\n            animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n        Behavior on opacity {\n            animation: Appearance?.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        StyledText {\n            id: tooltipTextObject\n            anchors.centerIn: parent\n            text: root.text\n            font.pixelSize: Appearance?.font.pixelSize.smaller ?? 14\n            font.hintingPreference: Font.PreferNoHinting // Prevent shaky text\n            color: Appearance?.colors.colOnTooltip ?? \"#FFFFFF\"\n            wrapMode: Text.Wrap\n        }\n    }   \n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ThumbnailImage.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\n/**\n * Thumbnail image. It currently generates to the right place at the right size, but does not handle metadata/maintenance on modification.\n * See Freedesktop's spec: https://specifications.freedesktop.org/thumbnail-spec/thumbnail-spec-latest.html\n */\nStyledImage {\n    id: root\n\n    property bool generateThumbnail: true\n    required property string sourcePath\n    property string thumbnailSizeName: Images.thumbnailSizeNameForDimensions(sourceSize.width, sourceSize.height)\n    property string thumbnailPath: {\n        if (sourcePath.length == 0) return;\n        const resolvedUrlWithoutFileProtocol = FileUtils.trimFileProtocol(`${Qt.resolvedUrl(sourcePath)}`);\n        const encodedUrlWithoutFileProtocol = resolvedUrlWithoutFileProtocol.split(\"/\").map(part => encodeURIComponent(part)).join(\"/\");\n        const md5Hash = Qt.md5(`file://${encodedUrlWithoutFileProtocol}`);\n        return `${Directories.genericCache}/thumbnails/${thumbnailSizeName}/${md5Hash}.png`;\n    }\n    source: thumbnailPath\n\n    asynchronous: true\n    smooth: true\n    mipmap: false\n\n    opacity: status === Image.Ready ? 1 : 0\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    onSourceSizeChanged: {\n        if (!root.generateThumbnail) return;\n        thumbnailGeneration.running = false;\n        thumbnailGeneration.running = true;\n    }\n    Process {\n        id: thumbnailGeneration\n        command: {\n            const maxSize = Images.thumbnailSizes[root.thumbnailSizeName];\n            return [\"bash\", \"-c\", \n                `[ -f '${FileUtils.trimFileProtocol(root.thumbnailPath)}' ] && exit 0 || { magick '${root.sourcePath}' -resize ${maxSize}x${maxSize} '${FileUtils.trimFileProtocol(root.thumbnailPath)}' && exit 1; }`\n            ]\n        }\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode === 1) { // Force reload if thumbnail had to be generated\n                root.source = \"\";\n                root.source = root.thumbnailPath; // Force reload\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/Toolbar.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\n/**\n * Material 3 expressive style toolbar.\n * https://m3.material.io/components/toolbars\n */\nItem {\n    id: root\n\n    property bool enableShadow: true\n    property real padding: 8\n    property alias colBackground: background.color\n    property alias spacing: toolbarLayout.spacing\n    default property alias toolbarData: toolbarLayout.data\n    implicitWidth: background.implicitWidth\n    implicitHeight: background.implicitHeight\n    property alias radius: background.radius\n\n    Loader {\n        active: root.enableShadow\n        anchors.fill: background\n        sourceComponent: StyledRectangularShadow {\n            target: background\n            anchors.fill: undefined\n        }\n    }\n\n    Rectangle {\n        id: background\n        anchors.fill: parent\n        color: Appearance.m3colors.m3surfaceContainer\n        implicitHeight: 56\n        implicitWidth: toolbarLayout.implicitWidth + root.padding * 2\n        radius: height / 2\n\n        RowLayout {\n            id: toolbarLayout\n            spacing: 4\n            anchors {\n                fill: parent\n                margins: root.padding\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ToolbarButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\n\nRippleButton {\n    Layout.fillHeight: true\n    buttonRadius: Appearance.rounding.full\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ToolbarPairedFab.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs.modules.common\n\nItem {\n    id: root\n\n    signal clicked(event: var)\n    property alias iconText: fabWidget.iconText\n    default property alias fabData: fabWidget.data\n    property bool enableShadow: true\n\n    anchors {\n        verticalCenter: parent.verticalCenter\n    }\n    implicitWidth: fabWidget.implicitWidth\n    implicitHeight: fabWidget.implicitHeight\n    Loader {\n        active: root.enableShadow\n        anchors.fill: parent\n        sourceComponent: StyledRectangularShadow {\n            target: fabWidget\n            radius: fabWidget.buttonRadius\n        }\n    }\n    FloatingActionButton {\n        id: fabWidget\n        onClicked: e => root.clicked(e)\n        baseSize: 48\n        colBackground: Appearance.colors.colTertiaryContainer\n        colBackgroundHover: Appearance.colors.colTertiaryContainerHover\n        colRipple: Appearance.colors.colTertiaryContainerActive\n        colOnBackground: Appearance.colors.colOnTertiaryContainer\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabBar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    property alias currentIndex: tabBar.currentIndex\n    required property var tabButtonList\n\n    function incrementCurrentIndex() {\n        tabBar.incrementCurrentIndex();\n    }\n    function decrementCurrentIndex() {\n        tabBar.decrementCurrentIndex();\n    }\n    function setCurrentIndex(index) {\n        tabBar.setCurrentIndex(index);\n    }\n\n    Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter\n    implicitWidth: contentItem.implicitWidth\n    implicitHeight: 40\n\n    property Component delegate: ToolbarTabButton {\n        required property int index\n        required property var modelData\n        current: index == root.currentIndex\n        text: modelData.name\n        materialSymbol: modelData.icon\n        onClicked: {\n            root.setCurrentIndex(index);\n        }\n    }\n\n    Row {\n        id: contentItem\n        z: 1\n        anchors.centerIn: parent\n        spacing: 4\n\n        Repeater {\n            model: root.tabButtonList\n            delegate: root.delegate\n        }\n    }\n\n    Rectangle {\n        id: activeIndicator\n        z: 0\n        color: Appearance.colors.colSecondaryContainer\n        implicitWidth: contentItem.children[root.currentIndex]?.implicitWidth ?? 0\n        implicitHeight: contentItem.children[root.currentIndex]?.implicitHeight ?? 0\n        radius: height / 2\n        // Animation\n        property Item targetItem: contentItem.children[root.currentIndex]\n        AnimatedTabIndexPair {\n            id: leftBound\n            idx1Duration: 50\n            idx2Duration: 200\n            index: activeIndicator.targetItem.x\n        }\n        AnimatedTabIndexPair {\n            id: rightBound\n            idx1Duration: 50\n            idx2Duration: 200\n            index: activeIndicator.targetItem.x + activeIndicator.targetItem.width\n        }\n        x: Math.min(leftBound.idx1, leftBound.idx2)\n        width: Math.max(rightBound.idx1, rightBound.idx2) - x\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        z: 2\n        acceptedButtons: Qt.NoButton\n        cursorShape: Qt.PointingHandCursor\n        onWheel: event => {\n            if (event.angleDelta.y < 0) {\n                root.incrementCurrentIndex();\n            } else {\n                root.decrementCurrentIndex();\n            }\n        }\n    }\n\n    // TabBar doesn't allow tabs to be of different sizes. That's what I thought...\n    // We use it only for the logic and draw stuff manually\n    TabBar {\n        id: tabBar\n        z: -1\n        background: null\n        Repeater {\n            // This is to fool the TabBar that it has tabs so it does the indices properly\n            model: root.tabButtonList.length\n            delegate: TabButton {\n                background: null\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ToolbarTabButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nRippleButton {\n    id: root\n    required property string materialSymbol\n    required property bool current\n    horizontalPadding: 10\n\n    implicitHeight: 40\n    implicitWidth: implicitContentWidth + horizontalPadding * 2\n    buttonRadius: height / 2\n\n    colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)\n    colBackgroundHover: ColorUtils.transparentize(Appearance.colors.colOnSurface, current ? 1 : 0.95)\n    colRipple: ColorUtils.transparentize(Appearance.colors.colOnSurface, 0.95)\n\n    contentItem: Row {\n        id: contentRow\n        anchors.centerIn: parent\n        spacing: 6\n\n        MaterialSymbol {\n            id: icon\n            anchors.verticalCenter: parent.verticalCenter\n            iconSize: 22\n            text: root.materialSymbol\n        }\n        StyledText {\n            id: label\n            anchors.verticalCenter: parent.verticalCenter\n            text: root.text\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/ToolbarTextField.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport QtQuick.Controls\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nTextField {\n    id: filterField\n\n    property alias colBackground: background.color\n\n    Layout.fillHeight: true\n    implicitWidth: 200\n    padding: 10\n\n    placeholderTextColor: Appearance.colors.colSubtext\n    color: Appearance.colors.colOnLayer1\n    font {\n        family: Appearance.font.family.main\n        pixelSize: Appearance.font.pixelSize.small\n        hintingPreference: Font.PreferFullHinting\n        variableAxes: Appearance.font.variableAxes.main\n    }\n    renderType: Text.NativeRendering\n    selectedTextColor: Appearance.colors.colOnSecondaryContainer\n    selectionColor: Appearance.colors.colSecondaryContainer\n\n    background: Rectangle {\n        id: background\n        color: Appearance.colors.colLayer1\n        radius: Appearance.rounding.full\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/VerticalButtonGroup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\n/**\n * A container that supports GroupButton children for bounciness.\n * See https://m3.material.io/components/button-groups/overview\n */\nRectangle {\n    id: root\n    default property alias content: columnLayout.data\n    property real spacing: 5\n    property real padding: 0\n    property int clickIndex: columnLayout.clickIndex\n\n    property real contentHeight: {\n        let total = 0;\n        for (let i = 0; i < columnLayout.children.length; ++i) {\n            const child = columnLayout.children[i];\n            total += child.baseHeight ?? child.implicitHeight ?? child.height;\n        }\n        return total + columnLayout.spacing * (columnLayout.children.length - 1);\n    }\n\n    topLeftRadius: columnLayout.children.length > 0 ? (columnLayout.children[0].radius + padding) : \n        Appearance?.rounding?.small\n    topRightRadius: topLeftRadius\n    bottomLeftRadius: columnLayout.children.length > 0 ? (columnLayout.children[columnLayout.children.length - 1].radius + padding) : \n        Appearance?.rounding?.small\n    bottomRightRadius: bottomLeftRadius\n\n    color: \"transparent\"\n    height: root.contentHeight + padding * 2\n    implicitWidth: columnLayout.implicitWidth + padding * 2\n    implicitHeight: root.contentHeight + padding * 2\n    \n    children: [ColumnLayout {\n        id: columnLayout\n        anchors.fill: parent\n        anchors.margins: root.padding\n        spacing: root.spacing\n        property int clickIndex: -1\n    }]\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/VibrantToolbarButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.functions\n\nToolbarButton {\n    colBackground: ColorUtils.transparentize(Appearance.colors.colPrimaryContainer)\n    colBackgroundHover: Appearance.colors.colPrimaryContainerHover\n    colRipple: Appearance.colors.colPrimaryContainerActive\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WaveVisualizer.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Effects\n\nCanvas { // Visualizer\n    id: root\n    property list<var> points\n    property list<var> smoothPoints\n    property real maxVisualizerValue: 1000\n    property int smoothing: 2\n    property bool live: true\n    property color color: Appearance.m3colors.m3primary\n\n    onPointsChanged: () => {\n        root.requestPaint()\n    }\n\n    anchors.fill: parent\n    onPaint: {\n        var ctx = getContext(\"2d\");\n        ctx.clearRect(0, 0, width, height);\n\n        var points = root.points;\n        var maxVal = root.maxVisualizerValue || 1;\n        var h = height;\n        var w = width;\n        var n = points.length;\n        if (n < 2) return;\n\n        // Smoothing: simple moving average (optional)\n        var smoothWindow = root.smoothing; // adjust for more/less smoothing\n        root.smoothPoints = [];\n        for (var i = 0; i < n; ++i) {\n            var sum = 0, count = 0;\n            for (var j = -smoothWindow; j <= smoothWindow; ++j) {\n                var idx = Math.max(0, Math.min(n - 1, i + j));\n                sum += points[idx];\n                count++;\n            }\n            root.smoothPoints.push(sum / count);\n        }\n        if (!root.live) root.smoothPoints.fill(0); // If not playing, show no points\n\n        ctx.beginPath();\n        ctx.moveTo(0, h);\n        for (var i = 0; i < n; ++i) {\n            var x = i * w / (n - 1);\n            var y = h - (root.smoothPoints[i] / maxVal) * h;\n            ctx.lineTo(x, y);\n        }\n        ctx.lineTo(w, h);\n        ctx.closePath();\n\n        ctx.fillStyle = Qt.rgba(\n            root.color.r,\n            root.color.g,\n            root.color.b,\n            0.15\n        );\n        ctx.fill();\n    }\n\n    layer.enabled: true\n    layer.effect: MultiEffect { // Blur a bit to obscure away the points\n        source: root\n        saturation: 0.2\n        blurEnabled: true\n        blurMax: 7\n        blur: 1\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WavyLine.qml",
    "content": "import qs.modules.common\nimport QtQuick\n\nCanvas {\n    id: root\n    property real amplitudeMultiplier: 0.5\n    property real frequency: 6\n    property color color: Appearance?.colors.colPrimary ?? \"#685496\"\n    property real lineWidth: 4\n    property real fullLength: width\n\n    onPaint: {\n        var ctx = getContext(\"2d\");\n        ctx.clearRect(0, 0, width, height);\n\n        var amplitude = root.lineWidth * root.amplitudeMultiplier;\n        var frequency = root.frequency;\n        var phase = Date.now() / 400.0;\n        var centerY = height / 2;\n\n        ctx.strokeStyle = root.color;\n        ctx.lineWidth = root.lineWidth;\n        ctx.lineCap = \"round\";\n        ctx.beginPath();\n        for (var x = ctx.lineWidth / 2; x <= root.width - ctx.lineWidth / 2; x += 1) {\n            var waveY = centerY + amplitude * Math.sin(frequency * 2 * Math.PI * x / root.fullLength + phase);\n            if (x === 0)\n                ctx.moveTo(x, waveY);\n            else\n                ctx.lineTo(x, waveY);\n        }\n        ctx.stroke();\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WeekRow.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common.functions\n\nRowLayout {\n    id: root\n\n    // Pls supply\n    required property date date // Any date within the week\n    property var locale\n\n    // Expose model and delegate for flexibility\n    property list<var> model: {\n        // Should expose props like here: https://doc.qt.io/qt-6/qml-qtquick-controls-monthgrid.html#delegate-prop\n        // (except weekNumber because i'm lazy and it's not so important)\n        const firstDayOfWeek = DateUtils.getFirstDayOfWeek(root.date, root.locale.firstDayOfWeek);\n        const weekDates = [];\n        for (let i = 0; i < 7; i++) {\n            const dayDate = new Date(firstDayOfWeek);\n            dayDate.setDate(firstDayOfWeek.getDate() + i);\n            weekDates.push({ \n                date: dayDate,\n                day: dayDate.getDate(),\n                month: dayDate.getMonth() + 1,\n                year: dayDate.getFullYear(),\n                today: DateUtils.sameDate(dayDate, DateTime.clock.date)\n            });\n        }\n        return weekDates;\n    }\n    property Component delegate: Text {\n        required property var model\n        text: model.day\n    }\n\n    // Obvious\n    Repeater {\n        model: root.model\n        delegate: root.delegate\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialog.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nRectangle {\n    id: root\n\n    property bool show: false\n    default property alias contentData: contentColumn.data\n    property real backgroundHeight: dialogBackground.implicitHeight\n    property real backgroundWidth: 350\n    property real backgroundAnimationMovementDistance: 60\n    \n    signal dismiss()\n    Keys.onPressed: (event) => {\n        if (event.key === Qt.Key_Escape) {\n            root.dismiss();\n            event.accepted = true;\n        }\n    }\n\n    color: root.show ? Appearance.colors.colScrim : ColorUtils.transparentize(Appearance.colors.colScrim)\n    Behavior on color {\n        animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n    }\n    visible: dialogBackground.implicitHeight > 0\n\n    onShowChanged: {\n        dialogBackgroundHeightAnimation.easing.bezierCurve = (show ? Appearance.animationCurves.emphasizedDecel : Appearance.animationCurves.emphasizedAccel)\n        dialogBackground.implicitHeight = show ? backgroundHeight : 0\n    }\n\n    radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1\n\n    MouseArea { // Clicking outside the dialog should dismiss\n        anchors.fill: parent\n        acceptedButtons: Qt.AllButtons\n        hoverEnabled: true\n        onPressed: root.dismiss()\n    }\n\n    Rectangle {\n        id: dialogBackground\n        anchors.horizontalCenter: parent.horizontalCenter\n        radius: Appearance.rounding.large\n        color: Appearance.m3colors.m3surfaceContainerHigh // Use opaque version of layer3\n        \n        property real targetY: root.height / 2 - root.backgroundHeight / 2\n        y: root.show ? targetY : (targetY - root.backgroundAnimationMovementDistance)\n        implicitWidth: root.backgroundWidth\n        implicitHeight: contentColumn.implicitHeight + dialogBackground.radius * 2\n        Behavior on implicitHeight {\n            NumberAnimation {\n                id: dialogBackgroundHeightAnimation\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animationCurves.emphasizedDecel\n            }\n        }\n        Behavior on y {\n            NumberAnimation {\n                duration: dialogBackgroundHeightAnimation.duration\n                easing.type: dialogBackgroundHeightAnimation.easing.type\n                easing.bezierCurve: dialogBackgroundHeightAnimation.easing.bezierCurve\n            }\n        }\n\n        MouseArea { // So clicking inside the dialog won't dismiss\n            anchors.fill: parent\n            acceptedButtons: Qt.AllButtons\n            hoverEnabled: true\n        }\n\n        ColumnLayout {\n            id: contentColumn\n            anchors {\n                fill: parent\n                margins: dialogBackground.radius\n            }\n            spacing: 16\n            opacity: root.show ? 1 : 0\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialogButtonRow.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nRowLayout {\n    id: root\n    spacing: 4\n\n    // These shouldn't be needed but it would be a terrible waste of space to follow the spec\n    Layout.margins: -8\n    Layout.topMargin: 0\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialogParagraph.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nStyledText {\n    text: \"Some body content\"\n    color: Appearance.colors.colOnSurfaceVariant\n    font.pixelSize: Appearance.font.pixelSize.small\n    wrapMode: Text.Wrap\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSectionHeader.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nStyledText {\n    text: \"Section\"\n    font {\n        family: Appearance.font.family.title\n        pixelSize: Appearance.font.pixelSize.large\n        variableAxes: Appearance.font.variableAxes.title\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSeparator.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nRectangle {\n    implicitHeight: 1\n    color: Appearance.colors.colOutline\n    Layout.fillWidth: true\n    Layout.leftMargin: -Appearance.rounding.large\n    Layout.rightMargin: -Appearance.rounding.large\n    Layout.topMargin: -8\n    Layout.bottomMargin: -8\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialogSlider.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Widgets\n\nColumn {\n    id: root\n\n    property alias text: sliderName.text\n    property alias from: sliderWidget.from\n    property alias to: sliderWidget.to\n    property alias value: sliderWidget.value\n    property alias tooltipContent: sliderWidget.tooltipContent\n    property alias stopIndicatorValues: sliderWidget.stopIndicatorValues\n\n    signal moved()\n    \n    spacing: -2\n    ContentSubsectionLabel {\n        id: sliderName\n        visible: text?.length > 0\n        text: \"\"\n        anchors {\n            left: parent.left\n            right: parent.right\n        }\n    }\n    StyledSlider {\n        id: sliderWidget\n        anchors {\n            left: parent.left\n            right: parent.right\n            leftMargin: 4\n            rightMargin: 4\n        }\n        configuration: StyledSlider.Configuration.S\n        onMoved: root.moved()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/WindowDialogTitle.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nStyledText {\n    text: \"Dialog Title\"\n    color: Appearance.colors.colOnSurface\n    wrapMode: Text.Wrap\n    font {\n        family: Appearance.font.family.title\n        pixelSize: Appearance.font.pixelSize.title\n        variableAxes: Appearance.font.variableAxes.title\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractOverlayWidget.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\n\n/*\n * Abstract widgets for an overlay. Doesn't contain any visuals.\n */\nAbstractWidget {\n    id: root\n\n    property bool pinned: false // Whether to stay visible when the overlay is dismissed\n    property bool clickthrough: true // When pinned, whether to allow clicks go through\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/AbstractWidget.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\n\n/*\n * Widget to be placed on a WidgetCanvas\n */\nMouseArea {\n    id: root\n\n    property alias animateXPos: xBehavior.enabled\n    property alias animateYPos: yBehavior.enabled\n    property bool draggable: true\n    drag.target: draggable ? root : undefined\n    cursorShape: (draggable && containsPress) ? Qt.ClosedHandCursor : draggable ? Qt.OpenHandCursor : Qt.ArrowCursor\n\n    function center() {\n        root.x = (root.parent.width - root.width) / 2\n        root.y = (root.parent.height - root.height) / 2\n    }\n\n    Behavior on x {\n        id: xBehavior\n        animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n    }\n    Behavior on y {\n        id: yBehavior\n        animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/common/widgets/widgetCanvas/WidgetCanvas.qml",
    "content": "import QtQuick\n\nMouseArea {\n    id: root\n\n    // uh this is stupid turns out we don't need anything here\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/Background.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\nimport qs.modules.common.functions as CF\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nimport qs.modules.ii.background.widgets\nimport qs.modules.ii.background.widgets.clock\nimport qs.modules.ii.background.widgets.weather\n\nVariants {\n    id: root\n    model: Quickshell.screens\n\n    PanelWindow {\n        id: bgRoot\n\n        required property var modelData\n\n        // Hide when fullscreen\n        property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name)\n        property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0]\n        visible: GlobalStates.screenLocked || (!(activeWorkspaceWithFullscreen != undefined)) || !Config?.options.background.hideWhenFullscreen\n\n        // Workspaces\n        property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)\n        property list<var> relevantWindows: HyprlandData.windowList.filter(win => win.monitor == monitor?.id && win.workspace.id >= 0).sort((a, b) => a.workspace.id - b.workspace.id)\n        property int firstWorkspaceId: relevantWindows[0]?.workspace.id || 1\n        property int lastWorkspaceId: relevantWindows[relevantWindows.length - 1]?.workspace.id || 10\n        property int workspaceChunkSize: Config?.options.bar.workspaces.shown ?? 10\n        property int totalWorkspaces: Math.ceil(lastWorkspaceId / workspaceChunkSize) * workspaceChunkSize\n        // Wallpaper\n        property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(\".mp4\") || Config.options.background.wallpaperPath.endsWith(\".webm\") || Config.options.background.wallpaperPath.endsWith(\".mkv\") || Config.options.background.wallpaperPath.endsWith(\".avi\") || Config.options.background.wallpaperPath.endsWith(\".mov\")\n        property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath\n        property bool wallpaperSafetyTriggered: {\n            const enabled = Config.options.workSafety.enable.wallpaper;\n            const sensitiveWallpaper = (CF.StringUtils.stringListContainsSubstring(wallpaperPath.toLowerCase(), Config.options.workSafety.triggerCondition.fileKeywords));\n            const sensitiveNetwork = (CF.StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords));\n            return enabled && sensitiveWallpaper && sensitiveNetwork;\n        }\n        readonly property real parallaxRation: Config.options.background.parallax.workspaceZoom\n        property real minSuitableScale: 1 // Some reasonable init, to be updated\n        property real effectiveWallpaperScale: minSuitableScale * parallaxRation\n        property int wallpaperWidth: modelData.width // Some reasonable init value, to be updated\n        property int wallpaperHeight: modelData.height // Some reasonable init value, to be updated\n        property real scaledWallpaperWidth: wallpaperWidth * effectiveWallpaperScale\n        property real scaledWallpaperHeight: wallpaperHeight * effectiveWallpaperScale\n        property real parallaxTotalPixelsX: Math.max(0, scaledWallpaperWidth - screen.width)\n        property real parallaxTotalPixelsY: Math.max(0, scaledWallpaperHeight - screen.height)\n        readonly property bool verticalParallax: (Config.options.background.parallax.autoVertical && wallpaperHeight > wallpaperWidth) || Config.options.background.parallax.vertical\n        // Colors\n        property bool shouldBlur: (GlobalStates.screenLocked && Config.options.lock.blur.enable)\n        property color dominantColor: Appearance.colors.colPrimary // Default, to be changed\n        property bool dominantColorIsDark: dominantColor.hslLightness < 0.5\n        property color colText: {\n            if (wallpaperSafetyTriggered)\n                return CF.ColorUtils.mix(Appearance.colors.colOnLayer0, Appearance.colors.colPrimary, 0.75);\n            return (GlobalStates.screenLocked && shouldBlur) ? Appearance.colors.colOnLayer0 : CF.ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12));\n        }\n        Behavior on colText {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        // Layer props\n        screen: modelData\n        exclusionMode: ExclusionMode.Ignore\n        WlrLayershell.layer: (GlobalStates.screenLocked && !scaleAnim.running) ? WlrLayer.Overlay : WlrLayer.Bottom\n        // WlrLayershell.layer: WlrLayer.Bottom\n        WlrLayershell.namespace: \"quickshell:background\"\n        anchors {\n            top: true\n            bottom: true\n            left: true\n            right: true\n        }\n        color: {\n            if (!bgRoot.wallpaperSafetyTriggered || bgRoot.wallpaperIsVideo)\n                return \"transparent\";\n            return CF.ColorUtils.mix(Appearance.colors.colLayer0, Appearance.colors.colPrimary, 0.75);\n        }\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        onWallpaperPathChanged: {\n            bgRoot.updateZoomScale();\n            // Clock position gets updated after zoom scale is updated\n        }\n\n        // Wallpaper zoom scale\n        function updateZoomScale() {\n            getWallpaperSizeProc.path = bgRoot.wallpaperPath;\n            getWallpaperSizeProc.running = true;\n        }\n        Process {\n            id: getWallpaperSizeProc\n            property string path: bgRoot.wallpaperPath\n            command: [\"magick\", \"identify\", \"-format\", \"%w %h\", path]\n            stdout: StdioCollector {\n                id: wallpaperSizeOutputCollector\n                onStreamFinished: {\n                    const output = wallpaperSizeOutputCollector.text;\n                    const [width, height] = output.split(\" \").map(Number);\n                    const [screenWidth, screenHeight] = [bgRoot.screen.width, bgRoot.screen.height];\n                    bgRoot.wallpaperWidth = width;\n                    bgRoot.wallpaperHeight = height;\n\n                    // Perfect image; scale = 1\n                    // Small picture; scale > 1; will zoom in the picture\n                    // Big picture; scale < 1; will zoom out the picture\n                    // Choose max number so every side will fit\n                    bgRoot.minSuitableScale = Math.max(screenWidth / width, screenHeight / height);\n                }\n            }\n        }\n\n        Item {\n            anchors.fill: parent\n\n            // Wallpaper\n            StyledImage {\n                id: wallpaper\n                visible: opacity > 0 && !blurLoader.active\n                opacity: (status === Image.Ready && !bgRoot.wallpaperIsVideo) ? 1 : 0\n                cache: false\n                smooth: false\n\n                property int workspaceIndex: (bgRoot.monitor.activeWorkspace?.id ?? 1) - 1\n                property real middleFraction: 0.5\n                property real fraction: {\n                    // 0 - start of the picture\n                    // 1 - end of the picture\n                    if (bgRoot.totalWorkspaces <= 1) {\n                        return middleFraction;\n                    }\n                    return Math.max(0, Math.min(1, workspaceIndex / (bgRoot.totalWorkspaces - 1)));\n                }\n\n                property real usedFractionX: {\n                    let usedFraction = middleFraction;\n                    if (Config.options.background.parallax.enableWorkspace && !bgRoot.verticalParallax) {\n                        usedFraction = fraction;\n                    }\n                    if (Config.options.background.parallax.enableSidebar) {\n                        let sidebarFraction = bgRoot.parallaxRation / bgRoot.workspaceChunkSize / 2;\n                        usedFraction += (sidebarFraction * GlobalStates.sidebarRightOpen - sidebarFraction * GlobalStates.sidebarLeftOpen);\n                    }\n                    return Math.max(0, Math.min(1, usedFraction));\n                }\n                property real usedFractionY: {\n                    let usedFraction = middleFraction;\n                    if (Config.options.background.parallax.enableWorkspace && bgRoot.verticalParallax) {\n                        usedFraction = fraction;\n                    }\n                    return Math.max(0, Math.min(1, usedFraction));\n                }\n\n                x: {\n                    if (bgRoot.screen.width > width) {\n                        // Center the picture\n                        return (bgRoot.screen.width - width) / 2;\n                    }\n                    return - bgRoot.parallaxTotalPixelsX * usedFractionX;\n                }\n                y: {\n                    if (bgRoot.screen.height > height) {\n                        // Center the picture\n                        return (bgRoot.screen.height - height) / 2;\n                    }\n                    return - bgRoot.parallaxTotalPixelsY * usedFractionY;\n                }\n\n                source: bgRoot.wallpaperSafetyTriggered ? \"\" : bgRoot.wallpaperPath\n                fillMode: Image.PreserveAspectCrop\n                Behavior on x {\n                    NumberAnimation {\n                        duration: 600\n                        easing.type: Easing.OutCubic\n                    }\n                }\n                Behavior on y {\n                    NumberAnimation {\n                        duration: 600\n                        easing.type: Easing.OutCubic\n                    }\n                }\n                width: bgRoot.scaledWallpaperWidth\n                height: bgRoot.scaledWallpaperHeight\n            }\n\n            Loader {\n                id: blurLoader\n                active: Config.options.lock.blur.enable && (GlobalStates.screenLocked || scaleAnim.running)\n                anchors.fill: wallpaper\n                scale: GlobalStates.screenLocked ? Config.options.lock.blur.extraZoom : 1\n                Behavior on scale {\n                    NumberAnimation {\n                        id: scaleAnim\n                        duration: 400\n                        easing.type: Easing.BezierSpline\n                        easing.bezierCurve: Appearance.animationCurves.expressiveDefaultSpatial\n                    }\n                }\n                sourceComponent: GaussianBlur {\n                    source: wallpaper\n                    radius: GlobalStates.screenLocked ? Config.options.lock.blur.radius : 0\n                    samples: radius * 2 + 1\n\n                    Rectangle {\n                        opacity: GlobalStates.screenLocked ? 1 : 0\n                        anchors.fill: parent\n                        color: CF.ColorUtils.transparentize(Appearance.colors.colLayer0, 0.7)\n                    }\n                }\n            }\n\n            WidgetCanvas {\n                id: widgetCanvas\n                width: parent.width\n                height: parent.height\n                readonly property real parallaxFactor: {\n                    var f = Config.options.background.parallax.widgetsFactor;\n                    return f / bgRoot.parallaxRation;\n                }\n                readonly property real baseWallpaperOffsetX: (bgRoot.screen.width - wallpaper.width) / 2\n                readonly property real baseWallpaperOffsetY: (bgRoot.screen.height - wallpaper.height) / 2\n                readonly property real wallpaperTotalOffsetX: wallpaper.x - baseWallpaperOffsetX\n                readonly property real wallpaperTotalOffsetY: wallpaper.y - baseWallpaperOffsetY\n                readonly property bool locked: GlobalStates.screenLocked\n                x: wallpaperTotalOffsetX * parallaxFactor * !locked\n                y: wallpaperTotalOffsetY * parallaxFactor * !locked\n\n                transitions: Transition {\n                    PropertyAnimation {\n                        properties: \"width,height\"\n                        duration: Appearance.animation.elementMove.duration\n                        easing.type: Appearance.animation.elementMove.type\n                        easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n                    }\n                    AnchorAnimation {\n                        duration: Appearance.animation.elementMove.duration\n                        easing.type: Appearance.animation.elementMove.type\n                        easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n                    }\n                }\n\n                FadeLoader {\n                    shown: Config.options.background.widgets.weather.enable\n                    sourceComponent: WeatherWidget {\n                        screenWidth: bgRoot.screen.width\n                        screenHeight: bgRoot.screen.height\n                        scaledScreenWidth: bgRoot.screen.width\n                        scaledScreenHeight: bgRoot.screen.height\n                        wallpaperScale: 1\n                    }\n                }\n\n                FadeLoader {\n                    shown: Config.options.background.widgets.clock.enable\n                    sourceComponent: ClockWidget {\n                        screenWidth: bgRoot.screen.width\n                        screenHeight: bgRoot.screen.height\n                        scaledScreenWidth: bgRoot.screen.width\n                        scaledScreenHeight: bgRoot.screen.height\n                        wallpaperScale: 1\n                        wallpaperSafetyTriggered: bgRoot.wallpaperSafetyTriggered\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/AbstractBackgroundWidget.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets.widgetCanvas\n\nAbstractWidget {\n    id: root\n\n    required property string configEntryName\n    required property int screenWidth\n    required property int screenHeight\n    required property int scaledScreenWidth\n    required property int scaledScreenHeight\n    required property real wallpaperScale\n    property bool visibleWhenLocked: false\n    property var configEntry: Config.options.background.widgets[configEntryName]\n    property string placementStrategy: configEntry.placementStrategy\n    property real targetX: Math.max(0, Math.min(configEntry.x, scaledScreenWidth - width))\n    property real targetY : Math.max(0, Math.min(configEntry.y, scaledScreenHeight - height))\n    x: targetX\n    y: targetY\n    visible: opacity > 0\n    opacity: (GlobalStates.screenLocked && !visibleWhenLocked) ? 0 : 1\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n    scale: (draggable && containsPress) ? 1.05 : 1\n    Behavior on scale {\n        animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n    }\n\n    draggable: placementStrategy === \"free\"\n    onReleased: {\n        root.targetX = root.x;\n        root.targetY = root.y;\n        configEntry.x = root.targetX;\n        configEntry.y = root.targetY;\n    }\n\n    property bool needsColText: false\n    property color dominantColor: Appearance.colors.colPrimary\n    property bool dominantColorIsDark: dominantColor.hslLightness < 0.5\n    property color colText: {\n        const onNormalBackground = (GlobalStates.screenLocked && Config.options.lock.blur.enable)\n        const adaptiveColor = ColorUtils.colorWithLightness(Appearance.colors.colPrimary, (dominantColorIsDark ? 0.8 : 0.12))\n        return onNormalBackground ? Appearance.colors.colOnLayer0 : adaptiveColor;\n    }\n\n    property bool wallpaperIsVideo: Config.options.background.wallpaperPath.endsWith(\".mp4\") || Config.options.background.wallpaperPath.endsWith(\".webm\") || Config.options.background.wallpaperPath.endsWith(\".mkv\") || Config.options.background.wallpaperPath.endsWith(\".avi\") || Config.options.background.wallpaperPath.endsWith(\".mov\")\n    property string wallpaperPath: wallpaperIsVideo ? Config.options.background.thumbnailPath : Config.options.background.wallpaperPath\n    \n    onWallpaperPathChanged: refreshPlacementIfNeeded()\n    onPlacementStrategyChanged: refreshPlacementIfNeeded()\n    Connections {\n        target: Config\n        function onReadyChanged() { refreshPlacementIfNeeded() }\n    }\n    function refreshPlacementIfNeeded() {\n        if (!Config.ready) return;\n        if (root.placementStrategy === \"free\" && !root.needsColText) return;\n        leastBusyRegionProc.wallpaperPath = root.wallpaperPath;\n        leastBusyRegionProc.running = false;\n        leastBusyRegionProc.running = true;\n    }\n    Process {\n        id: leastBusyRegionProc\n        property string wallpaperPath: root.wallpaperPath\n        // TODO: make these less arbitrary\n        property int contentWidth: 300\n        property int contentHeight: 300\n        property int horizontalPadding: 200\n        property int verticalPadding: 200\n        command: [Quickshell.shellPath(\"scripts/images/least-busy-region-venv.sh\") // Comments to force the formatter to break lines\n            , \"--screen-width\", Math.round(root.scaledScreenWidth) //\n            , \"--screen-height\", Math.round(root.scaledScreenHeight) //\n            , \"--width\", contentWidth //\n            , \"--height\", contentHeight //\n            , \"--horizontal-padding\", horizontalPadding //\n            , \"--vertical-padding\", verticalPadding //\n            , wallpaperPath //\n            , ...(root.placementStrategy === \"mostBusy\" ? [\"--busiest\"] : [])\n            // \"--visual-output\",\n        ]\n        stdout: StdioCollector {\n            id: leastBusyRegionOutputCollector\n            onStreamFinished: {\n                const output = leastBusyRegionOutputCollector.text;\n                // console.log(\"[Background] Least busy region output:\", output)\n                if (output.length === 0) return;\n                const parsedContent = JSON.parse(output);\n                root.dominantColor = parsedContent.dominant_color || Appearance.colors.colPrimary;\n                if (root.placementStrategy === \"free\") return;\n                root.targetX = parsedContent.center_x * root.wallpaperScale - root.width / 2;\n                root.targetY  = parsedContent.center_y * root.wallpaperScale - root.height / 2;\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/ClockText.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nStyledText {\n    Layout.fillWidth: true\n    font {\n        family: Appearance.font.family.expressive\n        pixelSize: 20\n        weight: 350\n        // Set empty to prevent conflicts, not meaningless\n        styleName: \"\"\n        variableAxes: ({})\n    }\n    style: Text.Raised\n    styleColor: Appearance.colors.colShadow\n    animateChange: Config.options.background.widgets.clock.digital.animateChange\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/ClockWidget.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\nimport qs.modules.ii.background.widgets\n\nAbstractBackgroundWidget {\n    id: root\n\n    configEntryName: \"clock\"\n\n    implicitHeight: contentColumn.implicitHeight\n    implicitWidth: contentColumn.implicitWidth\n\n    readonly property string clockStyle: GlobalStates.screenLocked ? Config.options.background.widgets.clock.styleLocked : Config.options.background.widgets.clock.style\n    readonly property bool forceCenter: (GlobalStates.screenLocked && Config.options.lock.centerClock)\n    readonly property bool shouldShow: (!Config.options.background.widgets.clock.showOnlyWhenLocked || GlobalStates.screenLocked)\n    property bool wallpaperSafetyTriggered: false\n    needsColText: clockStyle === \"digital\"\n    x: forceCenter ? ((root.screenWidth - root.width) / 2) : targetX\n    y: forceCenter ? ((root.screenHeight - root.height) / 2) : targetY\n    visibleWhenLocked: true\n\n    property var textHorizontalAlignment: {\n        if (!Config.options.background.widgets.clock.digital.adaptiveAlignment || root.forceCenter || Config.options.background.widgets.clock.digital.vertical) \n            return Text.AlignHCenter;\n        if (root.x < root.scaledScreenWidth / 3)\n            return Text.AlignLeft;\n        if (root.x > root.scaledScreenWidth * 2 / 3)\n            return Text.AlignRight;\n        return Text.AlignHCenter;\n    }\n\n    Column {\n        id: contentColumn\n        anchors.centerIn: parent\n        spacing: 10\n\n        FadeLoader {\n            id: cookieClockLoader\n            anchors.horizontalCenter: parent.horizontalCenter\n            shown: root.clockStyle === \"cookie\" && (root.shouldShow)\n            fade: false\n            sourceComponent: Column {\n                spacing: 10\n                CookieClock {\n                    anchors.horizontalCenter: parent.horizontalCenter\n                }\n                FadeLoader {\n                    anchors.horizontalCenter: parent.horizontalCenter\n                    shown: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text !== \"\"\n                    sourceComponent: CookieQuote {}\n                }\n            }\n        }\n\n        FadeLoader {\n            id: digitalClockLoader\n            anchors.horizontalCenter: parent.horizontalCenter\n            shown: root.clockStyle === \"digital\" && (root.shouldShow)\n            fade: false\n            sourceComponent: DigitalClock {\n                colText: root.colText\n                textHorizontalAlignment: root.textHorizontalAlignment\n            }\n        }\n        StatusRow {\n            anchors.horizontalCenter: parent.horizontalCenter\n        }\n    }\n\n    component StatusRow: Item {\n        id: statusText\n        implicitHeight: statusTextBg.implicitHeight\n        implicitWidth: statusTextBg.implicitWidth\n        StyledRectangularShadow {\n            target: statusTextBg\n            visible: statusTextBg.visible && root.clockStyle === \"cookie\"\n            opacity: statusTextBg.opacity\n        }\n        Rectangle {\n            id: statusTextBg\n            anchors.centerIn: parent\n            clip: true\n            opacity: (safetyStatusText.shown || lockStatusText.shown) ? 1 : 0\n            visible: opacity > 0\n            implicitHeight: statusTextRow.implicitHeight + 5 * 2\n            implicitWidth: statusTextRow.implicitWidth + 5 * 2\n            radius: Appearance.rounding.small\n            color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, root.clockStyle === \"cookie\" ? 0 : 1)\n\n            Behavior on implicitWidth {\n                animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n            }\n            Behavior on implicitHeight {\n                animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n            }\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n\n            RowLayout {\n                id: statusTextRow\n                anchors.centerIn: parent\n                spacing: 14\n                Item {\n                    Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignLeft\n                    implicitWidth: 1\n                }\n                ClockStatusText {\n                    id: safetyStatusText\n                    shown: root.wallpaperSafetyTriggered\n                    statusIcon: \"hide_image\"\n                    statusText: Translation.tr(\"Wallpaper safety enforced\")\n                }\n                ClockStatusText {\n                    id: lockStatusText\n                    shown: GlobalStates.screenLocked && Config.options.lock.showLockedText\n                    statusIcon: \"lock\"\n                    statusText: Translation.tr(\"Locked\")\n                }\n                Item {\n                    Layout.fillWidth: root.textHorizontalAlignment !== Text.AlignRight\n                    implicitWidth: 1\n                }\n            }\n        }\n    }\n\n    component ClockStatusText: Row {\n        id: statusTextRow\n        property alias statusIcon: statusIconWidget.text\n        property alias statusText: statusTextWidget.text\n        property bool shown: true\n        property color textColor: root.clockStyle === \"cookie\" ? Appearance.colors.colOnSecondaryContainer : root.colText\n        opacity: shown ? 1 : 0\n        visible: opacity > 0\n        Behavior on opacity {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n        spacing: 4\n        MaterialSymbol {\n            id: statusIconWidget\n            anchors.verticalCenter: statusTextRow.verticalCenter\n            iconSize: Appearance.font.pixelSize.huge\n            color: statusTextRow.textColor\n            style: Text.Raised\n            styleColor: Appearance.colors.colShadow\n        }\n        ClockText {\n            id: statusTextWidget\n            color: statusTextRow.textColor\n            horizontalAlignment: root.textHorizontalAlignment\n            anchors.verticalCenter: statusTextRow.verticalCenter\n            font {\n                pixelSize: Appearance.font.pixelSize.large\n                weight: Font.Normal\n            }\n            style: Text.Raised\n            styleColor: Appearance.colors.colShadow\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/CookieClock.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell.Io\n\nimport qs.modules.ii.background.widgets.clock.dateIndicator\nimport qs.modules.ii.background.widgets.clock.minuteMarks\n\nItem {\n    id: root\n\n    readonly property string clockStyle: Config.options.background.widgets.clock.style\n\n    property real implicitSize: 230\n\n    property color colShadow: Appearance.colors.colShadow\n    property color colBackground: Appearance.colors.colPrimaryContainer\n    property color colOnBackground: ColorUtils.mix(Appearance.colors.colSecondary, Appearance.colors.colPrimaryContainer, 0.15)\n    property color colBackgroundInfo: ColorUtils.mix(Appearance.colors.colPrimary, Appearance.colors.colPrimaryContainer, 0.55)\n    property color colHourHand: Appearance.colors.colPrimary\n    property color colMinuteHand: Appearance.colors.colTertiary\n    property color colSecondHand: Appearance.colors.colPrimary\n\n    readonly property list<string> clockNumbers: DateTime.time.split(/[: ]/)\n    readonly property int clockHour: parseInt(clockNumbers[0]) % 12\n    readonly property int clockMinute: DateTime.clock.minutes\n    readonly property int clockSecond: DateTime.clock.seconds\n\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    function applyStyle(sides, dialStyle, hourHandStyle, minuteHandStyle, secondHandStyle, dateStyle) {\n        Config.options.background.widgets.clock.cookie.sides = sides\n        Config.options.background.widgets.clock.cookie.dialNumberStyle = dialStyle\n        Config.options.background.widgets.clock.cookie.hourHandStyle = hourHandStyle\n        Config.options.background.widgets.clock.cookie.minuteHandStyle = minuteHandStyle\n        Config.options.background.widgets.clock.cookie.secondHandStyle = secondHandStyle\n        Config.options.background.widgets.clock.cookie.dateStyle = dateStyle\n    }\n\n    function setClockPreset(category) {\n        if (!Config.options.background.widgets.clock.cookie.aiStyling) return;\n        if (category === \"\") return;\n        print(\"[Cookie clock] Setting clock preset for category: \" + category)\n        // \"abstract\", \"anime\", \"city\", \"minimalist\", \"landscape\", \"plants\", \"person\", \"space\"\n        if (category == \"abstract\") {\n            applyStyle(9, \"none\", \"fill\", \"medium\", \"dot\", \"bubble\")\n        } else if (category == \"anime\") {\n            applyStyle(7, \"none\", \"fill\", \"bold\", \"dot\", \"bubble\")\n        } else if (category == \"city\" || category == \"space\") {\n            applyStyle(23, \"full\", \"hollow\", \"thin\", \"classic\", \"bubble\")\n        } else if (category == \"minimalist\") {\n            applyStyle(6, \"none\", \"fill\", \"bold\", \"dot\", \"hide\")\n        } else if (category == \"landscape\") {\n            applyStyle(14, \"full\", \"hollow\", \"medium\", \"classic\", \"bubble\")\n        } else if (category == \"plants\") {\n            applyStyle(9, \"dots\", \"fill\", \"bold\", \"dot\", \"border\")\n        } else if (category == \"person\") {\n            applyStyle(14, \"full\", \"classic\", \"classic\", \"classic\", \"rect\")\n        }\n    }\n\n    FileView {\n        id: categoryFileView\n        path: Config.ready ? Directories.generatedWallpaperCategoryPath : \"\"\n        watchChanges: true\n        onFileChanged: reload()\n        onLoaded: {\n            root.setClockPreset(categoryFileView.text().trim())\n        }\n    }\n\n    property bool useSineCookie: Config.options.background.widgets.clock.cookie.useSineCookie\n    StyledDropShadow {\n        target: root.useSineCookie ? sineCookieLoader : roundedPolygonCookieLoader\n\n        RotationAnimation on rotation {\n            running: Config.options.background.widgets.clock.cookie.constantlyRotate\n            duration: 30000\n            easing.type: Easing.Linear\n            loops: Animation.Infinite\n            from: 360\n            to: 0\n        }\n    }\n    Loader {\n        id: sineCookieLoader\n        z: 0\n        visible: false // The DropShadow already draws it\n        active: root.useSineCookie\n        sourceComponent: SineCookie {\n            implicitSize: root.implicitSize\n            sides: Config.options.background.widgets.clock.cookie.sides\n            color: root.colBackground\n        }\n    }\n    Loader {\n        id: roundedPolygonCookieLoader\n        z: 0\n        visible: false // The DropShadow already draws it\n        active: !root.useSineCookie\n        sourceComponent: MaterialCookie {\n            implicitSize: root.implicitSize\n            sides: Config.options.background.widgets.clock.cookie.sides\n            color: root.colBackground\n        }\n    }\n\n    // Hour/minutes numbers/dots/lines\n    MinuteMarks {\n        anchors.fill: parent\n        color: root.colOnBackground\n    }\n\n    // Stupid extra hour marks in the middle\n    FadeLoader {\n        id: hourMarksLoader\n        anchors.centerIn: parent\n        shown: Config.options.background.widgets.clock.cookie.hourMarks\n        sourceComponent: HourMarks {\n            implicitSize: 135 * (1.75 - 0.75 * hourMarksLoader.opacity)\n            color: root.colOnBackground\n            colOnBackground: ColorUtils.mix(root.colBackgroundInfo, root.colOnBackground, 0.5)\n        }\n    }\n\n    // Number column in the middle\n    FadeLoader {\n        id: timeColumnLoader\n        anchors.centerIn: parent\n        shown: Config.options.background.widgets.clock.cookie.timeIndicators\n        scale: 1.4 - 0.4 * timeColumnLoader.shown\n        Behavior on scale {\n            animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n        }\n\n        sourceComponent: TimeColumn {\n            color: root.colBackgroundInfo\n        }\n    }\n\n    // Minute hand\n    FadeLoader {\n        anchors.fill: parent\n        z: 1\n        shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== \"hide\"\n        sourceComponent: MinuteHand {\n            anchors.fill: parent\n            clockMinute: root.clockMinute\n            style: Config.options.background.widgets.clock.cookie.minuteHandStyle\n            color: root.colMinuteHand\n        }\n    }\n\n    // Hour hand\n    FadeLoader {\n        anchors.fill: parent\n        z: item?.style === \"hollow\" ? 0 : 2\n        shown: Config.options.background.widgets.clock.cookie.hourHandStyle !== \"hide\"\n        sourceComponent: HourHand {\n            clockHour: root.clockHour\n            clockMinute: root.clockMinute\n            style: Config.options.background.widgets.clock.cookie.hourHandStyle\n            color: root.colHourHand\n        }\n    }\n\n    // Second hand\n    FadeLoader {\n        id: secondHandLoader\n        z: (Config.options.background.widgets.clock.cookie.secondHandStyle === \"line\") ? 2 : 3\n        shown: Config.options.time.secondPrecision && Config.options.background.widgets.clock.cookie.secondHandStyle !== \"hide\"\n        anchors.fill: parent\n        sourceComponent: SecondHand {\n            id: secondHand\n            clockSecond: root.clockSecond\n            style: Config.options.background.widgets.clock.cookie.secondHandStyle\n            color: root.colSecondHand\n        }\n    }\n\n    // Center dot\n    FadeLoader {\n        z: 4\n        anchors.centerIn: parent\n        shown: Config.options.background.widgets.clock.cookie.minuteHandStyle !== \"bold\"\n        sourceComponent: Rectangle {\n            color: Config.options.background.widgets.clock.cookie.minuteHandStyle === \"medium\" ? root.colBackground : root.colMinuteHand\n            implicitWidth: 6\n            implicitHeight: implicitWidth\n            radius: width / 2\n        }\n    }\n\n    // Date\n    FadeLoader {\n        anchors.fill: parent\n        shown: Config.options.background.widgets.clock.cookie.dateStyle !== \"hide\"\n\n        sourceComponent: DateIndicator {\n            color: root.colBackgroundInfo\n            style: Config.options.background.widgets.clock.cookie.dateStyle\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/CookieQuote.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport Qt5Compat.GraphicalEffects\n\n\nItem {\n    id: root\n\n    readonly property string quoteText: Config.options.background.widgets.clock.quote.text\n\n    implicitWidth: quoteBox.implicitWidth\n    implicitHeight: quoteBox.implicitHeight\n\n    DropShadow {\n        source: quoteBox \n        anchors.fill: quoteBox\n        horizontalOffset: 0\n        verticalOffset: 2\n        radius: 12\n        samples: radius * 2 + 1\n        color: Appearance.colors.colShadow\n        transparentBorder: true\n    }\n    \n    Rectangle {\n        id: quoteBox\n\n        implicitWidth: quoteRow.implicitWidth + 8 * 2\n        implicitHeight: quoteRow.implicitHeight + 4 * 2\n        radius: Appearance.rounding.small\n        color: Appearance.colors.colSecondaryContainer\n\n        Row {\n            id: quoteRow\n            anchors.centerIn: parent\n            spacing: 4\n            \n            MaterialSymbol {\n                id: quoteIcon\n                anchors.top: parent.top\n                iconSize: Appearance.font.pixelSize.huge\n                text: \"format_quote\"\n                color: Appearance.colors.colOnSecondaryContainer\n            }\n            StyledText {\n                id: quoteStyledText\n                horizontalAlignment: Text.AlignLeft\n                text: Config.options.background.widgets.clock.quote.text\n                color: Appearance.colors.colOnSecondaryContainer\n                font {\n                    family: Appearance.font.family.reading\n                    pixelSize: Appearance.font.pixelSize.large\n                    weight: Font.Normal\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/DigitalClock.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\n\nColumnLayout {\n    id: clockColumn\n    spacing: 4\n\n    property bool isVertical: Config.options.background.widgets.clock.digital.vertical\n    property color colText: Appearance.colors.colOnSecondaryContainer\n    property var textHorizontalAlignment: Text.AlignHCenter\n\n    // Time\n    ClockText {\n        id: timeTextTop\n        text: clockColumn.isVertical ? DateTime.time.split(\":\")[0].padStart(2, \"0\") : DateTime.time\n        color: clockColumn.colText\n        horizontalAlignment: Text.AlignHCenter\n        font {\n            pixelSize: Config.options.background.widgets.clock.digital.font.size\n            weight: Config.options.background.widgets.clock.digital.font.weight\n            family: Config.options.background.widgets.clock.digital.font.family\n            variableAxes: ({\n                    \"wdth\": Config.options.background.widgets.clock.digital.font.width,\n                    \"ROND\": Config.options.background.widgets.clock.digital.font.roundness\n                })\n        }\n    }\n\n    Loader {\n        Layout.topMargin: -40\n        Layout.fillWidth: true\n        active: clockColumn.isVertical\n        visible: active\n        sourceComponent: ClockText {\n            id: timeTextBottom\n            text: DateTime.time.split(\":\")[1].split(\" \")[0].padStart(2, \"0\")\n            color: clockColumn.colText\n            horizontalAlignment: clockColumn.textHorizontalAlignment\n            font {\n                pixelSize: timeTextTop.font.pixelSize\n                weight: timeTextTop.font.weight\n                family: timeTextTop.font.family\n                variableAxes: timeTextTop.font.variableAxes\n            }\n        }\n    }\n\n    // Date\n    ClockText {\n        visible: Config.options.background.widgets.clock.digital.showDate\n        Layout.topMargin: -20\n        Layout.fillWidth: true\n        text: DateTime.longDate\n        color: clockColumn.colText\n        horizontalAlignment: clockColumn.textHorizontalAlignment\n    }\n\n    // Quote\n    ClockText {\n        visible: Config.options.background.widgets.clock.quote.enable && Config.options.background.widgets.clock.quote.text.length > 0\n        font.pixelSize: Appearance.font.pixelSize.normal\n        text: Config.options.background.widgets.clock.quote.text\n        animateChange: false\n        color: clockColumn.colText\n        horizontalAlignment: clockColumn.textHorizontalAlignment\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/HourHand.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport QtQuick\n\nItem {\n    id: root\n\n    required property int clockHour\n    required property int clockMinute\n    property real handLength: 72\n    property real handWidth: 20\n    property string style: \"fill\"\n    property color color: Appearance.colors.colPrimary\n\n    property real fillColorAlpha: root.style === \"hollow\" ? 0 : 1\n    Behavior on fillColorAlpha {\n        animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n    }\n\n    rotation: -90 + (360 / 12) * (root.clockHour + root.clockMinute / 60)\n    Behavior on rotation {\n        animation: RotationAnimation {\n            direction: RotationAnimation.Clockwise\n            duration: 300\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Appearance.animationCurves.emphasized\n        }\n    }\n\n    Rectangle {\n        anchors.verticalCenter: parent.verticalCenter\n        x: (parent.width - root.handWidth) / 2 - 15 * (root.style === \"classic\")\n        width: root.handLength\n        height: root.style === \"classic\" ? 8 : root.handWidth\n        radius: root.style === \"classic\" ? 2 : root.handWidth / 2\n        color : Qt.rgba(root.color.r, root.color.g, root.color.b, root.fillColorAlpha)\n        border.color: root.color\n        border.width: 4\n\n        Behavior on x {\n            animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/HourMarks.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\n\nItem {\n    id: root\n    property real implicitSize: 135\n    property real markLength: 12\n    property real markWidth: 4\n    property color color: Appearance.colors.colOnSecondaryContainer\n    property color colOnBackground: Appearance.colors.colSecondaryContainer\n    property real padding: 8\n\n    Rectangle {\n        color: root.color\n        anchors.centerIn: parent\n        implicitWidth: root.implicitSize\n        implicitHeight: root.implicitSize\n        radius: width / 2\n\n        // Hour mark lines\n        Repeater {\n            model: 12\n\n            Item {\n                required property int index\n                anchors.fill: parent\n                rotation: 360 / 12 * index \n\n                Rectangle {\n                    anchors {\n                        left: parent.left\n                        verticalCenter: parent.verticalCenter\n                        leftMargin: root.padding\n                    }\n                    implicitWidth: root.markLength\n                    implicitHeight: root.markWidth\n\n                    radius: width / 2\n                    color: root.colOnBackground\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/MinuteHand.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport QtQuick\n\nItem {\n    id: root\n    anchors.fill: parent\n\n    required property int clockMinute\n    property string style: \"medium\"\n    property real handLength: 95\n    property real handWidth: style === \"bold\" ? 20 : style === \"medium\" ? 12 : 5\n    property color color: Appearance.colors.colTertiary\n\n    rotation: -90 + (360 / 60) * root.clockMinute\n    Behavior on rotation {\n        animation: RotationAnimation {\n            direction: RotationAnimation.Clockwise\n            duration: 300\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Appearance.animationCurves.emphasized\n        }\n    }\n\n    Rectangle {\n        anchors.verticalCenter: parent.verticalCenter\n        x: {\n            let position = parent.width / 2 - root.handWidth / 2;\n            if (root.style === \"classic\") position -= 15;\n            return position;\n        }\n        width: root.handLength\n        height: root.handWidth\n        \n        radius: root.style === \"classic\" ? 2 : root.handWidth / 2\n        color: root.color\n\n        Behavior on height {\n            animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n        }\n\n        Behavior on x {\n            animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/SecondHand.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    anchors.fill: parent\n\n    required property int clockSecond\n    property real handWidth: 2\n    property real handLength: 95\n    property real dotSize: 20\n    property string style: \"hide\"\n    property color color: Appearance.colors.colSecondary\n    \n    rotation: (360 / 60 * clockSecond) + 90\n\n    Behavior on rotation {\n        enabled: Config.options.background.widgets.clock.cookie.constantlyRotate // Animating every second is expensive...\n        animation: RotationAnimation {\n            direction: RotationAnimation.Clockwise\n            duration: 1000 // 1 second\n            easing.type: Easing.InOutQuad\n        }\n    }\n\n    Rectangle {\n        anchors {\n            left: parent.left\n            verticalCenter: parent.verticalCenter\n            leftMargin: 10 + (root.style === \"dot\" ? root.dotSize : 0)\n        }\n        implicitWidth: root.style === \"dot\" ? root.dotSize : root.handLength\n        implicitHeight: root.style === \"dot\" ? root.dotSize : root.handWidth\n        radius: Math.min(width, height) / 2\n        color: root.color\n        Behavior on implicitHeight {\n            animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n        }\n        Behavior on implicitWidth {\n            animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n        }\n    }\n\n    // Classic style dot in the middle of the hand\n    FadeLoader {\n        id: classicDotLoader\n        anchors {\n            left: parent.left\n            verticalCenter: parent.verticalCenter\n        }\n        shown: root.style === \"classic\"\n        Rectangle {\n            anchors {\n                left: parent.left\n                verticalCenter: parent.verticalCenter\n                leftMargin: 40\n            }\n            implicitWidth: root.style === \"classic\" ? 14 : 0\n            implicitHeight: implicitWidth\n            color: root.color\n            radius: Appearance.rounding.small\n\n            Behavior on implicitWidth {\n                animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/TimeColumn.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nColumn {\n    id: root\n    property list<string> clockNumbers: DateTime.time.split(/[: ]/)\n    property bool isEnabled: Config.options.background.widgets.clock.cookie.timeIndicators\n    property color color: Appearance.colors.colOnSecondaryContainer\n\n    property bool hourMarksEnabled: Config.options.background.widgets.clock.cookie.hourMarks\n    spacing: -16\n\n    Repeater {\n        model: root.clockNumbers\n\n        delegate: StyledText {\n            required property string modelData\n            text: modelData.padStart(2, \"0\")\n            property bool isAmPm: !text.match(/\\d{2}/i)\n            property real numberSizeWithoutGlow: isAmPm ? 26 : 68\n            property real numberSizeWithGlow: isAmPm ? 20 : 40\n            property real numberSize: root.hourMarksEnabled ? numberSizeWithGlow : numberSizeWithoutGlow\n\n            anchors.horizontalCenter: root.horizontalCenter\n            color: root.color\n            font {\n                family: Appearance.font.family.expressive\n                weight: Font.Bold\n                pixelSize: numberSize\n            }\n\n            Behavior on numberSize {\n                animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/BubbleDate.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    property bool isMonth: false\n    property real targetSize: 0\n    property alias text: bubbleText.text\n\n    text: Qt.locale().toString(DateTime.clock.date, root.isMonth ? \"MM\" : \"d\")\n\n    MaterialShape {\n        id: bubble\n        z: 5\n        // sides: root.isMonth ? 1 : 4\n        shape: root.isMonth ? MaterialShape.Shape.Pill : MaterialShape.Shape.Pentagon\n        anchors.centerIn: parent\n        color: root.isMonth ? Appearance.colors.colSecondaryContainer : Appearance.colors.colTertiaryContainer\n        implicitSize: targetSize\n    }\n\n    StyledText {\n        id: bubbleText\n        z: 6\n        anchors.centerIn: parent\n        color: root.isMonth ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnTertiaryContainer\n        font {\n            family: Appearance.font.family.expressive\n            pixelSize: 30\n            weight: Font.Black\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/DateIndicator.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\n\nItem {\n    id: root\n    property string style: \"bubble\"\n    property color color: Appearance.colors.colOnSecondaryContainer\n    property real dateSquareSize: 64\n\n    // Rotating date\n    FadeLoader {\n        anchors.fill: parent\n        shown: Config.options.background.widgets.clock.cookie.dateStyle === \"border\"\n        sourceComponent: RotatingDate {\n            color: root.color\n        }\n    }\n\n    // Rectangle date (only today's number) in right side of the clock\n    FadeLoader {\n        id: rectLoader\n        shown: root.style === \"rect\"\n        anchors {\n            verticalCenter: parent.verticalCenter\n            right: parent.right\n            rightMargin: 40 - rectLoader.opacity * 30\n        }\n\n        sourceComponent: RectangleDate {\n            color: ColorUtils.mix(root.color, Appearance.colors.colSecondaryContainerHover, 0.5)\n            radius: Appearance.rounding.small\n            implicitWidth: 45 * rectLoader.opacity\n            implicitHeight: 30 * rectLoader.opacity\n        }\n    }\n\n    // Bubble style: day of month\n    FadeLoader {\n        id: dayBubbleLoader\n        shown: root.style === \"bubble\"\n        property real targetSize: root.dateSquareSize * opacity\n        anchors {\n            left: parent.left\n            top: parent.top\n        }\n\n        sourceComponent: BubbleDate {\n            implicitWidth: dayBubbleLoader.targetSize\n            implicitHeight: dayBubbleLoader.targetSize\n            isMonth: false\n            targetSize: dayBubbleLoader.targetSize\n        }\n    }\n\n    // Bubble style: month\n    FadeLoader {\n        id: monthBubbleLoader\n        shown: root.style === \"bubble\"\n        property real targetSize: root.dateSquareSize * opacity\n        anchors {\n            right: parent.right\n            bottom: parent.bottom\n        }\n\n        sourceComponent: BubbleDate {\n            implicitWidth: monthBubbleLoader.targetSize\n            implicitHeight: monthBubbleLoader.targetSize\n            isMonth: true\n            targetSize: monthBubbleLoader.targetSize\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/RectangleDate.qml",
    "content": "\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nRectangle {\n    id: rect\n    readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle\n\n    StyledText {\n        anchors.centerIn: parent\n        color: Appearance.colors.colSecondaryHover\n        text: Qt.locale().toString(DateTime.clock.date, \"dd\")\n        font {\n            family: Appearance.font.family.expressive\n            pixelSize: 20\n            weight: 1000\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/dateIndicator/RotatingDate.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n\n    property string style: Config.options.background.widgets.clock.cookie.dateStyle\n    property color color: Appearance.colors.colOnSecondaryContainer\n    property real angleStep: 12 * Math.PI / 180\n    property string dateText: Qt.locale().toString(DateTime.clock.date, \"ddd dd\")\n    \n    readonly property int clockSecond: DateTime.clock.seconds\n    readonly property string dialStyle: Config.options.background.widgets.clock.cookie.dialNumberStyle\n    readonly property bool timeIndicators: Config.options.background.widgets.clock.cookie.timeIndicators\n\n    property real radius: style === \"border\" ? 90 : 0\n    Behavior on radius {\n        animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n    }\n\n    rotation: {\n        if (!Config.options.time.secondPrecision) return 0\n        else return (360 / 60 * clockSecond) + 180 - (angleStep / Math.PI * 180 * dateText.length) / 2\n    }\n\n    Repeater {\n        model: root.dateText.length \n\n        delegate: Text {\n            required property int index\n            property real angle: index * root.angleStep - Math.PI / 2\n            x: root.width / 2 + root.radius * Math.cos(angle) - width / 2\n            y: root.height / 2 + root.radius * Math.sin(angle) - height / 2\n            rotation: angle * 180 / Math.PI + 90\n\n            color: root.color\n            font {\n                family: Appearance.font.family.title\n                pixelSize: 30\n                variableAxes: Appearance.font.variableAxes.title\n            }\n\n            text: root.dateText.charAt(index)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/BigHourNumbers.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    property real numberSize: 80\n    property real margins: 10\n    property color color: Appearance.colors.colOnSecondaryContainer\n\n    property int hours: 12\n    property int numbers: 4\n    property int fontSize: 80\n\n    Repeater {\n        model: root.numbers\n\n        Item {\n            id: numberItem\n            required property int index\n            rotation: 360 / root.numbers * (index + 1)\n            anchors.fill: parent\n            \n            Item {\n                implicitWidth: root.numberSize\n                implicitHeight: implicitWidth\n                anchors {\n                    top: parent.top\n                    horizontalCenter: parent.horizontalCenter\n                    topMargin: root.margins\n                }\n                StyledText {\n                    color: root.color\n                    anchors.centerIn: parent\n                    text: root.hours / root.numbers * (numberItem.index + 1)\n                    rotation: -numberItem.rotation\n\n                    font {\n                        family: Appearance.font.family.reading\n                        pixelSize: root.fontSize\n                        weight: Font.Black\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/Dots.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    property real implicitSize: 12\n    property real margins: 10\n    property color color: Appearance.colors.colOnSecondaryContainer\n\n    Repeater {\n        model: 12\n\n        Item {\n            required property int index\n            anchors.fill: parent // Ensures rotation works properly\n            rotation: 360 / 12 * index\n\n            Rectangle {\n                anchors {\n                    left: parent.left\n                    verticalCenter: parent.verticalCenter\n                    leftMargin: root.margins\n                }\n                implicitWidth: root.implicitSize\n                implicitHeight: implicitWidth\n                radius: implicitWidth / 2\n                color: root.color\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/Lines.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    property real numberSize: 80\n    property real margins: 10\n    property color color: Appearance.colors.colOnSecondaryContainer\n\n    property real hourLineSize: 4\n    property real minuteLineSize: 2\n    property real hourLineLength: 18\n    property real minuteLineLength: 7\n\n    property int hours: 12\n    property int minutes: 60\n\n    // Full dial style hour lines\n    Repeater {\n        model: root.hours\n\n        Item {\n            required property int index\n            rotation: 360 / root.hours * index\n            anchors.fill: parent\n\n            Rectangle {\n                anchors {\n                    left: parent.left\n                    verticalCenter: parent.verticalCenter\n                    leftMargin: root.margins\n                }\n                implicitWidth: root.hourLineLength\n                implicitHeight: root.hourLineSize\n                radius: implicitWidth / 2\n                color: root.color\n            }\n        }\n    }\n\n    // Minute lines\n    Repeater {\n        model: root.minutes\n\n        Item {\n            required property int index\n            rotation: 360 / root.minutes * index \n            anchors.fill: parent\n\n            Rectangle {\n                anchors {\n                    left: parent.left\n                    verticalCenter: parent.verticalCenter\n                    leftMargin: root.margins\n                }\n                implicitWidth: root.minuteLineLength\n                implicitHeight: root.minuteLineSize\n                radius: implicitWidth / 2\n                color: root.color\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/clock/minuteMarks/MinuteMarks.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n\n    property color color: Appearance.colors.colOnSecondaryContainer\n    property string style: Config.options.background.widgets.clock.cookie.dialNumberStyle // \"dots\", \"numbers\", \"full\", \"hide\"\n    property string dateStyle : Config.options.background.widgets.clock.cookie.dateStyle\n\n    // 12 Dots\n    FadeLoader {\n        id: dotsLoader\n        anchors {\n            fill: parent\n            margins: 10\n        }\n        shown: root.style === \"dots\"\n        sourceComponent: Dots {\n            color: root.color\n            margins: 46 - dotsLoader.opacity * 34\n        }\n    }\n\n    // 3-6-9-12 hour numbers (pls don't realize you can have more than 4 numbers)\n    FadeLoader {\n        id: bigHourNumbersLoader\n        anchors.fill: parent\n        shown: root.style === \"numbers\"\n        sourceComponent: BigHourNumbers {\n            numberSize: 80\n            color: root.color\n            margins: 20 - 10 * bigHourNumbersLoader.opacity\n        }\n    }\n\n    // Lines\n    FadeLoader {\n        id: linesLoader\n        anchors {\n            fill: parent\n            margins: 10\n        }\n        shown: root.style === \"full\"\n        sourceComponent: Lines {\n            color: root.color\n            margins: 46 - linesLoader.opacity * 34\n        }\n    }\n    \n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/background/widgets/weather/WeatherWidget.qml",
    "content": "import QtQuick\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\nimport qs.modules.ii.background.widgets\n\nAbstractBackgroundWidget {\n    id: root\n\n    configEntryName: \"weather\"\n\n    implicitHeight: backgroundShape.implicitHeight\n    implicitWidth: backgroundShape.implicitWidth\n\n    StyledDropShadow {\n        target: backgroundShape\n    }\n\n    MaterialShape {\n        id: backgroundShape\n        anchors.fill: parent\n        shape: MaterialShape.Shape.Pill\n        color: Appearance.colors.colPrimaryContainer\n        implicitSize: 200\n\n        StyledText {\n            font {\n                pixelSize: 80\n                family: Appearance.font.family.expressive\n                weight: Font.Medium\n            }\n            color: Appearance.colors.colPrimary\n            text: Weather.data?.temp.substring(0,Weather.data?.temp.length - 1) ?? \"--°\"\n            anchors {\n                right: parent.right\n                top: parent.top\n                rightMargin: 16\n                topMargin: 20\n            }\n        }\n\n        MaterialSymbol {\n            iconSize: 80\n            color: Appearance.colors.colOnPrimaryContainer\n            text: Icons.getWeatherIcon(Weather.data.wCode) ?? \"cloud\"\n            anchors {\n                left: parent.left\n                bottom: parent.bottom\n\n                leftMargin: 16\n                bottomMargin: 20\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/ActiveWindow.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nItem {\n    id: root\n    readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)\n    readonly property Toplevel activeWindow: ToplevelManager.activeToplevel\n\n    property string activeWindowAddress: `0x${activeWindow?.HyprlandToplevel?.address}`\n    property bool focusingThisMonitor: HyprlandData.activeWorkspace?.monitor == monitor?.name\n    property var biggestWindow: HyprlandData.biggestWindowForWorkspace(HyprlandData.monitors[root.monitor?.id]?.activeWorkspace.id)\n\n    implicitWidth: colLayout.implicitWidth\n\n    ColumnLayout {\n        id: colLayout\n\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.left: parent.left\n        anchors.right: parent.right\n        spacing: -4\n\n        StyledText {\n            Layout.fillWidth: true\n            font.pixelSize: Appearance.font.pixelSize.smaller\n            color: Appearance.colors.colSubtext\n            elide: Text.ElideRight\n            text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ? \n                root.activeWindow?.appId :\n                (root.biggestWindow?.class) ?? Translation.tr(\"Desktop\")\n\n        }\n\n        StyledText {\n            Layout.fillWidth: true\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnLayer0\n            elide: Text.ElideRight\n            text: root.focusingThisMonitor && root.activeWindow?.activated && root.biggestWindow ? \n                root.activeWindow?.title :\n                (root.biggestWindow?.title) ?? `${Translation.tr(\"Workspace\")} ${monitor?.activeWorkspace?.id ?? 1}`\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/Bar.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nScope {\n    id: bar\n    property bool showBarBackground: Config.options.bar.showBackground\n\n    Variants {\n        // For each monitor\n        model: {\n            const screens = Quickshell.screens;\n            const list = Config.options.bar.screenList;\n            if (!list || list.length === 0)\n                return screens;\n            return screens.filter(screen => list.includes(screen.name));\n        }\n        LazyLoader {\n            id: barLoader\n            active: GlobalStates.barOpen && !GlobalStates.screenLocked\n            required property ShellScreen modelData\n            component: PanelWindow { // Bar window\n                id: barRoot\n                screen: barLoader.modelData\n\n                Timer {\n                    id: showBarTimer\n                    interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)\n                    repeat: false\n                    onTriggered: {\n                        barRoot.superShow = true\n                    }\n                }\n                Connections {\n                    target: GlobalStates\n                    function onSuperDownChanged() {\n                        if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return;\n                        if (GlobalStates.superDown) showBarTimer.restart();\n                        else {\n                            showBarTimer.stop();\n                            barRoot.superShow = false;\n                        }\n                    }\n                }\n                property bool superShow: false\n                property bool mustShow: hoverRegion.containsMouse || superShow\n                exclusionMode: ExclusionMode.Ignore\n                exclusiveZone: (Config?.options.bar.autoHide.enable && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 :\n                    Appearance.sizes.baseBarHeight + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)\n                WlrLayershell.namespace: \"quickshell:bar\"\n                implicitHeight: Appearance.sizes.barHeight + Appearance.rounding.screenRounding\n                mask: Region {\n                    item: hoverMaskRegion\n                }\n                color: \"transparent\"\n\n                // Positioning\n                anchors {\n                    top: !Config.options.bar.bottom\n                    bottom: Config.options.bar.bottom\n                    left: true\n                    right: true\n                }\n\n                margins {\n                    right: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.right) * -1\n                    bottom: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * -1\n                }\n\n                // Include in focus grab\n                Component.onCompleted: {\n                    GlobalFocusGrab.addPersistent(barRoot);\n                }\n                Component.onDestruction: {\n                    GlobalFocusGrab.removePersistent(barRoot);\n                }\n\n                MouseArea  {\n                    id: hoverRegion\n                    hoverEnabled: true\n                    anchors {\n                        fill: parent\n                        rightMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.right) * 1\n                        bottomMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * 1\n                    }\n\n                    Item {\n                        id: hoverMaskRegion\n                        anchors {\n                            fill: barContent\n                            topMargin: -Config.options.bar.autoHide.hoverRegionWidth\n                            bottomMargin: -Config.options.bar.autoHide.hoverRegionWidth\n                        }\n                    }\n\n                    BarContent {\n                        id: barContent\n                        \n                        implicitHeight: Appearance.sizes.barHeight\n                        anchors {\n                            right: parent.right\n                            left: parent.left\n                            top: parent.top\n                            bottom: undefined\n                            topMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight : 0\n                            bottomMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.bottom) * -1\n                            rightMargin: (Config.options.interactions.deadPixelWorkaround.enable && barRoot.anchors.right) * -1\n                        }\n                        Behavior on anchors.topMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        Behavior on anchors.bottomMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n\n                        states: State {\n                            name: \"bottom\"\n                            when: Config.options.bar.bottom\n                            AnchorChanges {\n                                target: barContent\n                                anchors {\n                                    right: parent.right\n                                    left: parent.left\n                                    top: undefined\n                                    bottom: parent.bottom\n                                }\n                            }\n                            PropertyChanges {\n                                target: barContent\n                                anchors.topMargin: 0\n                                anchors.bottomMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight : 0\n                            }\n                        }\n                    }\n\n                    // Round decorators\n                    Loader {\n                        id: roundDecorators\n                        anchors {\n                            left: parent.left\n                            right: parent.right\n                            top: barContent.bottom\n                            bottom: undefined\n                        }\n                        height: Appearance.rounding.screenRounding\n                        active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug\n\n                        states: State {\n                            name: \"bottom\"\n                            when: Config.options.bar.bottom\n                            AnchorChanges {\n                                target: roundDecorators\n                                anchors {\n                                    right: parent.right\n                                    left: parent.left\n                                    top: undefined\n                                    bottom: barContent.top\n                                }\n                            }\n                        }\n\n                        sourceComponent: Item {\n                            implicitHeight: Appearance.rounding.screenRounding\n                            RoundCorner {\n                                id: leftCorner\n                                anchors {\n                                    top: parent.top\n                                    bottom: parent.bottom\n                                    left: parent.left\n                                }\n\n                                implicitSize: Appearance.rounding.screenRounding\n                                color: showBarBackground ? Appearance.colors.colLayer0 : \"transparent\"\n\n                                corner: RoundCorner.CornerEnum.TopLeft\n                                states: State {\n                                    name: \"bottom\"\n                                    when: Config.options.bar.bottom\n                                    PropertyChanges {\n                                        leftCorner.corner: RoundCorner.CornerEnum.BottomLeft\n                                    }\n                                }\n                            }\n                            RoundCorner {\n                                id: rightCorner\n                                anchors {\n                                    right: parent.right\n                                    top: !Config.options.bar.bottom ? parent.top : undefined\n                                    bottom: Config.options.bar.bottom ? parent.bottom : undefined\n                                }\n                                implicitSize: Appearance.rounding.screenRounding\n                                color: showBarBackground ? Appearance.colors.colLayer0 : \"transparent\"\n\n                                corner: RoundCorner.CornerEnum.TopRight\n                                states: State {\n                                    name: \"bottom\"\n                                    when: Config.options.bar.bottom\n                                    PropertyChanges {\n                                        rightCorner.corner: RoundCorner.CornerEnum.BottomRight\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"bar\"\n\n        function toggle(): void {\n            GlobalStates.barOpen = !GlobalStates.barOpen\n        }\n\n        function close(): void {\n            GlobalStates.barOpen = false\n        }\n\n        function open(): void {\n            GlobalStates.barOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barToggle\"\n        description: \"Toggles bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = !GlobalStates.barOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barOpen\"\n        description: \"Opens bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barClose\"\n        description: \"Closes bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/BarContent.qml",
    "content": "import qs.modules.ii.bar.weather\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.UPower\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nItem { // Bar content region\n    id: root\n\n    property var screen: root.QsWindow.window?.screen\n    property var brightnessMonitor: Brightness.getMonitorForScreen(screen)\n    property real useShortenedForm: (Appearance.sizes.barHellaShortenScreenWidthThreshold >= screen?.width) ? 2 : (Appearance.sizes.barShortenScreenWidthThreshold >= screen?.width) ? 1 : 0\n    readonly property int centerSideModuleWidth: (useShortenedForm == 2) ? Appearance.sizes.barCenterSideModuleWidthHellaShortened : (useShortenedForm == 1) ? Appearance.sizes.barCenterSideModuleWidthShortened : Appearance.sizes.barCenterSideModuleWidth\n\n    component VerticalBarSeparator: Rectangle {\n        Layout.topMargin: Appearance.sizes.baseBarHeight / 3\n        Layout.bottomMargin: Appearance.sizes.baseBarHeight / 3\n        Layout.fillHeight: true\n        implicitWidth: 1\n        color: Appearance.colors.colOutlineVariant\n    }\n\n    // Background shadow\n    Loader {\n        active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1 && Config.options.bar.floatStyleShadow\n        anchors.fill: barBackground\n        sourceComponent: StyledRectangularShadow {\n            anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor\n            target: barBackground\n        }\n    }\n    // Background\n    Rectangle {\n        id: barBackground\n        anchors {\n            fill: parent\n            margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed\n        }\n        color: Config.options.bar.showBackground ? Appearance.colors.colLayer0 : \"transparent\"\n        radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0\n        border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0\n        border.color: Appearance.colors.colLayer0Border\n    }\n\n    FocusedScrollMouseArea { // Left side | scroll to change brightness\n        id: barLeftSideMouseArea\n\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n            left: parent.left\n            right: middleSection.left\n        }\n        implicitWidth: leftSectionRowLayout.implicitWidth\n        implicitHeight: Appearance.sizes.baseBarHeight\n\n        onScrollDown: Brightness.decreaseBrightness()\n        onScrollUp: Brightness.increaseBrightness()\n        onMovedAway: GlobalStates.osdBrightnessOpen = false\n        onPressed: event => {\n            if (event.button === Qt.LeftButton)\n                GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n        }\n\n        // Visual content\n        ScrollHint {\n            reveal: barLeftSideMouseArea.hovered\n            icon: Hyprsunset.gamma === 100 ? \"light_mode\" : \"wb_twilight\"\n            tooltipText: Translation.tr(\"Scroll to change brightness\")\n            side: \"left\"\n            anchors.left: parent.left\n            anchors.verticalCenter: parent.verticalCenter\n        }\n\n        RowLayout {\n            id: leftSectionRowLayout\n            anchors.fill: parent\n            spacing: 0\n\n            LeftSidebarButton { // Left sidebar button\n                id: leftSidebarButton\n                Layout.alignment: Qt.AlignVCenter\n                Layout.leftMargin: Appearance.rounding.screenRounding\n                colBackground: barLeftSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)\n            }\n\n            ActiveWindow {\n                Layout.leftMargin: 10 + (leftSidebarButton.visible ? 0 : Appearance.rounding.screenRounding)\n                Layout.rightMargin: Appearance.rounding.screenRounding\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                visible: root.useShortenedForm === 0\n            }\n        }\n    }\n\n    Row { // Middle section\n        id: middleSection\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n            horizontalCenter: parent.horizontalCenter\n        }\n        spacing: 4\n\n        BarGroup {\n            id: leftCenterGroup\n            anchors.verticalCenter: parent.verticalCenter\n            implicitWidth: root.centerSideModuleWidth\n\n            Resources {\n                alwaysShowAllResources: root.useShortenedForm === 2\n                Layout.fillWidth: root.useShortenedForm === 2\n            }\n\n            Media {\n                visible: root.useShortenedForm < 2\n                Layout.fillWidth: true\n            }\n        }\n\n        VerticalBarSeparator {\n            visible: Config.options?.bar.borderless\n        }\n\n        BarGroup {\n            id: middleCenterGroup\n            anchors.verticalCenter: parent.verticalCenter\n            padding: workspacesWidget.widgetPadding\n\n            Workspaces {\n                id: workspacesWidget\n                Layout.fillHeight: true\n                MouseArea {\n                    // Right-click to toggle overview\n                    anchors.fill: parent\n                    acceptedButtons: Qt.RightButton\n\n                    onPressed: event => {\n                        if (event.button === Qt.RightButton) {\n                            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n                        }\n                    }\n                }\n            }\n        }\n\n        VerticalBarSeparator {\n            visible: Config.options?.bar.borderless\n        }\n\n        MouseArea {\n            id: rightCenterGroup\n            anchors.verticalCenter: parent.verticalCenter\n            implicitWidth: root.centerSideModuleWidth\n            implicitHeight: rightCenterGroupContent.implicitHeight\n\n            onPressed: {\n                GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n            }\n\n            BarGroup {\n                id: rightCenterGroupContent\n                anchors.fill: parent\n\n                ClockWidget {\n                    showDate: (Config.options.bar.verbose && root.useShortenedForm < 2)\n                    Layout.alignment: Qt.AlignVCenter\n                    Layout.fillWidth: true\n                }\n\n                UtilButtons {\n                    visible: (Config.options.bar.verbose && root.useShortenedForm === 0)\n                    Layout.alignment: Qt.AlignVCenter\n                }\n\n                BatteryIndicator {\n                    visible: (root.useShortenedForm < 2 && Battery.available)\n                    Layout.alignment: Qt.AlignVCenter\n                }\n            }\n        }\n    }\n\n    FocusedScrollMouseArea { // Right side | scroll to change volume\n        id: barRightSideMouseArea\n\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n            left: middleSection.right\n            right: parent.right\n        }\n        implicitWidth: rightSectionRowLayout.implicitWidth\n        implicitHeight: Appearance.sizes.baseBarHeight\n\n        onScrollDown: Audio.decrementVolume();\n        onScrollUp: Audio.incrementVolume();\n        onMovedAway: GlobalStates.osdVolumeOpen = false;\n        onPressed: event => {\n            if (event.button === Qt.LeftButton) {\n                GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n            }\n        }\n\n        // Visual content\n        ScrollHint {\n            reveal: barRightSideMouseArea.hovered\n            icon: \"volume_up\"\n            tooltipText: Translation.tr(\"Scroll to change volume\")\n            side: \"right\"\n            anchors.right: parent.right\n            anchors.verticalCenter: parent.verticalCenter\n        }\n\n        RowLayout {\n            id: rightSectionRowLayout\n            anchors.fill: parent\n            spacing: 5\n            layoutDirection: Qt.RightToLeft\n\n            RippleButton { // Right sidebar button\n                id: rightSidebarButton\n\n                Layout.alignment: Qt.AlignRight | Qt.AlignVCenter\n                Layout.rightMargin: Appearance.rounding.screenRounding\n                Layout.fillWidth: false\n\n                implicitWidth: indicatorsRowLayout.implicitWidth + 10 * 2\n                implicitHeight: indicatorsRowLayout.implicitHeight + 5 * 2\n\n                buttonRadius: Appearance.rounding.full\n                colBackground: barRightSideMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)\n                colBackgroundHover: Appearance.colors.colLayer1Hover\n                colRipple: Appearance.colors.colLayer1Active\n                colBackgroundToggled: Appearance.colors.colSecondaryContainer\n                colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n                colRippleToggled: Appearance.colors.colSecondaryContainerActive\n                toggled: GlobalStates.sidebarRightOpen\n                property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0\n\n                Behavior on colText {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n\n                onPressed: {\n                    GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n                }\n\n                RowLayout {\n                    id: indicatorsRowLayout\n                    anchors.centerIn: parent\n                    property real realSpacing: 15\n                    spacing: 0\n\n                    Revealer {\n                        reveal: Audio.sink?.audio?.muted ?? false\n                        Layout.fillHeight: true\n                        Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0\n                        Behavior on Layout.rightMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        MaterialSymbol {\n                            text: \"volume_off\"\n                            iconSize: Appearance.font.pixelSize.larger\n                            color: rightSidebarButton.colText\n                        }\n                    }\n                    Revealer {\n                        reveal: Audio.source?.audio?.muted ?? false\n                        Layout.fillHeight: true\n                        Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0\n                        Behavior on Layout.rightMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        MaterialSymbol {\n                            text: \"mic_off\"\n                            iconSize: Appearance.font.pixelSize.larger\n                            color: rightSidebarButton.colText\n                        }\n                    }\n                    HyprlandXkbIndicator {\n                        Layout.alignment: Qt.AlignVCenter\n                        Layout.rightMargin: indicatorsRowLayout.realSpacing\n                        color: rightSidebarButton.colText\n                    }\n                    Revealer {\n                        reveal: Notifications.silent || Notifications.unread > 0\n                        Layout.fillHeight: true\n                        Layout.rightMargin: reveal ? indicatorsRowLayout.realSpacing : 0\n                        implicitHeight: reveal ? notificationUnreadCount.implicitHeight : 0\n                        implicitWidth: reveal ? notificationUnreadCount.implicitWidth : 0\n                        Behavior on Layout.rightMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        NotificationUnreadCount {\n                            id: notificationUnreadCount\n                        }\n                    }\n                    MaterialSymbol {\n                        text: Network.materialSymbol\n                        iconSize: Appearance.font.pixelSize.larger\n                        color: rightSidebarButton.colText\n                    }\n                    MaterialSymbol {\n                        Layout.leftMargin: indicatorsRowLayout.realSpacing\n                        visible: BluetoothStatus.available\n                        text: BluetoothStatus.connected ? \"bluetooth_connected\" : BluetoothStatus.enabled ? \"bluetooth\" : \"bluetooth_disabled\"\n                        iconSize: Appearance.font.pixelSize.larger\n                        color: rightSidebarButton.colText\n                    }\n                }\n            }\n\n            SysTray {\n                visible: root.useShortenedForm === 0\n                Layout.fillWidth: false\n                Layout.fillHeight: true\n                invertSide: Config?.options.bar.bottom\n            }\n\n            Item {\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n            }\n\n            // Weather\n            Loader {\n                Layout.leftMargin: 4\n                active: Config.options.bar.weather.enable\n\n                sourceComponent: BarGroup {\n                    WeatherBar {}\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/BarGroup.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    property bool vertical: false\n    property real padding: 5\n    implicitWidth: vertical ? Appearance.sizes.baseVerticalBarWidth : (gridLayout.implicitWidth + padding * 2)\n    implicitHeight: vertical ? (gridLayout.implicitHeight + padding * 2) : Appearance.sizes.baseBarHeight\n    default property alias items: gridLayout.children\n\n    Rectangle {\n        id: background\n        anchors {\n            fill: parent\n            topMargin: root.vertical ? 0 : 4\n            bottomMargin: root.vertical ? 0 : 4\n            leftMargin: root.vertical ? 4 : 0\n            rightMargin: root.vertical ? 4 : 0\n        }\n        color: Config.options?.bar.borderless ? \"transparent\" : Appearance.colors.colLayer1\n        radius: Appearance.rounding.small\n    }\n\n    GridLayout {\n        id: gridLayout\n        columns: root.vertical ? 1 : -1\n        anchors {\n            verticalCenter: root.vertical ? undefined : parent.verticalCenter\n            horizontalCenter: root.vertical ? parent.horizontalCenter : undefined\n            left: root.vertical ? undefined : parent.left\n            right: root.vertical ? undefined : parent.right\n            top: root.vertical ? parent.top : undefined\n            bottom: root.vertical ? parent.bottom : undefined\n            margins: root.padding\n        }\n        columnSpacing: 4\n        rowSpacing: 12\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/BatteryIndicator.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nMouseArea {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    readonly property var chargeState: Battery.chargeState\n    readonly property bool isCharging: Battery.isCharging\n    readonly property bool isPluggedIn: Battery.isPluggedIn\n    readonly property real percentage: Battery.percentage\n    readonly property bool isLow: percentage <= Config.options.battery.low / 100\n\n    implicitWidth: batteryProgress.implicitWidth\n    implicitHeight: Appearance.sizes.barHeight\n\n    hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n    ClippedProgressBar {\n        id: batteryProgress\n        anchors.centerIn: parent\n        value: percentage\n        highlightColor: (isLow && !isCharging) ? Appearance.m3colors.m3error : Appearance.colors.colOnSecondaryContainer\n\n        Item {\n            anchors.centerIn: parent\n            width: batteryProgress.valueBarWidth\n            height: batteryProgress.valueBarHeight\n\n            RowLayout {\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    bottom: parent.bottom\n                    bottomMargin: (parent.height - height) / 2\n                }\n                spacing: 0\n\n                MaterialSymbol {\n                    id: boltIcon\n                    Layout.alignment: Qt.AlignVCenter\n                    Layout.leftMargin: -2\n                    Layout.rightMargin: -2\n                    fill: 1\n                    text: \"bolt\"\n                    iconSize: Appearance.font.pixelSize.smaller\n                    visible: isCharging && percentage < 1 // TODO: animation\n                }\n                StyledText {\n                    Layout.alignment: Qt.AlignVCenter\n                    font: batteryProgress.font\n                    text: batteryProgress.text\n                }\n            }\n        }\n    }\n\n    BatteryPopup {\n        id: batteryPopup\n        hoverTarget: root\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/BatteryPopup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nStyledPopup {\n    id: root\n    \n    ColumnLayout {\n        id: columnLayout\n        anchors.centerIn: parent\n        spacing: 4\n\n        // Header\n        StyledPopupHeaderRow {\n            icon: \"battery_android_full\"\n            label: Translation.tr(\"Battery\")\n        }\n\n        StyledPopupValueRow {\n            visible: {\n                let timeValue = Battery.isCharging ? Battery.timeToFull : Battery.timeToEmpty;\n                let power = Battery.energyRate;\n                return !(Battery.chargeState == 4 || timeValue <= 0 || power <= 0.01);\n            }\n            icon: \"schedule\"\n            label: Battery.isCharging ? Translation.tr(\"Time to full:\") : Translation.tr(\"Time to empty:\")\n            value: {\n                function formatTime(seconds) {\n                    var h = Math.floor(seconds / 3600);\n                    var m = Math.floor((seconds % 3600) / 60);\n                    if (h > 0)\n                        return `${h}h, ${m}m`;\n                    else\n                        return `${m}m`;\n                }\n                if (Battery.isCharging)\n                    return formatTime(Battery.timeToFull);\n                else\n                    return formatTime(Battery.timeToEmpty);\n            }\n        }\n\n        StyledPopupValueRow {\n            visible:  !(Battery.chargeState != 4 && Battery.energyRate == 0)\n            icon: \"bolt\"\n            label: {\n                if (Battery.chargeState == 4) {\n                    return Translation.tr(\"Fully charged\");\n                } else if (Battery.chargeState == 1) {\n                    return Translation.tr(\"Charging:\");\n                } else {\n                    return Translation.tr(\"Discharging:\");\n                }\n            }\n            value: {\n                if (Battery.chargeState == 4) {\n                    return \"\";\n                } else {\n                    return `${Battery.energyRate.toFixed(2)}W`;\n                }\n            }\n        }\n\n        StyledPopupValueRow {\n            icon: \"heart_check\"\n            label: Translation.tr(\"Health:\")\n            value: `${(Battery.health).toFixed(1)}%`\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/CircleUtilButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nRippleButton {\n    id: button\n\n    required default property Item content\n    property bool extraActiveCondition: false\n\n    implicitHeight: Math.max(content.implicitHeight, 26, content.implicitHeight)\n    implicitWidth: implicitHeight\n    contentItem: content\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/ClockWidget.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    property bool showDate: Config.options.bar.verbose\n    implicitWidth: rowLayout.implicitWidth\n    implicitHeight: Appearance.sizes.barHeight\n\n    RowLayout {\n        id: rowLayout\n        anchors.centerIn: parent\n        spacing: 4\n\n        StyledText {\n            font.pixelSize: Appearance.font.pixelSize.large\n            color: Appearance.colors.colOnLayer1\n            text: DateTime.time\n        }\n\n        StyledText {\n            visible: root.showDate\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnLayer1\n            text: \"•\"\n        }\n\n        StyledText {\n            visible: root.showDate\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnLayer1\n            text: DateTime.longDate\n        }\n    }\n\n    MouseArea {\n        id: mouseArea\n        anchors.fill: parent\n        hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n        ClockWidgetPopup {\n            hoverTarget: mouseArea\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/ClockWidgetPopup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nStyledPopup {\n    id: root\n    property string formattedDate: Qt.locale().toString(DateTime.clock.date, \"dddd, MMMM dd, yyyy\")\n    property string formattedTime: DateTime.time\n    property string formattedUptime: DateTime.uptime\n    property string todosSection: getUpcomingTodos()\n\n    function getUpcomingTodos() {\n        const unfinishedTodos = Todo.list.filter(function (item) {\n            return !item.done;\n        });\n        if (unfinishedTodos.length === 0) {\n            return Translation.tr(\"No pending tasks\");\n        }\n\n        // Limit to first 5 todos to keep popup manageable\n        const limitedTodos = unfinishedTodos.slice(0, 5);\n        let todoText = limitedTodos.map(function (item, index) {\n            return `  ${index + 1}. ${item.content}`;\n        }).join('\\n');\n\n        if (unfinishedTodos.length > 5) {\n            todoText += `\\n  ${Translation.tr(\"... and %1 more\").arg(unfinishedTodos.length - 5)}`;\n        }\n\n        return todoText;\n    }\n\n    ColumnLayout {\n        id: columnLayout\n        anchors.centerIn: parent\n        spacing: 4\n\n        StyledPopupHeaderRow {\n            icon: \"calendar_month\"\n            label: root.formattedDate\n        }\n\n        StyledPopupValueRow {\n            icon: \"timelapse\"\n            label: Translation.tr(\"System uptime:\")\n            value: root.formattedUptime\n        }\n\n        // Tasks\n        Column {\n            spacing: 0\n            Layout.fillWidth: true\n\n            StyledPopupValueRow {\n                icon: \"checklist\"\n                label: Translation.tr(\"To Do:\")\n                value: \"\"\n            }\n\n            StyledText {\n                horizontalAlignment: Text.AlignLeft\n                wrapMode: Text.Wrap\n                color: Appearance.colors.colOnSurfaceVariant\n                text: root.todosSection\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/HyprlandXkbIndicator.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nLoader {\n    id: root\n    property bool vertical: false\n    property color color: Appearance.colors.colOnSurfaceVariant\n    active: HyprlandXkb.layoutCodes.length > 1\n    visible: active\n\n    function abbreviateLayoutCode(fullCode) {\n    return fullCode.split(':').map(layout => {\n            const baseLayout = layout.split('-')[0];\n            return baseLayout.slice(0, 4);\n        }).join('\\n');\n    }\n\n    sourceComponent: Item {\n        implicitWidth: root.vertical ? null : layoutCodeText.implicitWidth\n        implicitHeight: root.vertical ? layoutCodeText.implicitHeight : null\n\n        StyledText {\n            id: layoutCodeText\n            anchors.centerIn: parent\n            horizontalAlignment: Text.AlignHCenter\n            text: abbreviateLayoutCode(HyprlandXkb.currentLayoutCode)\n            font.pixelSize: text.includes(\"\\n\") ? Appearance.font.pixelSize.smallie : Appearance.font.pixelSize.small\n            color: root.color\n            animateChange: true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/LeftSidebarButton.qml",
    "content": "import QtQuick\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRippleButton {\n    id: root\n\n    property bool showPing: false\n\n    property bool aiChatEnabled: Config.options.policies.ai !== 0\n    property bool translatorEnabled: Config.options.sidebar.translator.enable\n    property bool animeEnabled: Config.options.policies.weeb !== 0\n    visible: aiChatEnabled || translatorEnabled || animeEnabled\n\n    property real buttonPadding: 5\n    implicitWidth: distroIcon.width + buttonPadding * 2\n    implicitHeight: distroIcon.height + buttonPadding * 2\n    buttonRadius: Appearance.rounding.full\n    colBackgroundHover: Appearance.colors.colLayer1Hover\n    colRipple: Appearance.colors.colLayer1Active\n    colBackgroundToggled: Appearance.colors.colSecondaryContainer\n    colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n    colRippleToggled: Appearance.colors.colSecondaryContainerActive\n    toggled: GlobalStates.sidebarLeftOpen\n\n    onPressed: {\n        GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n    }\n\n    Connections {\n        target: Ai\n        function onResponseFinished() {\n            if (GlobalStates.sidebarLeftOpen) return;\n            root.showPing = true;\n        }\n    }\n\n    Connections {\n        target: Booru\n        function onResponseFinished() {\n            if (GlobalStates.sidebarLeftOpen) return;\n            root.showPing = true;\n        }\n    }\n\n    Connections {\n        target: GlobalStates\n        function onSidebarLeftOpenChanged() {\n            root.showPing = false;\n        }\n    }\n\n    CustomIcon {\n        id: distroIcon\n        anchors.centerIn: parent\n        width: 19.5\n        height: 19.5\n        source: Config.options.bar.topLeftIcon == 'distro' ? SystemInfo.distroIcon : `${Config.options.bar.topLeftIcon}-symbolic`\n        colorize: true\n        color: Appearance.colors.colOnLayer0\n\n        Rectangle {\n            opacity: root.showPing ? 1 : 0\n            visible: opacity > 0\n            anchors {\n                bottom: parent.bottom\n                right: parent.right\n                bottomMargin: -2\n                rightMargin: -2\n            }\n            implicitWidth: 8\n            implicitHeight: 8\n            radius: Appearance.rounding.full\n            color: Appearance.colors.colTertiary\n\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/Media.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs\nimport qs.modules.common.functions\n\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell.Services.Mpris\nimport Quickshell.Hyprland\n\nItem {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    readonly property MprisPlayer activePlayer: MprisController.activePlayer\n    readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr(\"No media\")\n\n    Layout.fillHeight: true\n    implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2\n    implicitHeight: Appearance.sizes.barHeight\n\n    Timer {\n        running: activePlayer?.playbackState == MprisPlaybackState.Playing\n        interval: Config.options.resources.updateInterval\n        repeat: true\n        onTriggered: activePlayer.positionChanged()\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton\n        onPressed: (event) => {\n            if (event.button === Qt.MiddleButton) {\n                activePlayer.togglePlaying();\n            } else if (event.button === Qt.BackButton) {\n                activePlayer.previous();\n            } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) {\n                activePlayer.next();\n            } else if (event.button === Qt.LeftButton) {\n                GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen\n            }\n        }\n    }\n\n    RowLayout { // Real content\n        id: rowLayout\n\n        spacing: 4\n        anchors.fill: parent\n\n        ClippedFilledCircularProgress {\n            id: mediaCircProg\n            Layout.alignment: Qt.AlignVCenter\n            lineWidth: Appearance.rounding.unsharpen\n            value: activePlayer?.position / activePlayer?.length\n            implicitSize: 20\n            colPrimary: Appearance.colors.colOnSecondaryContainer\n            enableAnimation: false\n\n            Item {\n                anchors.centerIn: parent\n                width: mediaCircProg.implicitSize\n                height: mediaCircProg.implicitSize\n                \n                MaterialSymbol {\n                    anchors.centerIn: parent\n                    fill: 1\n                    text: activePlayer?.isPlaying ? \"pause\" : \"music_note\"\n                    iconSize: Appearance.font.pixelSize.normal\n                    color: Appearance.m3colors.m3onSecondaryContainer\n                }\n            }\n        }\n\n        StyledText {\n            visible: Config.options.bar.verbose\n            width: rowLayout.width - (CircularProgress.size + rowLayout.spacing * 2)\n            Layout.alignment: Qt.AlignVCenter\n            Layout.fillWidth: true // Ensures the text takes up available space\n            Layout.rightMargin: rowLayout.spacing\n            horizontalAlignment: Text.AlignHCenter\n            elide: Text.ElideRight // Truncates the text on the right\n            color: Appearance.colors.colOnLayer1\n            text: `${cleanedTitle}${activePlayer?.trackArtist ? ' • ' + activePlayer.trackArtist : ''}`\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/NotificationUnreadCount.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nMaterialSymbol {\n    id: root\n    readonly property bool showUnreadCount: Config.options.bar.indicators.notifications.showUnreadCount\n    text: Notifications.silent ? \"notifications_paused\" : \"notifications\"\n    iconSize: Appearance.font.pixelSize.larger\n    color: rightSidebarButton.colText\n\n    Rectangle {\n        id: notifPing\n        visible: !Notifications.silent && Notifications.unread > 0\n        anchors {\n            right: parent.right\n            top: parent.top\n            rightMargin: root.showUnreadCount ? 0 : 1\n            topMargin: root.showUnreadCount ? 0 : 3\n        }\n        radius: Appearance.rounding.full\n        color: Appearance.colors.colOnLayer0\n        z: 1\n\n        implicitHeight: root.showUnreadCount ? Math.max(notificationCounterText.implicitWidth, notificationCounterText.implicitHeight) : 8\n        implicitWidth: implicitHeight\n\n        StyledText {\n            id: notificationCounterText\n            visible: root.showUnreadCount\n            anchors.centerIn: parent\n            font.pixelSize: Appearance.font.pixelSize.smallest\n            color: Appearance.colors.colLayer0\n            text: Notifications.unread\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/Resource.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    required property string iconName\n    required property double percentage\n    property int warningThreshold: 100\n    property bool shown: true\n    clip: true\n    visible: width > 0 && height > 0\n    implicitWidth: resourceRowLayout.x < 0 ? 0 : resourceRowLayout.implicitWidth\n    implicitHeight: Appearance.sizes.barHeight\n    property bool warning: percentage * 100 >= warningThreshold\n\n    RowLayout {\n        id: resourceRowLayout\n        spacing: 2\n        x: shown ? 0 : -resourceRowLayout.width\n        anchors {\n            verticalCenter: parent.verticalCenter\n        }\n\n        ClippedFilledCircularProgress {\n            id: resourceCircProg\n            Layout.alignment: Qt.AlignVCenter\n            lineWidth: Appearance.rounding.unsharpen\n            value: percentage\n            implicitSize: 20\n            colPrimary: root.warning ? Appearance.colors.colError : Appearance.colors.colOnSecondaryContainer\n            accountForLightBleeding: !root.warning\n            enableAnimation: false\n\n            Item {\n                anchors.centerIn: parent\n                width: resourceCircProg.implicitSize\n                height: resourceCircProg.implicitSize\n                \n                MaterialSymbol {\n                    anchors.centerIn: parent\n                    font.weight: Font.DemiBold\n                    fill: 1\n                    text: iconName\n                    iconSize: Appearance.font.pixelSize.normal\n                    color: Appearance.m3colors.m3onSecondaryContainer\n                }\n            }\n        }\n\n        Item {\n            Layout.alignment: Qt.AlignVCenter\n            implicitWidth: fullPercentageTextMetrics.width\n            implicitHeight: percentageText.implicitHeight\n\n            TextMetrics {\n                id: fullPercentageTextMetrics\n                text: \"100\"\n                font.pixelSize: Appearance.font.pixelSize.small\n            }\n\n            StyledText {\n                id: percentageText\n                anchors.centerIn: parent\n                color: Appearance.colors.colOnLayer1\n                font.pixelSize: Appearance.font.pixelSize.small\n                text: `${Math.round(percentage * 100).toString()}`\n            }\n        }\n\n        Behavior on x {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n    }\n\n    MouseArea {\n        id: mouseArea\n        anchors.fill: parent\n        hoverEnabled: true\n        acceptedButtons: Qt.NoButton\n        enabled: resourceRowLayout.x >= 0 && root.width > 0 && root.visible\n    }\n\n    Behavior on implicitWidth {\n        NumberAnimation {\n            duration: Appearance.animation.elementMove.duration\n            easing.type: Appearance.animation.elementMove.type\n            easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/Resources.qml",
    "content": "import qs.modules.common\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nMouseArea {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    property bool alwaysShowAllResources: false\n    implicitWidth: rowLayout.implicitWidth + rowLayout.anchors.leftMargin + rowLayout.anchors.rightMargin\n    implicitHeight: Appearance.sizes.barHeight\n    hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n    RowLayout {\n        id: rowLayout\n\n        spacing: 0\n        anchors.fill: parent\n        anchors.leftMargin: 4\n        anchors.rightMargin: 4\n\n        Resource {\n            iconName: \"memory\"\n            percentage: ResourceUsage.memoryUsedPercentage\n            warningThreshold: Config.options.bar.resources.memoryWarningThreshold\n        }\n\n        Resource {\n            iconName: \"swap_horiz\"\n            percentage: ResourceUsage.swapUsedPercentage\n            shown: (Config.options.bar.resources.alwaysShowSwap && percentage > 0) || \n                (MprisController.activePlayer?.trackTitle == null) ||\n                root.alwaysShowAllResources\n            Layout.leftMargin: shown ? 6 : 0\n            warningThreshold: Config.options.bar.resources.swapWarningThreshold\n        }\n\n        Resource {\n            iconName: \"planner_review\"\n            percentage: ResourceUsage.cpuUsage\n            shown: Config.options.bar.resources.alwaysShowCpu || \n                !(MprisController.activePlayer?.trackTitle?.length > 0) ||\n                root.alwaysShowAllResources\n            Layout.leftMargin: shown ? 6 : 0\n            warningThreshold: Config.options.bar.resources.cpuWarningThreshold\n        }\n\n    }\n\n    ResourcesPopup {\n        hoverTarget: root\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/ResourcesPopup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nStyledPopup {\n    id: root\n\n    // Helper function to format KB to GB\n    function formatKB(kb) {\n        return (kb / (1024 * 1024)).toFixed(1) + \" GB\";\n    }\n\n    Row {\n        anchors.centerIn: parent\n        spacing: 12\n\n        Column {\n            anchors.top: parent.top\n            spacing: 8\n\n            StyledPopupHeaderRow {\n                icon: \"memory\"\n                label: \"RAM\"\n            }\n            Column {\n                spacing: 4\n                StyledPopupValueRow {\n                    icon: \"clock_loader_60\"\n                    label: Translation.tr(\"Used:\")\n                    value: root.formatKB(ResourceUsage.memoryUsed)\n                }\n                StyledPopupValueRow {\n                    icon: \"check_circle\"\n                    label: Translation.tr(\"Free:\")\n                    value: root.formatKB(ResourceUsage.memoryFree)\n                }\n                StyledPopupValueRow {\n                    icon: \"empty_dashboard\"\n                    label: Translation.tr(\"Total:\")\n                    value: root.formatKB(ResourceUsage.memoryTotal)\n                }\n            }\n        }\n\n        Column {\n            visible: ResourceUsage.swapTotal > 0\n            anchors.top: parent.top\n            spacing: 8\n\n            StyledPopupHeaderRow {\n                icon: \"swap_horiz\"\n                label: \"Swap\"\n            }\n            Column {\n                spacing: 4\n                StyledPopupValueRow {\n                    icon: \"clock_loader_60\"\n                    label: Translation.tr(\"Used:\")\n                    value: root.formatKB(ResourceUsage.swapUsed)\n                }\n                StyledPopupValueRow {\n                    icon: \"check_circle\"\n                    label: Translation.tr(\"Free:\")\n                    value: root.formatKB(ResourceUsage.swapFree)\n                }\n                StyledPopupValueRow {\n                    icon: \"empty_dashboard\"\n                    label: Translation.tr(\"Total:\")\n                    value: root.formatKB(ResourceUsage.swapTotal)\n                }\n            }\n        }\n\n        Column {\n            anchors.top: parent.top\n            spacing: 8\n\n            StyledPopupHeaderRow {\n                icon: \"planner_review\"\n                label: \"CPU\"\n            }\n            Column {\n                spacing: 4\n                StyledPopupValueRow {\n                    icon: \"bolt\"\n                    label: Translation.tr(\"Load:\")\n                    value: `${Math.round(ResourceUsage.cpuUsage * 100)}%`\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/ScrollHint.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nRevealer { // Scroll hint\n    id: root\n    property string icon\n    property string side: \"left\"\n    property string tooltipText: \"\"\n    \n    MouseArea {\n        id: mouseArea\n        anchors.right: root.side === \"left\" ? parent.right : undefined\n        anchors.left: root.side === \"right\" ? parent.left : undefined\n        implicitWidth: contentColumn.implicitWidth\n        implicitHeight: contentColumn.implicitHeight\n        property bool hovered: false\n\n        hoverEnabled: true\n        onEntered: hovered = true\n        onExited: hovered = false\n        acceptedButtons: Qt.NoButton\n\n        property bool showHintTimedOut: false\n        onHoveredChanged: showHintTimedOut = false\n        Timer {\n            running: mouseArea.hovered\n            interval: 500\n            onTriggered: mouseArea.showHintTimedOut = true\n        }\n\n        PopupToolTip {\n            extraVisibleCondition: (tooltipText.length > 0 && mouseArea.showHintTimedOut)\n            text: tooltipText\n        }\n\n        Column {\n            id: contentColumn\n            anchors {\n                fill: parent\n            }\n            spacing: -5\n            MaterialSymbol {\n                text: \"keyboard_arrow_up\"\n                iconSize: 14\n                color: Appearance.colors.colSubtext\n            }\n            MaterialSymbol {\n                text: root.icon\n                iconSize: 14\n                color: Appearance.colors.colSubtext\n            }\n            MaterialSymbol {\n                text: \"keyboard_arrow_down\"\n                iconSize: 14\n                color: Appearance.colors.colSubtext\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/StyledPopup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Effects\nimport Quickshell\nimport Quickshell.Wayland\n\nLazyLoader {\n    id: root\n\n    property Item hoverTarget\n    default property Item contentItem\n    property real popupBackgroundMargin: 0\n\n    active: hoverTarget && hoverTarget.containsMouse\n\n    component: PanelWindow {\n        id: popupWindow\n        color: \"transparent\"\n\n        anchors.left: !Config.options.bar.vertical || (Config.options.bar.vertical && !Config.options.bar.bottom)\n        anchors.right: Config.options.bar.vertical && Config.options.bar.bottom\n        anchors.top: Config.options.bar.vertical || (!Config.options.bar.vertical && !Config.options.bar.bottom)\n        anchors.bottom: !Config.options.bar.vertical && Config.options.bar.bottom\n\n        implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2 + root.popupBackgroundMargin\n        implicitHeight: popupBackground.implicitHeight + Appearance.sizes.elevationMargin * 2 + root.popupBackgroundMargin\n\n        mask: Region {\n            item: popupBackground\n        }\n\n        exclusionMode: ExclusionMode.Ignore\n        exclusiveZone: 0\n        margins {\n            left: {\n                if (!Config.options.bar.vertical) return root.QsWindow?.mapFromItem(\n                    root.hoverTarget, \n                    (root.hoverTarget.width - popupBackground.implicitWidth) / 2, 0\n                ).x;\n                return Appearance.sizes.verticalBarWidth\n            }\n            top: {\n                if (!Config.options.bar.vertical) return Appearance.sizes.barHeight;\n                return root.QsWindow?.mapFromItem(\n                    root.hoverTarget, \n                    (root.hoverTarget.height - popupBackground.implicitHeight) / 2, 0\n                ).y;\n            }\n            right: Appearance.sizes.verticalBarWidth\n            bottom: Appearance.sizes.barHeight\n        }\n        WlrLayershell.namespace: \"quickshell:popup\"\n        WlrLayershell.layer: WlrLayer.Overlay\n\n        StyledRectangularShadow {\n            target: popupBackground\n        }\n\n        Rectangle {\n            id: popupBackground\n            readonly property real margin: 10\n            anchors {\n                fill: parent\n                leftMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.left)\n                rightMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.right)\n                topMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.top)\n                bottomMargin: Appearance.sizes.elevationMargin + root.popupBackgroundMargin * (!popupWindow.anchors.bottom)\n            }\n            implicitWidth: root.contentItem.implicitWidth + margin * 2\n            implicitHeight: root.contentItem.implicitHeight + margin * 2\n            color: Appearance.m3colors.m3surfaceContainer\n            radius: Appearance.rounding.small\n            children: [root.contentItem]\n\n            border.width: 1\n            border.color: Appearance.colors.colLayer0Border\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/StyledPopupHeaderRow.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRow {\n    id: root\n    required property var icon\n    required property var label\n    spacing: 5\n\n    MaterialSymbol {\n        anchors.verticalCenter: parent.verticalCenter\n        fill: 0\n        font.weight: Font.DemiBold\n        text: root.icon\n        iconSize: Appearance.font.pixelSize.large\n        color: Appearance.colors.colOnSurfaceVariant\n    }\n\n    StyledText {\n        anchors.verticalCenter: parent.verticalCenter\n        text: root.label\n        font {\n            weight: Font.DemiBold\n            pixelSize: Appearance.font.pixelSize.normal\n        }\n        color: Appearance.colors.colOnSurfaceVariant\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/StyledPopupValueRow.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRowLayout {\n    id: root\n    required property string icon\n    required property string label\n    required property string value\n    spacing: 4\n\n    MaterialSymbol {\n        text: root.icon\n        color: Appearance.colors.colOnSurfaceVariant\n        iconSize: Appearance.font.pixelSize.large\n    }\n    StyledText {\n        text: root.label\n        color: Appearance.colors.colOnSurfaceVariant\n    }\n    StyledText {\n        Layout.fillWidth: true\n        horizontalAlignment: Text.AlignRight\n        visible: root.value !== \"\"\n        color: Appearance.colors.colOnSurfaceVariant\n        text: root.value\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/SysTray.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport Quickshell.Services.SystemTray\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nItem {\n    id: root\n    implicitWidth: gridLayout.implicitWidth\n    implicitHeight: gridLayout.implicitHeight\n    property bool vertical: false\n    property bool invertSide: false\n    property bool trayOverflowOpen: false\n    property bool showSeparator: true\n    property bool showOverflowMenu: true\n    property var activeMenu: null\n\n    property list<var> pinnedItems: TrayService.pinnedItems\n    property list<var> unpinnedItems: TrayService.unpinnedItems\n    onUnpinnedItemsChanged: {\n        if (unpinnedItems.length == 0) root.closeOverflowMenu();\n    }\n\n    function grabFocus() {\n        focusGrab.active = true;\n    }\n\n    function setExtraWindowAndGrabFocus(window) {\n        if (root.activeMenu && root.activeMenu !== window) {\n            if (typeof root.activeMenu.close === \"function\")\n                root.activeMenu.close();\n            root.activeMenu = null;\n        }\n        root.activeMenu = window;\n        root.grabFocus();\n    }\n\n    function releaseFocus() {\n        focusGrab.active = false;\n    }\n\n    function closeOverflowMenu() {\n        focusGrab.active = false;\n    }\n\n    onTrayOverflowOpenChanged: {\n        if (root.trayOverflowOpen) {\n            root.grabFocus();\n        }\n    }\n\n    HyprlandFocusGrab {\n        id: focusGrab\n        active: false\n        windows: [trayOverflowLayout.QsWindow?.window, root.activeMenu]\n        onCleared: {\n            root.trayOverflowOpen = false;\n            if (root.activeMenu) {\n                root.activeMenu.close();\n                root.activeMenu = null;\n            }\n        }\n    }\n\n    GridLayout {\n        id: gridLayout\n        columns: root.vertical ? 1 : -1\n        anchors.fill: parent\n        rowSpacing: 8\n        columnSpacing: 15\n\n        RippleButton {\n            id: trayOverflowButton\n            visible: root.showOverflowMenu && root.unpinnedItems.length > 0\n            toggled: root.trayOverflowOpen\n            property bool containsMouse: hovered\n\n            downAction: () => root.trayOverflowOpen = !root.trayOverflowOpen\n\n            Layout.fillHeight: !root.vertical\n            Layout.fillWidth: root.vertical\n            background.implicitWidth: 24\n            background.implicitHeight: 24\n            background.anchors.centerIn: this\n            colBackgroundToggled: Appearance.colors.colSecondaryContainer\n            colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n            colRippleToggled: Appearance.colors.colSecondaryContainerActive\n\n            contentItem: MaterialSymbol {\n                anchors.centerIn: parent\n                iconSize: Appearance.font.pixelSize.larger\n                text: \"expand_more\"\n                horizontalAlignment: Text.AlignHCenter\n                color: root.trayOverflowOpen ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer2\n                rotation: (root.trayOverflowOpen ? 180 : 0) - (90 * root.vertical) + (180 * root.invertSide)\n                Behavior on rotation {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n            }\n\n            StyledPopup {\n                id: overflowPopup\n                hoverTarget: trayOverflowButton\n                active: root.trayOverflowOpen && root.unpinnedItems.length > 0\n\n                GridLayout {\n                    id: trayOverflowLayout\n                    anchors.centerIn: parent\n                    columns: Math.ceil(Math.sqrt(root.unpinnedItems.length))\n                    columnSpacing: 10\n                    rowSpacing: 10\n\n                    Repeater {\n                        model: root.unpinnedItems\n\n                        delegate: SysTrayItem {\n                            required property SystemTrayItem modelData\n                            item: modelData\n                            Layout.fillHeight: !root.vertical\n                            Layout.fillWidth: root.vertical\n                            onMenuClosed: root.releaseFocus();\n                            onMenuOpened: (qsWindow) => root.setExtraWindowAndGrabFocus(qsWindow);\n                        }\n                    }\n                }\n            }\n        }\n\n        Repeater {\n            model: ScriptModel {\n                values: root.pinnedItems\n            }\n\n            delegate: SysTrayItem {\n                required property SystemTrayItem modelData\n                item: modelData\n                Layout.fillHeight: !root.vertical\n                Layout.fillWidth: root.vertical\n                onMenuClosed: root.releaseFocus();\n                onMenuOpened: (qsWindow) => {\n                    root.setExtraWindowAndGrabFocus(qsWindow);\n                }\n            }\n        }\n\n        StyledText {\n            Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter\n            font.pixelSize: Appearance.font.pixelSize.larger\n            color: Appearance.colors.colSubtext\n            text: \"•\"\n            visible: root.showSeparator && SystemTray.items.values.length > 0\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/SysTrayItem.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.SystemTray\nimport Quickshell.Widgets\nimport Qt5Compat.GraphicalEffects\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nMouseArea {\n    id: root\n    required property SystemTrayItem item\n    property bool targetMenuOpen: false\n\n    signal menuOpened(qsWindow: var)\n    signal menuClosed()\n\n    hoverEnabled: true\n    acceptedButtons: Qt.LeftButton | Qt.RightButton\n    implicitWidth: 20\n    implicitHeight: 20\n    onPressed: (event) => {\n        switch (event.button) {\n        case Qt.LeftButton:\n            item.activate();\n            break;\n        case Qt.RightButton:\n            if (item.hasMenu)\n                if (menu.active && menu.item && typeof menu.item.close === \"function\")\n                    menu.item.close();\n                else \n                    menu.open();\n            break;\n        }\n        event.accepted = true;\n    }\n    onEntered: {\n        tooltip.text = TrayService.getTooltipForItem(root.item);\n    }\n\n    Loader {\n        id: menu\n        function open() {\n            menu.active = true;\n        }\n        active: false\n        sourceComponent: SysTrayMenu {\n            Component.onCompleted: this.open();\n            trayItemMenuHandle: root.item.menu\n            trayItemId: root.item.id\n            anchor {\n                window: root.QsWindow.window\n                item: root\n                gravity: Config.options.bar.vertical\n                    ? (Config.options.bar.bottom ? Edges.Left : Edges.Right)\n                    : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom)\n                edges: Config.options.bar.vertical\n                    ? (Config.options.bar.bottom ? Edges.Left : Edges.Right)\n                    : (Config.options.bar.bottom ? Edges.Top : Edges.Bottom)\n            }\n            onMenuOpened: (window) => root.menuOpened(window);\n            onMenuClosed: {\n                root.menuClosed();\n                menu.active = false;\n            }\n        }\n    }\n\n    IconImage {\n        id: trayIcon\n        visible: !Config.options.tray.monochromeIcons\n        source: root.item.icon\n        anchors.centerIn: parent\n        width: parent.width\n        height: parent.height\n    }\n\n    Loader {\n        active: Config.options.tray.monochromeIcons\n        anchors.fill: trayIcon\n        sourceComponent: Item {\n            Desaturate {\n                id: desaturatedIcon\n                visible: false // There's already color overlay\n                anchors.fill: parent\n                source: trayIcon\n                desaturation: 0.8 // 1.0 means fully grayscale\n            }\n            ColorOverlay {\n                anchors.fill: desaturatedIcon\n                source: desaturatedIcon\n                color: ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.9)\n            }\n        }\n    }\n\n    PopupToolTip {\n        id: tooltip\n        extraVisibleCondition: root.containsMouse\n        alternativeVisibleCondition: extraVisibleCondition\n        anchorEdges: (!Config.options.bar.bottom && !Config.options.bar.vertical) ? Edges.Bottom : Edges.Top\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenu.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nPopupWindow {\n    id: root\n    required property QsMenuHandle trayItemMenuHandle\n    property string trayItemId: \"\"\n    property real popupBackgroundMargin: 0\n\n    signal menuClosed\n    signal menuOpened(qsWindow: var) // Correct type is QsWindow, but QML does not like that\n\n    color: \"transparent\"\n    property real padding: Appearance.sizes.elevationMargin\n\n    implicitHeight: {\n        let result = 0;\n        for (let child of stackView.children) {\n            result = Math.max(child.implicitHeight, result);\n        }\n        return result + popupBackground.padding * 2 + root.padding * 2;\n    }\n    implicitWidth: {\n        let result = 0;\n        for (let child of stackView.children) {\n            result = Math.max(child.implicitWidth, result);\n        }\n        return result + popupBackground.padding * 2 + root.padding * 2;\n    }\n\n    function open() {\n        root.visible = true;\n        root.menuOpened(root);\n    }\n\n    function close() {\n        root.visible = false;\n        while (stackView.depth > 1)\n            stackView.pop();\n        root.menuClosed();\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.BackButton | Qt.RightButton\n        onPressed: event => {\n            if ((event.button === Qt.BackButton || event.button === Qt.RightButton) && stackView.depth > 1)\n                stackView.pop();\n        }\n\n        StyledRectangularShadow {\n            target: popupBackground\n            opacity: popupBackground.opacity\n        }\n\n        Rectangle {\n            id: popupBackground\n            readonly property real padding: 4\n            anchors {\n                left: parent.left\n                right: parent.right\n                verticalCenter: Config.options.bar.vertical ? parent.verticalCenter : undefined\n                top: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? undefined : parent.top\n                bottom: Config.options.bar.vertical ? undefined : Config.options.bar.bottom ? parent.bottom : undefined\n                margins: root.padding\n            }\n\n            color: Appearance.colors.colLayer0\n            radius: Appearance.rounding.windowRounding\n            border.width: 1\n            border.color: Appearance.colors.colLayer0Border\n            clip: true\n\n            opacity: 0\n            Component.onCompleted: opacity = 1\n            implicitWidth: stackView.implicitWidth + popupBackground.padding * 2\n            implicitHeight: stackView.implicitHeight + popupBackground.padding * 2\n\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n            Behavior on implicitHeight {\n                animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n            }\n            Behavior on implicitWidth {\n                animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n            }\n\n            StackView {\n                id: stackView\n                anchors {\n                    fill: parent\n                    margins: popupBackground.padding\n                }\n                pushEnter: NoAnim {}\n                pushExit: NoAnim {}\n                popEnter: NoAnim {}\n                popExit: NoAnim {}\n\n                implicitWidth: currentItem.implicitWidth\n                implicitHeight: currentItem.implicitHeight\n\n                initialItem: SubMenu {\n                    handle: root.trayItemMenuHandle\n                }\n            }\n        }\n    }\n\n    component NoAnim: Transition {\n        NumberAnimation {\n            duration: 0\n        }\n    }\n\n    component SubMenu: ColumnLayout {\n        id: submenu\n        required property QsMenuHandle handle\n        property bool isSubMenu: false\n        property bool shown: false\n        opacity: shown ? 1 : 0\n\n        Behavior on opacity {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        Component.onCompleted: shown = true\n        StackView.onActivating: shown = true\n        StackView.onDeactivating: shown = false\n        StackView.onRemoved: destroy()\n\n        QsMenuOpener {\n            id: menuOpener\n            menu: submenu.handle\n        }\n\n        spacing: 0\n\n        Loader {\n            Layout.fillWidth: true\n            visible: submenu.isSubMenu\n            active: visible\n            sourceComponent: RippleButton {\n                id: backButton\n                buttonRadius: popupBackground.radius - popupBackground.padding\n                horizontalPadding: 12\n                implicitWidth: contentItem.implicitWidth + horizontalPadding * 2\n                implicitHeight: 36\n\n                downAction: () => stackView.pop()\n\n                contentItem: RowLayout {\n                    anchors {\n                        verticalCenter: parent.verticalCenter\n                        left: parent.left\n                        right: parent.right\n                        leftMargin: backButton.horizontalPadding\n                        rightMargin: backButton.horizontalPadding\n                    }\n                    spacing: 8\n                    MaterialSymbol {\n                        iconSize: 20\n                        text: \"chevron_left\"\n                    }\n                    StyledText {\n                        Layout.fillWidth: true\n                        text: Translation.tr(\"Back\")\n                    }\n                }\n            }\n        }\n        RippleButton {\n            id: pinEntry\n            buttonRadius: popupBackground.radius - popupBackground.padding\n            horizontalPadding: 12\n            implicitWidth: contentItem.implicitWidth + horizontalPadding * 2\n            implicitHeight: 36\n            Layout.topMargin: 0\n            Layout.bottomMargin: 0\n            Layout.fillWidth: true\n\n            visible: root.trayItemId !== undefined && root.trayItemId.length > 0 && stackView.depth === 1\n            releaseAction: () => TrayService.togglePin(root.trayItemId);\n\n            contentItem: RowLayout {\n                anchors {\n                    verticalCenter: parent.verticalCenter\n                    left: parent.left\n                    right: parent.right\n                    leftMargin: pinEntry.horizontalPadding\n                    rightMargin: pinEntry.horizontalPadding\n                }\n                spacing: 8\n\n                MaterialSymbol {\n                    iconSize: 18\n                    text: \"push_pin\"\n                }\n\n                StyledText {\n                    Layout.fillWidth: true\n                    text: TrayService.isPinned(root.trayItemId) ? Translation.tr(\"Unpin\") : Translation.tr(\"Pin\")\n                }\n            }\n        }\n\n        Rectangle {\n            Layout.fillWidth: true\n            implicitHeight: 1\n            color: Appearance.colors.colSubtext\n            Layout.topMargin: 4\n            Layout.bottomMargin: 4\n        }\n\n        Repeater {\n            id: menuEntriesRepeater\n            property bool iconColumnNeeded: {\n                for (let i = 0; i < menuOpener.children.values.length; i++) {\n                    if (menuOpener.children.values[i].icon.length > 0)\n                        return true;\n                }\n                return false;\n            }\n            property bool specialInteractionColumnNeeded: {\n                for (let i = 0; i < menuOpener.children.values.length; i++) {\n                    if (menuOpener.children.values[i].buttonType !== QsMenuButtonType.None)\n                        return true;\n                }\n                return false;\n            }\n            model: menuOpener.children\n            delegate: SysTrayMenuEntry {\n                required property QsMenuEntry modelData\n                forceIconColumn: menuEntriesRepeater.iconColumnNeeded\n                forceSpecialInteractionColumn: menuEntriesRepeater.specialInteractionColumnNeeded\n                menuEntry: modelData\n\n                buttonRadius: popupBackground.radius - popupBackground.padding\n\n                onDismiss: root.close()\n                onOpenSubmenu: handle => {\n                    stackView.push(subMenuComponent.createObject(null, {\n                        handle: handle,\n                        isSubMenu: true\n                    }));\n                }\n            }\n        }\n    }\n\n    Component {\n        id: subMenuComponent\n        SubMenu {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/SysTrayMenuEntry.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\n\nRippleButton {\n    id: root\n    required property QsMenuEntry menuEntry\n    property bool forceIconColumn: false\n    property bool forceSpecialInteractionColumn: false\n    readonly property bool hasIcon: menuEntry.icon.length > 0\n    readonly property bool hasSpecialInteraction: menuEntry.buttonType !== QsMenuButtonType.None\n\n    signal dismiss()\n    signal openSubmenu(handle: QsMenuHandle)\n\n    colBackground: menuEntry.isSeparator ? Appearance.m3colors.m3outlineVariant : ColorUtils.transparentize(Appearance.colors.colLayer0)\n    enabled: !menuEntry.isSeparator\n    opacity: 1\n\n    horizontalPadding: 12\n    implicitWidth: contentItem.implicitWidth + horizontalPadding * 2\n    implicitHeight: menuEntry.isSeparator ? 1 : 36\n    Layout.topMargin: menuEntry.isSeparator ? 4 : 0\n    Layout.bottomMargin: menuEntry.isSeparator ? 4 : 0\n    Layout.fillWidth: true\n\n    Component.onCompleted: {\n        if (menuEntry.isSeparator) {\n            root.buttonColor = root.colBackground;\n        }\n    }\n\n    releaseAction: () => { \n        if (menuEntry.hasChildren) {\n            root.openSubmenu(root.menuEntry);\n            return;\n        }\n        menuEntry.triggered();\n        root.dismiss(); \n    }\n    altAction: (event) => { // Not hog right-click\n        event.accepted = false;\n    }\n\n    contentItem: RowLayout {\n        id: contentItem\n        anchors {\n            verticalCenter: parent.verticalCenter\n            left: parent.left\n            right: parent.right\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n        }\n        spacing: 8\n        visible: !root.menuEntry.isSeparator\n\n        // Interaction: checkbox or radio button\n        Item {\n            visible: root.hasSpecialInteraction || root.forceSpecialInteractionColumn\n            implicitWidth: 20\n            implicitHeight: 20\n\n            Loader {\n                anchors.fill: parent\n                active: root.menuEntry.buttonType === QsMenuButtonType.RadioButton\n\n                sourceComponent: StyledRadioButton {\n                    enabled: false\n                    padding: 0\n                    checked: root.menuEntry.checkState === Qt.Checked\n                }\n            }\n\n            Loader {\n                anchors.fill: parent\n                active: root.menuEntry.buttonType === QsMenuButtonType.CheckBox && root.menuEntry.checkState !== Qt.Unchecked\n\n                sourceComponent: MaterialSymbol {\n                    text: root.menuEntry.checkState === Qt.PartiallyChecked ? \"check_indeterminate_small\" : \"check\"\n                    iconSize: 20\n                }\n            }\n        }\n\n        // Button icon\n        Item {\n            visible: root.hasIcon || root.forceIconColumn\n            implicitWidth: 20\n            implicitHeight: 20\n\n            Loader {\n                anchors.centerIn: parent\n                active: root.menuEntry.icon.length > 0\n                sourceComponent: IconImage {\n                    asynchronous: true\n                    source: root.menuEntry.icon\n                    implicitSize: 20\n                    mipmap: true\n                }\n            }\n        }\n\n        StyledText {\n            id: label\n            text: root.menuEntry.text\n            font.pixelSize: Appearance.font.pixelSize.smallie\n            Layout.fillWidth: true\n        }\n\n        Loader {\n            active: root.menuEntry.hasChildren\n\n            sourceComponent: MaterialSymbol {\n                text: \"chevron_right\"\n                iconSize: 20\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/UtilButtons.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport Quickshell.Services.Pipewire\nimport Quickshell.Services.UPower\n\nItem {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    implicitWidth: rowLayout.implicitWidth + rowLayout.spacing * 2\n    implicitHeight: rowLayout.implicitHeight\n\n    RowLayout {\n        id: rowLayout\n\n        spacing: 4\n        anchors.centerIn: parent\n\n        Loader {\n            active: Config.options.bar.utilButtons.showScreenSnip\n            visible: Config.options.bar.utilButtons.showScreenSnip\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"region\", \"screenshot\"]);\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 1\n                    text: \"screenshot_region\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n\n        Loader {\n            active: Config.options.bar.utilButtons.showScreenRecord\n            visible: Config.options.bar.utilButtons.showScreenRecord\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: Quickshell.execDetached([Directories.recordScriptPath])\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 1\n                    text: \"videocam\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n\n        Loader {\n            active: Config.options.bar.utilButtons.showColorPicker\n            visible: Config.options.bar.utilButtons.showColorPicker\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: Quickshell.execDetached([\"hyprpicker\", \"-a\"])\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 1\n                    text: \"colorize\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n\n        Loader {\n            active: Config.options.bar.utilButtons.showKeyboardToggle\n            visible: Config.options.bar.utilButtons.showKeyboardToggle\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: GlobalStates.oskOpen = !GlobalStates.oskOpen\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 0\n                    text: \"keyboard\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n\n        Loader {\n            active: Config.options.bar.utilButtons.showMicToggle\n            visible: Config.options.bar.utilButtons.showMicToggle\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: Quickshell.execDetached([\"wpctl\", \"set-mute\", \"@DEFAULT_SOURCE@\", \"toggle\"])\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 0\n                    text: Pipewire.defaultAudioSource?.audio?.muted ? \"mic_off\" : \"mic\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n\n        Loader {\n            active: Config.options.bar.utilButtons.showDarkModeToggle\n            visible: Config.options.bar.utilButtons.showDarkModeToggle\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: event => {\n                    if (Appearance.m3colors.darkmode) {\n                        Quickshell.execDetached([\"bash\", \"-c\", `${Directories.wallpaperSwitchScriptPath} --mode light --noswitch`])\n                    } else {\n                        Quickshell.execDetached([\"bash\", \"-c\", `${Directories.wallpaperSwitchScriptPath} --mode dark --noswitch`])\n                    }\n                }\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 0\n                    text: Appearance.m3colors.darkmode ? \"light_mode\" : \"dark_mode\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n\n        Loader {\n            active: Config.options.bar.utilButtons.showPerformanceProfileToggle\n            visible: Config.options.bar.utilButtons.showPerformanceProfileToggle\n            sourceComponent: CircleUtilButton {\n                Layout.alignment: Qt.AlignVCenter\n                onClicked: event => {\n                    if (PowerProfiles.hasPerformanceProfile) {\n                        switch(PowerProfiles.profile) {\n                            case PowerProfile.PowerSaver: PowerProfiles.profile = PowerProfile.Balanced\n                            break;\n                            case PowerProfile.Balanced: PowerProfiles.profile = PowerProfile.Performance\n                            break;\n                            case PowerProfile.Performance: PowerProfiles.profile = PowerProfile.PowerSaver\n                            break;\n                        }\n                    } else {\n                        PowerProfiles.profile = PowerProfiles.profile == PowerProfile.Balanced ? PowerProfile.PowerSaver : PowerProfile.Balanced\n                    }\n                }\n                MaterialSymbol {\n                    horizontalAlignment: Qt.AlignHCenter\n                    fill: 0\n                    text: switch(PowerProfiles.profile) {\n                        case PowerProfile.PowerSaver: return \"energy_savings_leaf\"\n                        case PowerProfile.Balanced: return \"airwave\"\n                        case PowerProfile.Performance: return \"local_fire_department\"\n                    }\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer2\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/Workspaces.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport Quickshell.Widgets\nimport Qt5Compat.GraphicalEffects\n\nItem {\n    id: root\n    property bool vertical: false\n    property bool borderless: Config.options.bar.borderless\n    readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.QsWindow.window?.screen)\n    readonly property Toplevel activeWindow: ToplevelManager.activeToplevel\n    readonly property int effectiveActiveWorkspaceId: monitor?.activeWorkspace?.id ?? 1\n    \n    readonly property int workspacesShown: Config.options.bar.workspaces.shown\n    readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / root.workspacesShown)\n    property list<bool> workspaceOccupied: []\n    property int widgetPadding: 4\n    property int workspaceButtonWidth: 26\n    property real activeWorkspaceMargin: 2\n    property real workspaceIconSize: workspaceButtonWidth * 0.69\n    property real workspaceIconSizeShrinked: workspaceButtonWidth * 0.55\n    property real workspaceIconOpacityShrinked: 1\n    property real workspaceIconMarginShrinked: -4\n    property int workspaceIndexInGroup: (effectiveActiveWorkspaceId - 1) % root.workspacesShown\n\n    property bool showNumbers: false\n    Timer {\n        id: showNumbersTimer\n        interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)\n        repeat: false\n        onTriggered: {\n            root.showNumbers = true\n        }\n    }\n    Connections {\n        target: GlobalStates\n        function onSuperDownChanged() {\n            if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return;\n            if (GlobalStates.superDown) showNumbersTimer.restart();\n            else {\n                showNumbersTimer.stop();\n                root.showNumbers = false;\n            }\n        }\n        function onSuperReleaseMightTriggerChanged() { \n            showNumbersTimer.stop()\n        }\n    }\n\n    // Function to update workspaceOccupied\n    function updateWorkspaceOccupied() {\n        workspaceOccupied = Array.from({ length: root.workspacesShown }, (_, i) => {\n            return Hyprland.workspaces.values.some(ws => ws.id === workspaceGroup * root.workspacesShown + i + 1);\n        })\n    }\n\n    // Occupied workspace updates\n    Component.onCompleted: updateWorkspaceOccupied()\n    Connections {\n        target: Hyprland.workspaces\n        function onValuesChanged() {\n            updateWorkspaceOccupied();\n        }\n    }\n    Connections {\n        target: Hyprland\n        function onFocusedWorkspaceChanged() {\n            updateWorkspaceOccupied();\n        }\n    }\n    onWorkspaceGroupChanged: {\n        updateWorkspaceOccupied();\n    }\n\n    implicitWidth: root.vertical ? Appearance.sizes.verticalBarWidth : (root.workspaceButtonWidth * root.workspacesShown)\n    implicitHeight: root.vertical ? (root.workspaceButtonWidth * root.workspacesShown) : Appearance.sizes.barHeight\n\n    // Scroll to switch workspaces\n    WheelHandler {\n        onWheel: (event) => {\n            if (event.angleDelta.y < 0)\n                Hyprland.dispatch(`hl.dsp.focus({workspace = \"r+1\"})`);\n            else if (event.angleDelta.y > 0)\n                Hyprland.dispatch(`hl.dsp.focus({workspace = \"r-1\"})`);\n        }\n        acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.BackButton\n        onPressed: (event) => {\n            if (event.button === Qt.BackButton) {\n                Hyprland.dispatch(`hl.dsp.workspace.toggle_special(\"special\")`);\n            } \n        }\n    }\n\n    // Workspaces - background\n    Grid {\n        z: 1\n        anchors.centerIn: parent\n\n        rowSpacing: 0\n        columnSpacing: 0\n        columns: root.vertical ? 1 : root.workspacesShown\n        rows: root.vertical ? root.workspacesShown : 1\n\n        Repeater {\n            model: root.workspacesShown\n\n            Rectangle {\n                z: 1\n                implicitWidth: workspaceButtonWidth\n                implicitHeight: workspaceButtonWidth\n                radius: (width / 2)\n                property var previousOccupied: (workspaceOccupied[index-1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index))\n                property var rightOccupied: (workspaceOccupied[index+1] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+2))\n                property var radiusPrev: previousOccupied ? 0 : (width / 2)\n                property var radiusNext: rightOccupied ? 0 : (width / 2)\n\n                topLeftRadius: radiusPrev\n                bottomLeftRadius: root.vertical ? radiusNext : radiusPrev\n                topRightRadius: root.vertical ? radiusPrev : radiusNext\n                bottomRightRadius: radiusNext\n                \n                color: ColorUtils.transparentize(Appearance.m3colors.m3secondaryContainer, 0.4)\n                opacity: (workspaceOccupied[index] && !(!activeWindow?.activated && root.effectiveActiveWorkspaceId === index+1)) ? 1 : 0\n\n                Behavior on opacity {\n                    animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n                }\n                Behavior on radiusPrev {\n                    animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n                }\n\n                Behavior on radiusNext {\n                    animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n                }\n\n            }\n\n        }\n\n    }\n\n    // Active workspace\n    Rectangle {\n        z: 2\n        // Make active ws indicator, which has a brighter color, smaller to look like it is of the same size as ws occupied highlight\n        radius: Appearance.rounding.full\n        color: Appearance.colors.colPrimary\n\n        anchors {\n            verticalCenter: vertical ? undefined : parent.verticalCenter\n            horizontalCenter: vertical ? parent.horizontalCenter : undefined\n        }\n\n        AnimatedTabIndexPair {\n            id: idxPair\n            index: root.workspaceIndexInGroup\n        }\n        property real indicatorPosition: Math.min(idxPair.idx1, idxPair.idx2) * workspaceButtonWidth + root.activeWorkspaceMargin\n        property real indicatorLength: Math.abs(idxPair.idx1 - idxPair.idx2) * workspaceButtonWidth + workspaceButtonWidth - root.activeWorkspaceMargin * 2\n        property real indicatorThickness: workspaceButtonWidth - root.activeWorkspaceMargin * 2\n\n        x: root.vertical ? null : indicatorPosition\n        implicitWidth: root.vertical ? indicatorThickness : indicatorLength\n        y: root.vertical ? indicatorPosition : null\n        implicitHeight: root.vertical ? indicatorLength : indicatorThickness\n\n    }\n\n    // Workspaces - numbers\n    Grid {\n        z: 3\n\n        columns: root.vertical ? 1 : root.workspacesShown\n        rows: root.vertical ? root.workspacesShown : 1\n        columnSpacing: 0\n        rowSpacing: 0\n\n        anchors.fill: parent\n\n        Repeater {\n            model: root.workspacesShown\n\n            Button {\n                id: button\n                property int workspaceValue: workspaceGroup * root.workspacesShown + index + 1\n                implicitHeight: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.barHeight\n                implicitWidth: vertical ? Appearance.sizes.verticalBarWidth : Appearance.sizes.verticalBarWidth\n                onPressed: Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspaceValue}})`)\n                width: vertical ? undefined : root.workspaceButtonWidth\n                height: vertical ? root.workspaceButtonWidth : undefined\n\n                background: Item {\n                    id: workspaceButtonBackground\n                    implicitWidth: workspaceButtonWidth\n                    implicitHeight: workspaceButtonWidth\n                    property var biggestWindow: HyprlandData.biggestWindowForWorkspace(button.workspaceValue)\n                    property var mainAppIconSource: Quickshell.iconPath(AppSearch.guessIcon(biggestWindow?.class), \"image-missing\")\n\n                    StyledText { // Workspace number text\n                        opacity: root.showNumbers\n                            || ((Config.options?.bar.workspaces.alwaysShowNumbers && (!Config.options?.bar.workspaces.showAppIcons || !workspaceButtonBackground.biggestWindow || root.showNumbers))\n                            || (root.showNumbers && !Config.options?.bar.workspaces.showAppIcons)\n                            )  ? 1 : 0\n                        z: 3\n\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        verticalAlignment: Text.AlignVCenter\n                        font {\n                            pixelSize: Appearance.font.pixelSize.small - ((text.length - 1) * (text !== \"10\") * 2)\n                            family: Config.options?.bar.workspaces.useNerdFont ? Appearance.font.family.iconNerd : defaultFont\n                        }\n                        text: Config.options?.bar.workspaces.numberMap[button.workspaceValue - 1] || button.workspaceValue\n                        elide: Text.ElideRight\n                        color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ? \n                            Appearance.m3colors.m3onPrimary : \n                            (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : \n                                Appearance.colors.colOnLayer1Inactive)\n\n                        Behavior on opacity {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                    }\n                    Rectangle { // Dot instead of ws number\n                        id: wsDot\n                        opacity: (Config.options?.bar.workspaces.alwaysShowNumbers\n                            || root.showNumbers\n                            || (Config.options?.bar.workspaces.showAppIcons && workspaceButtonBackground.biggestWindow)\n                            ) ? 0 : 1\n                        visible: opacity > 0\n                        anchors.centerIn: parent\n                        width: workspaceButtonWidth * 0.18\n                        height: width\n                        radius: width / 2\n                        color: (root.effectiveActiveWorkspaceId == button.workspaceValue) ? \n                            Appearance.m3colors.m3onPrimary : \n                            (workspaceOccupied[index] ? Appearance.m3colors.m3onSecondaryContainer : \n                                Appearance.colors.colOnLayer1Inactive)\n\n                        Behavior on opacity {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                    }\n                    Item { // Main app icon\n                        anchors.centerIn: parent\n                        width: workspaceButtonWidth\n                        height: workspaceButtonWidth\n                        opacity: !Config.options?.bar.workspaces.showAppIcons ? 0 :\n                            (workspaceButtonBackground.biggestWindow && !root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? \n                            1 : workspaceButtonBackground.biggestWindow ? workspaceIconOpacityShrinked : 0\n                            visible: opacity > 0\n                        IconImage {\n                            id: mainAppIcon\n                            anchors.bottom: parent.bottom\n                            anchors.right: parent.right\n                            anchors.bottomMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? \n                                (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked\n                            anchors.rightMargin: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? \n                                (workspaceButtonWidth - workspaceIconSize) / 2 : workspaceIconMarginShrinked\n\n                            source: workspaceButtonBackground.mainAppIconSource\n                            implicitSize: (!root.showNumbers && Config.options?.bar.workspaces.showAppIcons) ? workspaceIconSize : workspaceIconSizeShrinked\n\n                            Behavior on opacity {\n                                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                            }\n                            Behavior on anchors.bottomMargin {\n                                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                            }\n                            Behavior on anchors.rightMargin {\n                                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                            }\n                            Behavior on implicitSize {\n                                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                            }\n                        }\n\n                        Loader {\n                            active: Config.options.bar.workspaces.monochromeIcons\n                            anchors.fill: mainAppIcon\n                            sourceComponent: Item {\n                                Desaturate {\n                                    id: desaturatedIcon\n                                    visible: false // There's already color overlay\n                                    anchors.fill: parent\n                                    source: mainAppIcon\n                                    desaturation: 0.8\n                                }\n                                ColorOverlay {\n                                    anchors.fill: desaturatedIcon\n                                    source: desaturatedIcon\n                                    color: ColorUtils.transparentize(wsDot.color, 0.9)\n                                }\n                            }\n                        }\n                    }\n                }\n                \n\n            }\n\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/weather/WeatherBar.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport Quickshell\nimport QtQuick\nimport QtQuick.Layouts\n\nMouseArea {\n    id: root\n    property bool hovered: false\n    implicitWidth: rowLayout.implicitWidth + 10 * 2\n    implicitHeight: Appearance.sizes.barHeight\n\n    acceptedButtons: Qt.LeftButton | Qt.RightButton\n    hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n    onPressed: {\n        if (mouse.button === Qt.RightButton) {\n            Weather.getData();\n            Quickshell.execDetached([\"notify-send\", \n                Translation.tr(\"Weather\"), \n                Translation.tr(\"Refreshing (manually triggered)\")\n                , \"-a\", \"Shell\"\n            ])\n            mouse.accepted = false\n        }\n    }\n\n    RowLayout {\n        id: rowLayout\n        anchors.centerIn: parent\n\n        MaterialSymbol {\n            fill: 0\n            text: Icons.getWeatherIcon(Weather.data.wCode) ?? \"cloud\"\n            iconSize: Appearance.font.pixelSize.large\n            color: Appearance.colors.colOnLayer1\n            Layout.alignment: Qt.AlignVCenter\n        }\n\n        StyledText {\n            visible: true\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnLayer1\n            text: Weather.data?.temp ?? \"--°\"\n            Layout.alignment: Qt.AlignVCenter\n        }\n    }\n\n    WeatherPopup {\n        id: weatherPopup\n        hoverTarget: root\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/weather/WeatherCard.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\n\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRectangle {\n    id: root\n    radius: Appearance.rounding.small\n    color: Appearance.colors.colSurfaceContainerHigh\n    implicitWidth: columnLayout.implicitWidth + 14 * 2\n    implicitHeight: columnLayout.implicitHeight + 14 * 2\n    Layout.fillWidth: parent\n\n    property alias title: title.text\n    property alias value: value.text\n    property alias symbol: symbol.text\n\n    ColumnLayout {\n        id: columnLayout\n        anchors.fill: parent\n        spacing: -10\n        RowLayout {\n            Layout.alignment: Qt.AlignHCenter\n            MaterialSymbol {\n                id: symbol\n                fill: 0\n                iconSize: Appearance.font.pixelSize.normal\n                color: Appearance.colors.colOnSurfaceVariant\n            }\n            StyledText {\n                id: title\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                color: Appearance.colors.colOnSurfaceVariant\n            }\n        }\n        StyledText {\n            id: value\n            Layout.alignment: Qt.AlignHCenter\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnSurfaceVariant\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/bar/weather/WeatherPopup.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nimport QtQuick\nimport QtQuick.Layouts\nimport qs.modules.ii.bar\n\nStyledPopup {\n    id: root\n\n    ColumnLayout {\n        id: columnLayout\n        anchors.centerIn: parent\n        implicitWidth: Math.max(header.implicitWidth, gridLayout.implicitWidth)\n        implicitHeight: gridLayout.implicitHeight\n        spacing: 5\n\n        // Header\n        ColumnLayout {\n            id: header\n            Layout.alignment: Qt.AlignHCenter\n            spacing: 2\n\n            RowLayout {\n                Layout.alignment: Qt.AlignHCenter\n                spacing: 6\n\n                MaterialSymbol {\n                    fill: 0\n                    font.weight: Font.Medium\n                    text: \"location_on\"\n                    iconSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnSurfaceVariant\n                }\n\n                StyledText {\n                    text: Weather.data.city\n                    font {\n                        weight: Font.Medium\n                        pixelSize: Appearance.font.pixelSize.normal\n                    }\n                    color: Appearance.colors.colOnSurfaceVariant\n                }\n            }\n            StyledText {\n                id: temp\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                color: Appearance.colors.colOnSurfaceVariant\n                text: Weather.data.temp + \" • \" + Translation.tr(\"Feels like %1\").arg(Weather.data.tempFeelsLike)\n            }\n        }\n\n        // Metrics grid\n        GridLayout {\n            id: gridLayout\n            columns: 2\n            rowSpacing: 5\n            columnSpacing: 5\n            uniformCellWidths: true\n\n            WeatherCard {\n                title: Translation.tr(\"UV Index\")\n                symbol: \"wb_sunny\"\n                value: Weather.data.uv\n            }\n            WeatherCard {\n                title: Translation.tr(\"Wind\")\n                symbol: \"air\"\n                value: `(${Weather.data.windDir}) ${Weather.data.wind}`\n            }\n            WeatherCard {\n                title: Translation.tr(\"Precipitation\")\n                symbol: \"rainy_light\"\n                value: Weather.data.precip\n            }\n            WeatherCard {\n                title: Translation.tr(\"Humidity\")\n                symbol: \"humidity_low\"\n                value: Weather.data.humidity\n            }\n            WeatherCard {\n                title: Translation.tr(\"Visibility\")\n                symbol: \"visibility\"\n                value: Weather.data.visib\n            }\n            WeatherCard {\n                title: Translation.tr(\"Pressure\")\n                symbol: \"readiness_score\"\n                value: Weather.data.press\n            }\n            WeatherCard {\n                title: Translation.tr(\"Sunrise\")\n                symbol: \"wb_twilight\"\n                value: Weather.data.sunrise\n            }\n            WeatherCard {\n                title: Translation.tr(\"Sunset\")\n                symbol: \"bedtime\"\n                value: Weather.data.sunset\n            }\n        }\n\n        // Footer: last refresh\n        StyledText {\n            Layout.alignment: Qt.AlignHCenter\n            text: Translation.tr(\"Last refresh: %1\").arg(Weather.data.lastRefresh)\n            font {\n                weight: Font.Medium\n                pixelSize: Appearance.font.pixelSize.smaller\n            }\n            color: Appearance.colors.colOnSurfaceVariant\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/cheatsheet/Cheatsheet.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt.labs.synchronizer\nimport Qt5Compat.GraphicalEffects\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope { // Scope\n    id: root\n    property var tabButtonList: [\n        {\n            \"icon\": \"keyboard\",\n            \"name\": Translation.tr(\"Keybinds\")\n        },\n        {\n            \"icon\": \"experiment\",\n            \"name\": Translation.tr(\"Elements\")\n        },\n    ]\n\n    Loader {\n        id: cheatsheetLoader\n        active: false\n\n        sourceComponent: PanelWindow { // Window\n            id: cheatsheetRoot\n            visible: cheatsheetLoader.active\n\n            anchors {\n                top: true\n                bottom: true\n                left: true\n                right: true\n            }\n\n            function hide() {\n                cheatsheetLoader.active = false;\n            }\n            exclusiveZone: 0\n            implicitWidth: cheatsheetBackground.width + Appearance.sizes.elevationMargin * 2\n            implicitHeight: cheatsheetBackground.height + Appearance.sizes.elevationMargin * 2\n            WlrLayershell.namespace: \"quickshell:cheatsheet\"\n            // Setting this value makes it take its sweet time to open\n            // WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n            color: \"transparent\"\n\n            mask: Region {\n                item: cheatsheetBackground\n            }\n\n            Component.onCompleted: {\n                GlobalFocusGrab.addDismissable(cheatsheetRoot);\n            }\n            Component.onDestruction: {\n                GlobalFocusGrab.removeDismissable(cheatsheetRoot);\n            }\n            Connections {\n                target: GlobalFocusGrab\n                function onDismissed() {\n                    cheatsheetRoot.hide();\n                }\n            }\n\n            // Background\n            StyledRectangularShadow {\n                target: cheatsheetBackground\n            }\n            Rectangle {\n                id: cheatsheetBackground\n                anchors.centerIn: parent\n                color: Appearance.colors.colLayer0\n                border.width: 1\n                border.color: Appearance.colors.colLayer0Border\n                radius: Appearance.rounding.windowRounding\n                property real padding: 20\n                implicitWidth: cheatsheetColumnLayout.implicitWidth + padding * 2\n                implicitHeight: cheatsheetColumnLayout.implicitHeight + padding * 2\n\n                Keys.onPressed: event => { // Esc to close\n                    if (event.key === Qt.Key_Escape) {\n                        cheatsheetRoot.hide();\n                    }\n                    if (event.modifiers === Qt.ControlModifier) {\n                        if (event.key === Qt.Key_PageDown) {\n                            tabBar.incrementCurrentIndex();\n                            event.accepted = true;\n                        } else if (event.key === Qt.Key_PageUp) {\n                            tabBar.decrementCurrentIndex();\n                            event.accepted = true;\n                        } else if (event.key === Qt.Key_Tab) {\n                            tabBar.setCurrentIndex((tabBar.currentIndex + 1) % root.tabButtonList.length);\n                            event.accepted = true;\n                        } else if (event.key === Qt.Key_Backtab) {\n                            tabBar.setCurrentIndex((tabBar.currentIndex - 1 + root.tabButtonList.length) % root.tabButtonList.length);\n                            event.accepted = true;\n                        }\n                    }\n                }\n\n                RippleButton { // Close button\n                    id: closeButton\n                    focus: cheatsheetRoot.visible\n                    implicitWidth: 40\n                    implicitHeight: 40\n                    buttonRadius: Appearance.rounding.full\n                    anchors {\n                        top: parent.top\n                        right: parent.right\n                        topMargin: 20\n                        rightMargin: 20\n                    }\n\n                    onClicked: {\n                        cheatsheetRoot.hide();\n                    }\n\n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        font.pixelSize: Appearance.font.pixelSize.title\n                        text: \"close\"\n                    }\n                }\n\n                ColumnLayout { // Real content\n                    id: cheatsheetColumnLayout\n                    anchors.centerIn: parent\n                    spacing: 10\n\n                    Toolbar {\n                        Layout.alignment: Qt.AlignHCenter\n                        enableShadow: false\n                        ToolbarTabBar {\n                            id: tabBar\n                            tabButtonList: root.tabButtonList\n\n                            Synchronizer on currentIndex {\n                                property alias source: swipeView.currentIndex\n                            }\n                        }\n                    }\n\n                    SwipeView { // Content pages\n                        id: swipeView\n                        Layout.topMargin: 5\n                        Layout.fillWidth: true\n                        Layout.fillHeight: true\n                        spacing: 10\n                        currentIndex: Persistent.states.cheatsheet.tabIndex\n                        onCurrentIndexChanged: {\n                            Persistent.states.cheatsheet.tabIndex = currentIndex;\n                        }\n\n                        implicitWidth: Math.max.apply(null, contentChildren.map(child => child.implicitWidth || 0))\n                        implicitHeight: Math.max.apply(null, contentChildren.map(child => child.implicitHeight || 0))\n\n                        clip: true\n                        layer.enabled: true\n                        layer.effect: OpacityMask {\n                            maskSource: Rectangle {\n                                width: swipeView.width\n                                height: swipeView.height\n                                radius: Appearance.rounding.small\n                            }\n                        }\n\n                        CheatsheetKeybinds {}\n                        CheatsheetPeriodicTable {}\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"cheatsheet\"\n\n        function toggle(): void {\n            cheatsheetLoader.active = !cheatsheetLoader.active;\n        }\n\n        function close(): void {\n            cheatsheetLoader.active = false;\n        }\n\n        function open(): void {\n            cheatsheetLoader.active = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"cheatsheetToggle\"\n        description: \"Toggles cheatsheet on press\"\n\n        onPressed: {\n            cheatsheetLoader.active = !cheatsheetLoader.active;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"cheatsheetOpen\"\n        description: \"Opens cheatsheet on press\"\n\n        onPressed: {\n            cheatsheetLoader.active = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"cheatsheetClose\"\n        description: \"Closes cheatsheet on press\"\n\n        onPressed: {\n            cheatsheetLoader.active = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybinds.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: root\n    property real padding: 4\n    implicitWidth: QsWindow?.window?.screen.width * 0.7 ?? 0\n    implicitHeight: QsWindow?.window?.screen.height * 0.7 ?? 0\n\n    StyledFlickable {\n        id: flickable\n        clip: true\n        anchors.fill: parent\n        anchors.margins: Appearance.rounding.small\n        contentHeight: height\n        contentWidth: flow.implicitWidth\n        Flow {\n            id: flow\n            height: flickable.height\n            flow: Flow.TopToBottom\n            spacing: 10\n            Repeater {\n                model: [...HyprlandKeybinds.keybindCategories, \"\"]\n                delegate: CheatsheetKeybindsCategory {\n                    required property var modelData\n                    categoryName: modelData\n                }\n            }\n        }\n    }\n\n    ScrollEdgeFade {\n        target: flickable\n        vertical: false\n        color: Appearance.colors.colLayer0Base\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetKeybindsCategory.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\n\n// Notes:\n// We deal with keybinds being numbered 1, 2, etc by discarding 2+, keeping 1 and replacing it with a generic \"<Number>\"\nColumn {\n    id: root\n    required property string categoryName\n    readonly property bool isCategorized: categoryName?.length > 0\n    property int maxBindWidth: 0\n    property real columnSpacing: 40\n    property real titleSpacing: 7\n\n    // Excellent symbol explaination and source :\n    // http://xahlee.info/comp/unicode_computing_symbols.html\n    // https://www.nerdfonts.com/cheat-sheet\n    property var macSymbolMap: ({\n        \"Ctrl\": \"󰘴\",\n        \"Alt\": \"󰘵\",\n        \"Shift\": \"󰘶\",\n        \"Space\": \"󱁐\",\n        \"Tab\": \"↹\",\n        \"Equal\": \"󰇼\",\n        \"Minus\": \"\",\n        \"Print\": \"\",\n        \"BackSpace\": \"󰭜\",\n        \"Delete\": \"⌦\",\n        \"Return\": \"󰌑\",\n        \"Period\": \".\",\n        \"Escape\": \"⎋\"\n      })\n    property var functionSymbolMap: ({\n        \"F1\":  \"󱊫\",\n        \"F2\":  \"󱊬\",\n        \"F3\":  \"󱊭\",\n        \"F4\":  \"󱊮\",\n        \"F5\":  \"󱊯\",\n        \"F6\":  \"󱊰\",\n        \"F7\":  \"󱊱\",\n        \"F8\":  \"󱊲\",\n        \"F9\":  \"󱊳\",\n        \"F10\": \"󱊴\",\n        \"F11\": \"󱊵\",\n        \"F12\": \"󱊶\",\n    })\n\n    property var mouseSymbolMap: ({\n        \"mouse_up\": \"󱕐\",\n        \"mouse_down\": \"󱕑\",\n        \"mouse:272\": \"L󰍽\",\n        \"mouse:273\": \"R󰍽\",\n        \"Scroll ↑/↓\": \"󱕒\",\n        \"Page_↑/↓\": \"⇞/⇟\",\n    })\n\n    property var keyBlacklist: [\"SUPER_L\", \"SUPER_R\"]\n    property var keySubstitutions: Object.assign({\n        \"Super\": \"\",\n        \"mouse_up\": \"Scroll ↓\",    // ikr, weird\n        \"mouse_down\": \"Scroll ↑\",  // trust me bro\n        \"mouse:272\": \"LMB\",\n        \"mouse:273\": \"RMB\",\n        \"mouse:275\": \"MouseBack\",\n        \"Slash\": \"/\",\n        \"Hash\": \"#\",\n        \"Return\": \"Enter\",\n        // \"Shift\": \"\",\n      },\n      !!Config.options.cheatsheet.superKey ? {\n          \"Super\": Config.options.cheatsheet.superKey,\n      }: {},\n      Config.options.cheatsheet.useMacSymbol ? macSymbolMap : {},\n      Config.options.cheatsheet.useFnSymbol ? functionSymbolMap : {},\n      Config.options.cheatsheet.useMouseSymbol ? mouseSymbolMap : {},\n    )\n\n    function modMaskToStringList(modMask: int): list<string> {\n        var list = [];\n        // Funny mathematical order but we wanna have this natural user-facing order\n        if (modMask & (1 << 2)) { list.push(\"Ctrl\"); }\n        if (modMask & (1 << 6)) { list.push(\"Super\"); }\n        if (modMask & (1 << 0)) { list.push(\"Shift\"); }\n        if (modMask & (1 << 3)) { list.push(\"Alt\"); }\n        if (modMask & (1 << 1)) { list.push(\"Caps\"); }\n        if (modMask & (1 << 4)) { list.push(\"Mod2\"); }\n        if (modMask & (1 << 5)) { list.push(\"Mod3\"); }\n        if (modMask & (1 << 7)) { list.push(\"Mod5\"); }\n        return list;\n    }\n\n    visible: repeater.model.length > 0\n    spacing: titleSpacing\n\n    StyledText {\n        text: root.isCategorized ? root.categoryName : \"Uncategorized\"\n        font.pixelSize: Appearance.font.pixelSize.title\n    }\n\n    function hasDescription(bind) {\n        return bind.description?.length > 0;\n    }\n\n    function isCategory(bind, categoryName) {\n        return bind.description.substring(0, bind.description.indexOf(\":\")) === categoryName;\n    }\n\n    function isUncategorized(bind) {\n        return bind.description.indexOf(\":\") === -1;\n    }\n\n    function containsNonFirstRepetitive(bind) {\n        const key = bind.key;\n        if (key.includes(\"mouse\") || key.includes(\"page\")) return false;\n        // Contains non-1 number\n        if (/\\d/.test(key) && !key.includes(\"1\")) return true;\n        // Contains non-left direction\n        if (/^(right|up|down)\\b/i.test(key)) return true;\n        return false;\n    }\n\n    function containsFirstRepetitive(bind) {\n        const key = bind.key;\n        return key.includes(\"1\") || /left/i.test(key);\n    }\n\n    function transformKey(key) {\n        const replaced = root.keySubstitutions[key] || key;\n        const denumbered = replaced.replace(\"1\", \"<Number>\");\n        const dedirectioned = denumbered.replace(\"Left\", \"<Direction>\");\n        return dedirectioned;\n    }\n\n    function transformDescription(bind, categoryName) {\n        const description = bind.description\n        const regex = new RegExp(\"\\\\s*\" + categoryName + \"\\\\s*:\\\\s*\");\n        const decategorized = description.replace(regex, \"\");\n        if (!containsFirstRepetitive(bind)) return decategorized;\n        const denumbered = decategorized.replace(\"1\", \"<Number>\");\n        const dedirectioned = denumbered.replace(/ \\b(left|right|up|down)\\b/i, \" <Direction>\");\n        return dedirectioned;\n    }\n\n    Column {\n        spacing: 4\n        Repeater {\n            id: repeater\n            model: {\n                if (!root.isCategorized) {\n                    return HyprlandKeybinds.keybinds.filter(bind => root.hasDescription(bind) && root.isUncategorized(bind) && !root.containsNonFirstRepetitive(bind));\n                }\n                return HyprlandKeybinds.keybinds.filter(bind => root.hasDescription(bind) && root.isCategory(bind, root.categoryName) && !root.containsNonFirstRepetitive(bind));\n            }\n            delegate: BindLine {\n                required property var modelData\n                keyData: modelData\n                categoryName: root.categoryName\n            }\n        }\n    }\n\n    component BindLine: Row {\n        id: bindLine\n        required property var keyData\n        property string categoryName: \"\"\n\n        Row {\n            spacing: 16\n            Row {\n                id: modRow\n                Component.onCompleted: root.maxBindWidth = Math.max(root.maxBindWidth, implicitWidth)\n                width: root.maxBindWidth\n                spacing: 4\n                Repeater {\n                    model: {\n                        const modList = root.modMaskToStringList(bindLine.keyData.modmask).map(mod => root.keySubstitutions[mod] || mod)\n                        if (modList.length == 0) return []\n                        if (Config.options.cheatsheet.splitButtons) return modList;\n                        return [modList.join(\" \")]\n                    }\n                    delegate: KeyboardKey {\n                        required property var modelData\n                        key: root.transformKey(modelData)\n                        pixelSize: Config.options.cheatsheet.fontSize.key\n                    }\n                }\n                StyledText {\n                    id: keybindPlus\n                    anchors.verticalCenter: parent.verticalCenter\n                    visible: !keyBlacklist.includes(bindLine.keyData.key) && bindLine.keyData.modmask > 0\n                    text: \"+\"\n                }\n                KeyboardKey {\n                    id: keybindKey\n                    anchors.verticalCenter: parent.verticalCenter\n                    visible: !keyBlacklist.includes(bindLine.keyData.key)\n                    key: root.transformKey(bindLine.keyData.key)\n                    pixelSize: Config.options.cheatsheet.fontSize.key\n                    color: Appearance.colors.colOnLayer0\n                }\n            }\n            Item {\n                anchors.verticalCenter: parent.verticalCenter\n                implicitWidth: commentText.implicitWidth + root.columnSpacing\n                implicitHeight: commentText.implicitHeight\n                StyledText {\n                    id: commentText\n                    anchors.verticalCenter: parent.verticalCenter\n                    anchors.left: parent.left\n                    font.pixelSize: Config.options.cheatsheet.fontSize.comment || Appearance.font.pixelSize.smaller\n                    text: root.transformDescription(bindLine.keyData, bindLine.categoryName)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/cheatsheet/CheatsheetPeriodicTable.qml",
    "content": "import \"periodic_table.js\" as PTable\nimport QtQuick\n\nItem {\n    id: root\n    readonly property var elements: PTable.elements\n    readonly property var series: PTable.series\n    property real spacing: 6\n    implicitWidth: mainLayout.implicitWidth\n    implicitHeight: mainLayout.implicitHeight\n\n    Column {\n        id: mainLayout\n        anchors.centerIn: parent\n        spacing: root.spacing\n\n        Repeater { // Main table rows\n            model: root.elements\n            \n            delegate: Row { // Table cells\n                id: tableRow\n                spacing: root.spacing\n                required property var modelData\n                \n                Repeater {\n                    model: tableRow.modelData\n                    delegate: ElementTile {\n                        required property var modelData\n                        element: modelData\n                    }\n\n                }\n            }\n            \n        }\n\n        Item {\n            id: gap\n            implicitHeight: 20\n        }\n\n        Repeater { // Main table rows\n            model: root.series\n            \n            delegate: Row { // Table cells\n                id: seriesTableRow\n                spacing: root.spacing\n                required property var modelData\n                \n                Repeater {\n                    model: seriesTableRow.modelData\n                    delegate: ElementTile {\n                        required property var modelData\n                        element: modelData\n                    }\n\n                }\n            }\n            \n        }\n    }\n    \n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/cheatsheet/ElementTile.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\n\nRippleButton {\n    id: root\n    required property var element\n    opacity: element.type != \"empty\" ? 1 : 0\n    implicitHeight: 70\n    implicitWidth: 70\n    colBackground: Appearance.colors.colLayer2\n    buttonRadius: Appearance.rounding.small\n\n    Rectangle {\n        anchors {\n            top: parent.top\n            left: parent.left\n            topMargin: 4\n            leftMargin: 4\n        }\n        color: ColorUtils.transparentize(Appearance.colors.colLayer2)\n        radius: Appearance.rounding.full\n        implicitWidth: Math.max(20, elementNumber.implicitWidth)\n        implicitHeight: Math.max(20, elementNumber.implicitHeight)\n        width: height\n\n        StyledText {\n            id: elementNumber\n            anchors.left: parent.left\n            color: Appearance.colors.colOnLayer2\n            text: root.element.number\n            font.pixelSize: Appearance.font.pixelSize.smallest\n        }\n    }\n\n    Rectangle {\n        anchors {\n            top: parent.top\n            right: parent.right\n            topMargin: 4\n            rightMargin: 4\n        }\n        color: ColorUtils.transparentize(Appearance.colors.colLayer2)\n        radius: Appearance.rounding.full\n        implicitWidth: Math.max(20, elementWeight.implicitWidth)\n        implicitHeight: Math.max(20, elementWeight.implicitHeight)\n        width: height\n\n        StyledText {\n            id: elementWeight\n            anchors.right: parent.right\n            color: Appearance.colors.colOnLayer2\n            text: root.element.weight\n            font.pixelSize: Appearance.font.pixelSize.smallest\n        }\n    }\n\n    StyledText {\n        id: elementSymbol\n        anchors.centerIn: parent\n        color: Appearance.colors.colSecondary\n        font.pixelSize: Appearance.font.pixelSize.huge\n        text: root.element.symbol\n    }\n\n    StyledText {\n        id: elementName\n        anchors {\n            horizontalCenter: parent.horizontalCenter\n            bottom: parent.bottom\n            bottomMargin: 4\n        }\n        font.pixelSize: Appearance.font.pixelSize.smallest\n        color: Appearance.colors.colOnLayer2\n        text: root.element.name\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/cheatsheet/periodic_table.js",
    "content": "// List of rows\nconst elements = [\n    [\n        { name: 'Hydrogen', symbol: 'H', number: 1, weight: 1.01, type: 'nonmetal' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: 'Helium', symbol: 'He', number: 2, weight: 4.00, type: 'noblegas' },\n    ],\n    [\n        { name: 'Lithium', symbol: 'Li', number: 3, weight: 6.94, type: 'metal' },\n        { name: 'Beryllium', symbol: 'Be', number: 4, weight: 9.01, type: 'metal' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: 'Boron', symbol: 'B', number: 5, weight: 10.81, type: 'nonmetal' },\n        { name: 'Carbon', symbol: 'C', number: 6, weight: 12.01, type: 'nonmetal' },\n        { name: 'Nitrogen', symbol: 'N', number: 7, weight: 14.01, type: 'nonmetal' },\n        { name: 'Oxygen', symbol: 'O', number: 8, weight: 16, type: 'nonmetal' },\n        { name: 'Fluorine', symbol: 'F', number: 9, weight: 19, type: 'nonmetal' },\n        { name: 'Neon', symbol: 'Ne', number: 10, weight: 20.18, type: 'noblegas' },\n\n\n    ],\n    [\n        { name: 'Sodium', symbol: 'Na', number: 11, weight: 22.99, type: 'metal' },\n        { name: 'Magnesium', symbol: 'Mg', number: 12, weight: 24.31, type: 'metal' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: 'Aluminum', symbol: 'Al', number: 13, weight: 26.98, type: 'metal' },\n        { name: 'Silicon', symbol: 'Si', number: 14, weight: 28.09, type: 'nonmetal' },\n        { name: 'Phosphorus', symbol: 'P', number: 15, weight: 30.97, type: 'nonmetal' },\n        { name: 'Sulfur', symbol: 'S', number: 16, weight: 32.07, type: 'nonmetal' },\n        { name: 'Chlorine', symbol: 'Cl', number: 17, weight: 35.45, type: 'nonmetal' },\n        { name: 'Argon', symbol: 'Ar', number: 18, weight: 39.95, type: 'noblegas' },\n    ],\n    [\n        { name: 'Potassium', symbol: 'K', number: 19, weight: 39.098, type: 'metal' },\n        { name: 'Calcium', symbol: 'Ca', number: 20, weight: 40.078, type: 'metal' },\n        { name: 'Scandium', symbol: 'Sc', number: 21, weight: 44.956, type: 'metal' },\n        { name: 'Titanium', symbol: 'Ti', number: 22, weight: 47.87, type: 'metal' },\n        { name: 'Vanadium', symbol: 'V', number: 23, weight: 50.94, type: 'metal' },\n        { name: 'Chromium', symbol: 'Cr', number: 24, weight: 52, type: 'metal'/*, icon: 'chromium-browser'*/ },\n        { name: 'Manganese', symbol: 'Mn', number: 25, weight: 54.94, type: 'metal' },\n        { name: 'Iron', symbol: 'Fe', number: 26, weight: 55.85, type: 'metal' },\n        { name: 'Cobalt', symbol: 'Co', number: 27, weight: 58.93, type: 'metal' },\n        { name: 'Nickel', symbol: 'Ni', number: 28, weight: 58.69, type: 'metal' },\n        { name: 'Copper', symbol: 'Cu', number: 29, weight: 63.55, type: 'metal' },\n        { name: 'Zinc', symbol: 'Zn', number: 30, weight: 65.38, type: 'metal' },\n        { name: 'Gallium', symbol: 'Ga', number: 31, weight: 69.72, type: 'metal' },\n        { name: 'Germanium', symbol: 'Ge', number: 32, weight: 72.63, type: 'metal' },\n        { name: 'Arsenic', symbol: 'As', number: 33, weight: 74.92, type: 'nonmetal' },\n        { name: 'Selenium', symbol: 'Se', number: 34, weight: 78.96, type: 'nonmetal' },\n        { name: 'Bromine', symbol: 'Br', number: 35, weight: 79.904, type: 'nonmetal' },\n        { name: 'Krypton', symbol: 'Kr', number: 36, weight: 83.8, type: 'noblegas' },\n    ],\n    [\n        { name: 'Rubidium', symbol: 'Rb', number: 37, weight: 85.47, type: 'metal' },\n        { name: 'Strontium', symbol: 'Sr', number: 38, weight: 87.62, type: 'metal' },\n        { name: 'Yttrium', symbol: 'Y', number: 39, weight: 88.91, type: 'metal' },\n        { name: 'Zirconium', symbol: 'Zr', number: 40, weight: 91.22, type: 'metal' },\n        { name: 'Niobium', symbol: 'Nb', number: 41, weight: 92.91, type: 'metal' },\n        { name: 'Molybdenum', symbol: 'Mo', number: 42, weight: 95.94, type: 'metal' },\n        { name: 'Technetium', symbol: 'Tc', number: 43, weight: 98, type: 'metal' },\n        { name: 'Ruthenium', symbol: 'Ru', number: 44, weight: 101.07, type: 'metal' },\n        { name: 'Rhodium', symbol: 'Rh', number: 45, weight: 102.91, type: 'metal' },\n        { name: 'Palladium', symbol: 'Pd', number: 46, weight: 106.42, type: 'metal' },\n        { name: 'Silver', symbol: 'Ag', number: 47, weight: 107.87, type: 'metal' },\n        { name: 'Cadmium', symbol: 'Cd', number: 48, weight: 112.41, type: 'metal' },\n        { name: 'Indium', symbol: 'In', number: 49, weight: 114.82, type: 'metal' },\n        { name: 'Tin', symbol: 'Sn', number: 50, weight: 118.71, type: 'metal' },\n        { name: 'Antimony', symbol: 'Sb', number: 51, weight: 121.76, type: 'metal' },\n        { name: 'Tellurium', symbol: 'Te', number: 52, weight: 127.6, type: 'nonmetal' },\n        { name: 'Iodine', symbol: 'I', number: 53, weight: 126.9, type: 'nonmetal' },\n        { name: 'Xenon', symbol: 'Xe', number: 54, weight: 131.29, type: 'noblegas' },\n    ],\n    [\n        { name: 'Cesium', symbol: 'Cs', number: 55, weight: 132.91, type: 'metal' },\n        { name: 'Barium', symbol: 'Ba', number: 56, weight: 137.33, type: 'metal' },\n        { name: 'Lanthanum', symbol: 'La', number: 57, weight: 138.91, type: 'lanthanum' },\n        { name: 'Hafnium', symbol: 'Hf', number: 72, weight: 178.49, type: 'metal' },\n        { name: 'Tantalum', symbol: 'Ta', number: 73, weight: 180.95, type: 'metal' },\n        { name: 'Tungsten', symbol: 'W', number: 74, weight: 183.84, type: 'metal' },\n        { name: 'Rhenium', symbol: 'Re', number: 75, weight: 186.21, type: 'metal' },\n        { name: 'Osmium', symbol: 'Os', number: 76, weight: 190.23, type: 'metal' },\n        { name: 'Iridium', symbol: 'Ir', number: 77, weight: 192.22, type: 'metal' },\n        { name: 'Platinum', symbol: 'Pt', number: 78, weight: 195.09, type: 'metal' },\n        { name: 'Gold', symbol: 'Au', number: 79, weight: 196.97, type: 'metal' },\n        { name: 'Mercury', symbol: 'Hg', number: 80, weight: 200.59, type: 'metal' },\n        { name: 'Thallium', symbol: 'Tl', number: 81, weight: 204.38, type: 'metal' },\n        { name: 'Lead', symbol: 'Pb', number: 82, weight: 207.2, type: 'metal' },\n        { name: 'Bismuth', symbol: 'Bi', number: 83, weight: 208.98, type: 'metal' },\n        { name: 'Polonium', symbol: 'Po', number: 84, weight: 209, type: 'metal' },\n        { name: 'Astatine', symbol: 'At', number: 85, weight: 210, type: 'nonmetal' },\n        { name: 'Radon', symbol: 'Rn', number: 86, weight: 222, type: 'noblegas' },\n    ],\n    [\n        { name: 'Francium', symbol: 'Fr', number: 87, weight: 223, type: 'metal' },\n        { name: 'Radium', symbol: 'Ra', number: 88, weight: 226, type: 'metal' },\n        { name: 'Actinium', symbol: 'Ac', number: 89, weight: 227, type: 'actinium' },\n        { name: 'Rutherfordium', symbol: 'Rf', number: 104, weight: 267, type: 'metal' },\n        { name: 'Dubnium', symbol: 'Db', number: 105, weight: 268, type: 'metal' },\n        { name: 'Seaborgium', symbol: 'Sg', number: 106, weight: 271, type: 'metal' },\n        { name: 'Bohrium', symbol: 'Bh', number: 107, weight: 272, type: 'metal' },\n        { name: 'Hassium', symbol: 'Hs', number: 108, weight: 277, type: 'metal' },\n        { name: 'Meitnerium', symbol: 'Mt', number: 109, weight: 278, type: 'metal' },\n        { name: 'Darmstadtium', symbol: 'Ds', number: 110, weight: 281, type: 'metal' },\n        { name: 'Roentgenium', symbol: 'Rg', number: 111, weight: 280, type: 'metal' },\n        { name: 'Copernicium', symbol: 'Cn', number: 112, weight: 285, type: 'metal' },\n        { name: 'Nihonium', symbol: 'Nh', number: 113, weight: 286, type: 'metal' },\n        { name: 'Flerovium', symbol: 'Fl', number: 114, weight: 289, type: 'metal' },\n        { name: 'Moscovium', symbol: 'Mc', number: 115, weight: 290, type: 'metal' },\n        { name: 'Livermorium', symbol: 'Lv', number: 116, weight: 293, type: 'metal' },\n        { name: 'Tennessine', symbol: 'Ts', number: 117, weight: 294, type: 'metal' },\n        { name: 'Oganesson', symbol: 'Og', number: 118, weight: 294, type: 'noblegas' },\n    ],\n]\n\nconst series = [\n    [\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: 'Cerium', symbol: 'Ce', number: 58, weight: 140.12, type: 'lanthanum' },\n        { name: 'Praseodymium', symbol: 'Pr', number: 59, weight: 140.91, type: 'lanthanum' },\n        { name: 'Neodymium', symbol: 'Nd', number: 60, weight: 144.24, type: 'lanthanum' },\n        { name: 'Promethium', symbol: 'Pm', number: 61, weight: 145, type: 'lanthanum' },\n        { name: 'Samarium', symbol: 'Sm', number: 62, weight: 150.36, type: 'lanthanum' },\n        { name: 'Europium', symbol: 'Eu', number: 63, weight: 151.96, type: 'lanthanum' },\n        { name: 'Gadolinium', symbol: 'Gd', number: 64, weight: 157.25, type: 'lanthanum' },\n        { name: 'Terbium', symbol: 'Tb', number: 65, weight: 158.93, type: 'lanthanum' },\n        { name: 'Dysprosium', symbol: 'Dy', number: 66, weight: 162.5, type: 'lanthanum' },\n        { name: 'Holmium', symbol: 'Ho', number: 67, weight: 164.93, type: 'lanthanum' },\n        { name: 'Erbium', symbol: 'Er', number: 68, weight: 167.26, type: 'lanthanum' },\n        { name: 'Thulium', symbol: 'Tm', number: 69, weight: 168.93, type: 'lanthanum' },\n        { name: 'Ytterbium', symbol: 'Yb', number: 70, weight: 173.04, type: 'lanthanum' },\n        { name: 'Lutetium', symbol: 'Lu', number: 71, weight: 174.97, type: 'lanthanum' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n    ],\n    [\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n        { name: 'Thorium', symbol: 'Th', number: 90, weight: 232.04, type: 'actinium' },\n        { name: 'Protactinium', symbol: 'Pa', number: 91, weight: 231.04, type: 'actinium' },\n        { name: 'Uranium', symbol: 'U', number: 92, weight: 238.03, type: 'actinium' },\n        { name: 'Neptunium', symbol: 'Np', number: 93, weight: 237, type: 'actinium' },\n        { name: 'Plutonium', symbol: 'Pu', number: 94, weight: 244, type: 'actinium' },\n        { name: 'Americium', symbol: 'Am', number: 95, weight: 243, type: 'actinium' },\n        { name: 'Curium', symbol: 'Cm', number: 96, weight: 247, type: 'actinium' },\n        { name: 'Berkelium', symbol: 'Bk', number: 97, weight: 247, type: 'actinium' },\n        { name: 'Californium', symbol: 'Cf', number: 98, weight: 251, type: 'actinium' },\n        { name: 'Einsteinium', symbol: 'Es', number: 99, weight: 252, type: 'actinium' },\n        { name: 'Fermium', symbol: 'Fm', number: 100, weight: 257, type: 'actinium' },\n        { name: 'Mendelevium', symbol: 'Md', number: 101, weight: 258, type: 'actinium' },\n        { name: 'Nobelium', symbol: 'No', number: 102, weight: 259, type: 'actinium' },\n        { name: 'Lawrencium', symbol: 'Lr', number: 103, weight: 262, type: 'actinium' },\n        { name: '', symbol: '', number: -1, weight: 0, type: 'empty' },\n    ],\n];\n\nconst niceTypes = {\n    'metal': \"Metal\",\n    'nonmetal': \"Nonmetal\",\n    'noblegas': \"Noble gas\",\n    'lanthanum': \"Lanthanum\",\n    'actinium': \"Actinium\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/dock/Dock.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Effects\nimport QtQuick.Layouts\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Widgets\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope { // Scope\n    id: root\n    property bool pinned: Config.options?.dock.pinnedOnStartup ?? false\n\n    Variants {\n        // For each monitor\n        model: Quickshell.screens\n\n        PanelWindow {\n            id: dockRoot\n            // Window\n            required property var modelData\n            screen: modelData\n            visible: !GlobalStates.screenLocked\n\n            property bool reveal: root.pinned || (Config.options?.dock.hoverToReveal && dockMouseArea.containsMouse) || dockApps.requestDockShow || (!ToplevelManager.activeToplevel?.activated)\n\n            anchors {\n                bottom: true\n                left: true\n                right: true\n            }\n\n            exclusiveZone: root.pinned ? implicitHeight - (Appearance.sizes.hyprlandGapsOut) - (Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut) : 0\n\n            implicitWidth: dockBackground.implicitWidth\n            WlrLayershell.namespace: \"quickshell:dock\"\n            color: \"transparent\"\n\n            implicitHeight: (Config.options?.dock.height ?? 70) + Appearance.sizes.elevationMargin + Appearance.sizes.hyprlandGapsOut\n\n            mask: Region {\n                item: dockMouseArea\n            }\n\n            MouseArea {\n                id: dockMouseArea\n                height: parent.height\n                anchors {\n                    top: parent.top\n                    topMargin: dockRoot.reveal ? 0 : Config.options?.dock.hoverToReveal ? (dockRoot.implicitHeight - Config.options.dock.hoverRegionHeight) : (dockRoot.implicitHeight + 1)\n                    horizontalCenter: parent.horizontalCenter\n                }\n                implicitWidth: dockHoverRegion.implicitWidth + Appearance.sizes.elevationMargin * 2\n                hoverEnabled: true\n\n                Behavior on anchors.topMargin {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n\n                Item {\n                    id: dockHoverRegion\n                    anchors.fill: parent\n                    implicitWidth: dockBackground.implicitWidth\n\n                    Item { // Wrapper for the dock background\n                        id: dockBackground\n                        anchors {\n                            top: parent.top\n                            bottom: parent.bottom\n                            horizontalCenter: parent.horizontalCenter\n                        }\n\n                        implicitWidth: dockRow.implicitWidth + 5 * 2\n                        height: parent.height - Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut\n\n                        StyledRectangularShadow {\n                            target: dockVisualBackground\n                        }\n                        Rectangle { // The real rectangle that is visible\n                            id: dockVisualBackground\n                            property real margin: Appearance.sizes.elevationMargin\n                            anchors.fill: parent\n                            anchors.topMargin: Appearance.sizes.elevationMargin\n                            anchors.bottomMargin: Appearance.sizes.hyprlandGapsOut\n                            color: Appearance.colors.colLayer0\n                            border.width: 1\n                            border.color: Appearance.colors.colLayer0Border\n                            radius: Appearance.rounding.large\n                        }\n\n                        RowLayout {\n                            id: dockRow\n                            anchors.top: parent.top\n                            anchors.bottom: parent.bottom\n                            anchors.horizontalCenter: parent.horizontalCenter\n                            spacing: 3\n                            property real padding: 5\n\n                            VerticalButtonGroup {\n                                Layout.topMargin: Appearance.sizes.hyprlandGapsOut // why does this work\n                                GroupButton {\n                                    // Pin button\n                                    baseWidth: 35\n                                    baseHeight: 35\n                                    clickedWidth: baseWidth\n                                    clickedHeight: baseHeight + 20\n                                    buttonRadius: Appearance.rounding.normal\n                                    toggled: root.pinned\n                                    onClicked: root.pinned = !root.pinned\n                                    contentItem: MaterialSymbol {\n                                        text: \"keep\"\n                                        horizontalAlignment: Text.AlignHCenter\n                                        iconSize: Appearance.font.pixelSize.larger\n                                        color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0\n                                    }\n                                }\n                            }\n                            DockSeparator {}\n                            DockApps {\n                                id: dockApps\n                                buttonPadding: dockRow.padding\n                            }\n                            DockSeparator {}\n                            DockButton {\n                                Layout.fillHeight: true\n                                onClicked: GlobalStates.overviewOpen = !GlobalStates.overviewOpen\n                                topInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding\n                                bottomInset: Appearance.sizes.hyprlandGapsOut + dockRow.padding\n                                contentItem: MaterialSymbol {\n                                    anchors.fill: parent\n                                    horizontalAlignment: Text.AlignHCenter\n                                    font.pixelSize: parent.width / 2\n                                    text: \"apps\"\n                                    color: Appearance.colors.colOnLayer0\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/dock/DockAppButton.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\n\nDockButton {\n    id: root\n    property var appToplevel\n    property var appListRoot\n    property int lastFocused: -1\n    property real iconSize: 35\n    property real countDotWidth: 10\n    property real countDotHeight: 4\n    property bool appIsActive: appToplevel.toplevels.find(t => (t.activated == true)) !== undefined\n\n    readonly property bool isSeparator: appToplevel.appId === \"SEPARATOR\"\n    property var desktopEntry: DesktopEntries.heuristicLookup(appToplevel.appId)\n    enabled: !isSeparator\n    implicitWidth: isSeparator ? 1 : implicitHeight - topInset - bottomInset\n\n    Connections {\n        target: DesktopEntries\n\n        function onApplicationsChanged() {\n            root.desktopEntry = DesktopEntries.heuristicLookup(appToplevel.appId);\n        }\n    }\n\n    Loader {\n        active: isSeparator\n        anchors {\n            fill: parent\n            topMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal\n            bottomMargin: dockVisualBackground.margin + dockRow.padding + Appearance.rounding.normal\n        }\n        sourceComponent: DockSeparator {}\n    }\n\n    Loader {\n        anchors.fill: parent\n        active: appToplevel.toplevels.length > 0\n        sourceComponent: MouseArea {\n            id: mouseArea\n            anchors.fill: parent\n            hoverEnabled: true\n            acceptedButtons: Qt.NoButton\n            onEntered: {\n                appListRoot.lastHoveredButton = root\n                appListRoot.buttonHovered = true\n                lastFocused = appToplevel.toplevels.length - 1\n            }\n            onExited: {\n                if (appListRoot.lastHoveredButton === root) {\n                    appListRoot.buttonHovered = false\n                }\n            }\n        }\n    }\n\n    onClicked: {\n        if (appToplevel.toplevels.length === 0) {\n            root.desktopEntry?.execute();\n            return;\n        }\n        lastFocused = (lastFocused + 1) % appToplevel.toplevels.length\n        appToplevel.toplevels[lastFocused].activate()\n    }\n\n    middleClickAction: () => {\n        root.desktopEntry?.execute();\n    }\n\n    altAction: () => {\n        TaskbarApps.togglePin(appToplevel.appId);\n    }\n\n    contentItem: Loader {\n        active: !isSeparator\n        sourceComponent: Item {\n            anchors.centerIn: parent\n\n            Loader {\n                id: iconImageLoader\n                anchors {\n                    left: parent.left\n                    right: parent.right\n                    verticalCenter: parent.verticalCenter\n                }\n                active: !root.isSeparator\n                sourceComponent: IconImage {\n                    source: Quickshell.iconPath(AppSearch.guessIcon(appToplevel.appId), \"image-missing\")\n                    implicitSize: root.iconSize\n                }\n            }\n\n            Loader {\n                active: Config.options.dock.monochromeIcons\n                anchors.fill: iconImageLoader\n                sourceComponent: Item {\n                    Desaturate {\n                        id: desaturatedIcon\n                        visible: false // There's already color overlay\n                        anchors.fill: parent\n                        source: iconImageLoader\n                        desaturation: 0.8\n                    }\n                    ColorOverlay {\n                        anchors.fill: desaturatedIcon\n                        source: desaturatedIcon\n                        color: ColorUtils.transparentize(Appearance.colors.colPrimary, 0.9)\n                    }\n                }\n            }\n\n            RowLayout {\n                spacing: 3\n                anchors {\n                    top: iconImageLoader.bottom\n                    topMargin: 2\n                    horizontalCenter: parent.horizontalCenter\n                }\n                Repeater {\n                    model: Math.min(appToplevel.toplevels.length, 3)\n                    delegate: Rectangle {\n                        required property int index\n                        radius: Appearance.rounding.full\n                        implicitWidth: (appToplevel.toplevels.length <= 3) ? \n                            root.countDotWidth : root.countDotHeight // Circles when too many\n                        implicitHeight: root.countDotHeight\n                        color: appIsActive ? Appearance.colors.colPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer0, 0.4)\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/dock/DockApps.qml",
    "content": "pragma ComponentBehavior: Bound\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\nimport Quickshell.Wayland\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nItem {\n    id: root\n    property real maxWindowPreviewHeight: 200\n    property real maxWindowPreviewWidth: 300\n    property real windowControlsHeight: 30\n    property real buttonPadding: 5\n\n    property Item lastHoveredButton: null\n    property bool buttonHovered: false\n    property bool requestDockShow: previewPopup.show\n\n    Layout.fillHeight: true\n    Layout.topMargin: Appearance.sizes.hyprlandGapsOut\n    implicitWidth: listView.implicitWidth\n\n    function popupCenterXForButton(button) {\n        if (!button || !root.QsWindow)\n            return 0;\n        return root.QsWindow.mapFromItem(button, button.width / 2, 0).x;\n    }\n\n    StyledListView {\n        id: listView\n        spacing: 2\n        orientation: ListView.Horizontal\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n        }\n        implicitWidth: contentWidth\n\n        Behavior on implicitWidth {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n\n        model: ScriptModel {\n            objectProp: \"appId\"\n            values: TaskbarApps.apps\n        }\n        delegate: DockAppButton {\n            required property var modelData\n            appToplevel: modelData\n            appListRoot: root\n\n            topInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding\n            bottomInset: Appearance.sizes.hyprlandGapsOut + root.buttonPadding\n        }\n    }\n\n    PopupWindow {\n        id: previewPopup\n        property var appTopLevel: root.lastHoveredButton?.appToplevel\n\n        property bool shouldShow: (popupMouseArea.containsMouse || root.buttonHovered) && appTopLevel && appTopLevel.toplevels && appTopLevel.toplevels.length > 0\n\n        property bool show: false\n        property real cachedCenterX: 0\n\n        Connections {\n            target: root\n            function onLastHoveredButtonChanged() {\n                if (root.lastHoveredButton && root.QsWindow)\n                    previewPopup.cachedCenterX = root.popupCenterXForButton(root.lastHoveredButton);\n            }\n            function onButtonHoveredChanged() {\n                if (root.buttonHovered && root.lastHoveredButton && root.QsWindow)\n                    previewPopup.cachedCenterX = root.popupCenterXForButton(root.lastHoveredButton);\n                updateTimer.restart();\n            }\n        }\n\n        onShouldShowChanged: {\n            updateTimer.restart();\n        }\n\n        Timer {\n            id: updateTimer\n            interval: 100\n            onTriggered: {\n                previewPopup.show = previewPopup.shouldShow;\n            }\n        }\n\n        anchor {\n            window: root.QsWindow.window\n            adjustment: PopupAdjustment.None\n            gravity: Edges.Top | Edges.Right\n            edges: Edges.Top | Edges.Left\n        }\n\n        visible: popupBackground.opacity > 0\n        color: \"transparent\"\n        implicitWidth: root.QsWindow.window?.width ?? 1\n        implicitHeight: popupMouseArea.implicitHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2\n\n        MouseArea {\n            id: popupMouseArea\n            anchors.bottom: parent.bottom\n            implicitWidth: popupBackground.implicitWidth + Appearance.sizes.elevationMargin * 2\n            implicitHeight: root.maxWindowPreviewHeight + root.windowControlsHeight + Appearance.sizes.elevationMargin * 2\n            hoverEnabled: true\n            x: previewPopup.cachedCenterX - width / 2\n\n            StyledRectangularShadow {\n                target: popupBackground\n                opacity: previewPopup.show ? 1 : 0\n                visible: opacity > 0\n                Behavior on opacity {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n            }\n\n            Rectangle {\n                id: popupBackground\n                property real padding: 5\n                opacity: previewPopup.show ? 1 : 0\n                visible: opacity > 0\n                Behavior on opacity {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                clip: true\n                color: Appearance.m3colors.m3surfaceContainer\n                radius: Appearance.rounding.normal\n                anchors.bottom: parent.bottom\n                anchors.bottomMargin: Appearance.sizes.elevationMargin\n                anchors.horizontalCenter: parent.horizontalCenter\n                implicitHeight: previewRowLayout.implicitHeight + padding * 2\n                implicitWidth: previewRowLayout.implicitWidth + padding * 2\n                Behavior on implicitWidth {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                Behavior on implicitHeight {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n\n                RowLayout {\n                    id: previewRowLayout\n                    anchors.centerIn: parent\n                    Repeater {\n                        model: ScriptModel {\n                            values: previewPopup.appTopLevel?.toplevels ?? []\n                        }\n                        RippleButton {\n                            id: windowButton\n                            Layout.fillHeight: true\n                            required property var modelData\n                            padding: 0\n                            middleClickAction: () => {\n                                windowButton.modelData?.close();\n                            }\n                            onClicked: {\n                                windowButton.modelData?.activate();\n                            }\n                            contentItem: ColumnLayout {\n                                implicitWidth: screencopyView.implicitWidth\n                                implicitHeight: screencopyView.implicitHeight\n\n                                ButtonGroup {\n                                    contentWidth: parent.width - anchors.margins * 2\n                                    StyledText {\n                                        Layout.margins: 5\n                                        Layout.fillWidth: true\n                                        font.pixelSize: Appearance.font.pixelSize.small\n                                        text: windowButton.modelData?.title\n                                        elide: Text.ElideRight\n                                        color: Appearance.m3colors.m3onSurface\n                                    }\n                                    GroupButton {\n                                        id: closeButton\n                                        colBackground: ColorUtils.transparentize(Appearance.colors.colSurfaceContainer)\n                                        baseWidth: root.windowControlsHeight\n                                        baseHeight: root.windowControlsHeight\n                                        buttonRadius: Appearance.rounding.full\n                                        contentItem: MaterialSymbol {\n                                            anchors.centerIn: parent\n                                            horizontalAlignment: Text.AlignHCenter\n                                            text: \"close\"\n                                            iconSize: Appearance.font.pixelSize.normal\n                                            color: Appearance.m3colors.m3onSurface\n                                        }\n                                        onClicked: {\n                                            windowButton.modelData?.close();\n                                        }\n                                    }\n                                }\n                                Item {\n                                    Layout.fillWidth: true\n                                    Layout.fillHeight: true\n                                    implicitHeight: screencopyView.height\n                                    implicitWidth: screencopyView.width\n                                    ScreencopyView {\n                                        id: screencopyView\n                                        anchors.centerIn: parent\n                                        captureSource: windowButton.modelData\n                                        live: true\n                                        paintCursor: true\n                                        constraintSize: Qt.size(root.maxWindowPreviewWidth, root.maxWindowPreviewHeight)\n                                        layer.enabled: true\n                                        layer.effect: OpacityMask {\n                                            maskSource: Rectangle {\n                                                width: screencopyView.width\n                                                height: screencopyView.height\n                                                radius: Appearance.rounding.small\n                                            }\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/dock/DockButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton {\n    Layout.fillHeight: true\n    Layout.topMargin: Appearance.sizes.elevationMargin - Appearance.sizes.hyprlandGapsOut\n    implicitWidth: implicitHeight - topInset - bottomInset\n    buttonRadius: Appearance.rounding.normal\n\n    background.implicitHeight: 50\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/dock/DockSeparator.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\n\nRectangle {\n    Layout.topMargin: Appearance.sizes.elevationMargin + dockRow.padding + Appearance.rounding.normal\n    Layout.bottomMargin: Appearance.sizes.hyprlandGapsOut + dockRow.padding + Appearance.rounding.normal\n    Layout.fillHeight: true\n    implicitWidth: 1\n    color: Appearance.colors.colOutlineVariant\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/lock/Lock.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.panels.lock\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\n\nLockScreen {\n    id: root\n\n    // Monitor name -> workspace id to restore on unlock (set when locking)\n    property var savedWorkspaces: ({})\n\n    Timer {\n        id: restoreTimer\n        interval: 150\n        repeat: false\n        onTriggered: {\n            var batch = \"\"\n            for (var j = 0; j < Quickshell.screens.length; ++j) {\n                var monName = Quickshell.screens[j].name\n                var wsId = root.savedWorkspaces[monName]\n                if (wsId !== undefined) {\n                    batch += `hyprctl dispatch 'hl.dsp.focus({monitor=\"${monName}\"})'; hyprctl dispatch 'hl.dsp.focus({workspace=${wsId}})';`\n                }\n            }\n            if (batch.length > 0) {\n                Quickshell.execDetached([\"bash\", \"-c\", batch])\n            }\n        }\n    }\n\n    lockSurface: LockSurface {\n        context: root.context\n    }\n\n    // Single batch for lock and unlock so we don't race multiple hyprctl calls\n    Connections {\n        target: GlobalStates\n        function onScreenLockedChanged() {\n            if (GlobalStates.screenLocked) {\n                // Lock: save workspace per monitor and move all to temp workspace in one batch\n                var next = {}\n                var batch = \"keyword animation workspaces,1,7,menu_decel,slidevert; \"\n                for (var i = 0; i < Quickshell.screens.length; ++i) {\n                    var mon = Quickshell.screens[i].name\n                    var mData = HyprlandData.monitors.find(m => m.name === mon)\n                    if (mData?.activeWorkspace == undefined) {\n                        return;\n                    }\n                    var ws = (mData?.activeWorkspace?.id ?? 1)\n                    next[mon] = ws\n                    batch += `hyprctl dispatch 'hl.dsp.focus({monitor=\"${mon}\"})'; hyprctl dispatch 'hl.dsp.focus({workspace=${2147483647 - ws}})';`\n                }\n                root.savedWorkspaces = next\n                Quickshell.execDetached([\"bash\", \"-c\", batch])\n            } else {\n                restoreTimer.start()\n            }\n        }\n    }\n\n    // Push everything down (visual only; workspace switch is in Connections above)\n    Variants {\n        model: Quickshell.screens\n        delegate: Scope {\n            required property ShellScreen modelData\n            property bool shouldPush: GlobalStates.screenLocked\n            property string targetMonitorName: modelData.name\n            property int verticalMovementDistance: modelData.height\n            property int horizontalSqueeze: modelData.width * 0.2\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/lock/LockSurface.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell.Services.UPower\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.common.panels.lock\nimport qs.modules.ii.bar as Bar\nimport Quickshell\nimport Quickshell.Services.SystemTray\n\nMouseArea {\n    id: root\n    required property LockContext context\n    property bool active: false\n    property bool showInputField: active || context.currentText.length > 0\n    readonly property bool requirePasswordToPower: Config.options.lock.security.requirePasswordToPower\n\n    // Force focus on entry\n    function forceFieldFocus() {\n        passwordBox.forceActiveFocus();\n    }\n    Connections {\n        target: context\n        function onShouldReFocus() {\n            forceFieldFocus();\n        }\n    }\n    hoverEnabled: true\n    acceptedButtons: Qt.LeftButton\n    onPressed: mouse => {\n        forceFieldFocus();\n    }\n    onPositionChanged: mouse => {\n        forceFieldFocus();\n    }\n\n    // Toolbar appearing animation\n    property real toolbarScale: 0.9\n    property real toolbarOpacity: 0\n    Behavior on toolbarScale {\n        NumberAnimation {\n            duration: Appearance.animation.elementMove.duration\n            easing.type: Appearance.animation.elementMove.type\n            easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n        }\n    }\n    Behavior on toolbarOpacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    // Init\n    Component.onCompleted: {\n        forceFieldFocus();\n        toolbarScale = 1;\n        toolbarOpacity = 1;\n    }\n\n    // Key presses\n    property bool ctrlHeld: false\n    Keys.onPressed: event => {\n        root.context.resetClearTimer();\n        if (event.key === Qt.Key_Control) {\n            root.ctrlHeld = true;\n        }\n        if (event.key === Qt.Key_Escape) { // Esc to clear\n            root.context.currentText = \"\";\n        } \n        forceFieldFocus();\n    }\n    Keys.onReleased: event => {\n        if (event.key === Qt.Key_Control) {\n            root.ctrlHeld = false;\n        }\n        forceFieldFocus();\n    }\n\n    // RippleButton {\n    //     anchors {\n    //         top: parent.top\n    //         left: parent.left\n    //         leftMargin: 10\n    //         topMargin: 10\n    //     }\n    //     implicitHeight: 40\n    //     colBackground: Appearance.colors.colLayer2\n    //     onClicked: {\n    //         context.unlocked(LockContext.ActionEnum.Unlock);\n    //         GlobalStates.screenLocked = false;\n    //     }\n    //     contentItem: StyledText {\n    //         text: \"[[ DEBUG BYPASS ]]\"\n    //     }\n    // }\n\n    // Main toolbar: password box\n    Toolbar {\n        id: mainIsland\n        anchors {\n            horizontalCenter: parent.horizontalCenter\n            bottom: parent.bottom\n            bottomMargin: 20\n        }\n        Behavior on anchors.bottomMargin {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n\n        scale: root.toolbarScale\n        opacity: root.toolbarOpacity\n\n        // Fingerprint\n        Loader {\n            Layout.leftMargin: 10\n            Layout.rightMargin: 6\n            Layout.alignment: Qt.AlignVCenter\n            active: root.context.fingerprintsConfigured\n            visible: active\n\n            sourceComponent: MaterialSymbol {\n                id: fingerprintIcon\n                fill: 1\n                text: \"fingerprint\"\n                iconSize: Appearance.font.pixelSize.hugeass\n                color: Appearance.colors.colOnSurfaceVariant\n            }\n        }\n\n        ToolbarTextField {\n            id: passwordBox\n            Layout.rightMargin: -Layout.leftMargin\n            placeholderText: GlobalStates.screenUnlockFailed ? Translation.tr(\"Incorrect password\") : Translation.tr(\"Enter password\")\n\n            // Style\n            clip: true\n            font.pixelSize: Appearance.font.pixelSize.small\n            selectedTextColor: materialShapeChars ? \"transparent\" : Appearance.colors.colOnSecondaryContainer\n            selectionColor: materialShapeChars ? \"transparent\" : Appearance.colors.colSecondaryContainer\n\n            // Password\n            enabled: !root.context.unlockInProgress\n            echoMode: TextInput.Password\n            inputMethodHints: Qt.ImhSensitiveData\n\n            // Synchronizing (across monitors) and unlocking\n            onTextChanged: root.context.currentText = this.text\n            onAccepted: {\n                root.context.tryUnlock(ctrlHeld);\n            }\n            Connections {\n                target: root.context\n                function onCurrentTextChanged() {\n                    passwordBox.text = root.context.currentText;\n                }\n            }\n\n            Keys.onPressed: event => {\n                root.context.resetClearTimer();\n            }\n            \n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: passwordBox.width - 8\n                    height: passwordBox.height\n                    radius: height / 2\n                }\n            }\n\n            // Shake when wrong password\n            ErrorShakeAnimation {\n                id: wrongPasswordShakeAnim\n                target: passwordBox\n            }\n            Connections {\n                target: GlobalStates\n                function onScreenUnlockFailedChanged() {\n                    if (GlobalStates.screenUnlockFailed) wrongPasswordShakeAnim.restart();\n                }\n            }\n\n            // We're drawing dots manually\n            property bool materialShapeChars: Config.options.lock.materialShapeChars\n            color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, materialShapeChars ? 1 : 0)\n            Loader {\n                active: passwordBox.materialShapeChars\n                anchors {\n                    fill: parent\n                    leftMargin: passwordBox.padding\n                    rightMargin: passwordBox.padding\n                }\n                sourceComponent: PasswordChars {\n                    length: root.context.currentText.length\n                    selectionStart: passwordBox.selectionStart\n                    selectionEnd: passwordBox.selectionEnd\n                    cursorPosition: passwordBox.cursorPosition\n                }\n            }\n        }\n\n        ToolbarButton {\n            id: confirmButton\n            implicitWidth: height\n            toggled: true\n            enabled: !root.context.unlockInProgress\n            colBackgroundToggled: Appearance.colors.colPrimary\n\n            onClicked: root.context.tryUnlock()\n\n            contentItem: MaterialSymbol {\n                anchors.centerIn: parent\n                horizontalAlignment: Text.AlignHCenter\n                verticalAlignment: Text.AlignVCenter\n                iconSize: 24\n                text: {\n                    if (root.context.targetAction === LockContext.ActionEnum.Unlock) {\n                        return root.ctrlHeld ? \"coffee\" : \"arrow_right_alt\";\n                    } else if (root.context.targetAction === LockContext.ActionEnum.Poweroff) {\n                        return \"power_settings_new\";\n                    } else if (root.context.targetAction === LockContext.ActionEnum.Reboot) {\n                        return \"restart_alt\";\n                    }\n                }\n                color: confirmButton.enabled ? Appearance.colors.colOnPrimary : Appearance.colors.colSubtext\n            }\n        }\n    }\n\n    // Left toolbar\n    Toolbar {\n        id: leftIsland\n        anchors {\n            right: mainIsland.left\n            top: mainIsland.top\n            bottom: mainIsland.bottom\n            rightMargin: 10\n        }\n        scale: root.toolbarScale\n        opacity: root.toolbarOpacity\n\n        // Username\n        IconAndTextPair {\n            Layout.leftMargin: 8\n            icon: \"account_circle\"\n            text: SystemInfo.username\n        }\n\n        // Keyboard layout (Xkb)\n        Loader {\n            Layout.rightMargin: 8\n            Layout.fillHeight: true\n\n            active: true\n            visible: active\n\n            sourceComponent: Row {\n                spacing: 8\n\n                MaterialSymbol {\n                    id: keyboardIcon\n                    anchors.verticalCenter: parent.verticalCenter\n                    fill: 1\n                    text: \"keyboard_alt\"\n                    iconSize: Appearance.font.pixelSize.huge\n                    color: Appearance.colors.colOnSurfaceVariant\n                }\n                Loader {\n                    anchors.verticalCenter: parent.verticalCenter\n                    sourceComponent: StyledText {\n                        text: HyprlandXkb.currentLayoutCode\n                        color: Appearance.colors.colOnSurfaceVariant\n                        animateChange: true\n                    }\n                }\n            }\n        }\n\n        // Keyboard layout (Fcitx)\n        Bar.SysTray {\n            Layout.rightMargin: 10\n            Layout.alignment: Qt.AlignVCenter\n            showSeparator: false\n            showOverflowMenu: false\n            pinnedItems: SystemTray.items.values.filter(i => i.id == \"Fcitx\")\n            visible: pinnedItems.length > 0\n        }\n    }\n\n    // Right toolbar\n    Toolbar {\n        id: rightIsland\n        anchors {\n            left: mainIsland.right\n            top: mainIsland.top\n            bottom: mainIsland.bottom\n            leftMargin: 10\n        }\n\n        scale: root.toolbarScale\n        opacity: root.toolbarOpacity\n\n        IconAndTextPair {\n            visible: Battery.available\n            icon: Battery.isCharging ? \"bolt\" : \"battery_android_full\"\n            text: Math.round(Battery.percentage * 100)\n            color: (Battery.isLow && !Battery.isCharging) ? Appearance.colors.colError : Appearance.colors.colOnSurfaceVariant\n        }\n\n        IconToolbarButton {\n            id: sleepButton\n            onClicked: Session.suspend()\n            text: \"dark_mode\"\n        }\n\n        PasswordGuardedIconToolbarButton {\n            id: powerButton\n            text: \"power_settings_new\"\n            targetAction: LockContext.ActionEnum.Poweroff\n        }\n\n        PasswordGuardedIconToolbarButton {\n            id: rebootButton\n            text: \"restart_alt\"\n            targetAction: LockContext.ActionEnum.Reboot\n        }\n    }\n\n    component PasswordGuardedIconToolbarButton: IconToolbarButton {\n        id: guardedBtn\n        required property var targetAction\n\n        toggled: root.context.targetAction === guardedBtn.targetAction\n\n        onClicked: {\n            if (!root.requirePasswordToPower) {\n                root.context.unlocked(guardedBtn.targetAction);\n                return;\n            }\n            if (root.context.targetAction === guardedBtn.targetAction) {\n                root.context.resetTargetAction();\n            } else {\n                root.context.targetAction = guardedBtn.targetAction;\n                root.context.shouldReFocus();\n            }\n        }\n    }\n\n    component IconAndTextPair: Row {\n        id: pair\n        required property string icon\n        required property string text\n        property color color: Appearance.colors.colOnSurfaceVariant\n\n        spacing: 4\n        Layout.fillHeight: true\n        Layout.leftMargin: 10\n        Layout.rightMargin: 10\n        \n\n        MaterialSymbol {\n            anchors.verticalCenter: parent.verticalCenter\n            fill: 1\n            text: pair.icon\n            iconSize: Appearance.font.pixelSize.huge\n            animateChange: true\n            color: pair.color\n        }\n        StyledText {\n            anchors.verticalCenter: parent.verticalCenter\n            text: pair.text\n            color: pair.color\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/lock/PasswordChars.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport Quickshell\n\nStyledFlickable {\n    id: root\n\n    required property int length\n    property int selectionStart\n    property int selectionEnd\n    property int cursorPosition\n\n    property color color: Appearance.colors.colPrimary\n    property color selectedTextColor: Appearance.colors.colOnSecondaryContainer\n    property color selectionColor: Appearance.colors.colSecondaryContainer\n\n    property int charSize: 20\n\n    contentWidth: dotsRow.implicitWidth\n    contentX: (Math.max(contentWidth - width, 0))\n    Behavior on contentX {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n\n    Rectangle {\n        id: cursor\n        anchors {\n            verticalCenter: parent.verticalCenter\n            left: parent.left\n            leftMargin: root.charSize * root.cursorPosition\n        }\n        color: root.color\n        implicitWidth: 2\n        implicitHeight: root.charSize\n        Behavior on anchors.leftMargin {\n            animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(cursor)\n        }\n    }\n\n    Row {\n        id: dotsRow\n        anchors {\n            left: parent.left\n            verticalCenter: parent.verticalCenter\n            leftMargin: 4 - 5 // -5 to account for spacing being simulated by char item width\n        }\n        spacing: 0\n\n        Repeater {\n            model: ScriptModel { // TODO: use proper custom object model to insert new char at the correct pos\n                values: Array(root.length)\n            }\n\n            delegate: Rectangle {\n                id: charItem\n                required property int index\n                implicitWidth: root.charSize\n                implicitHeight: root.charSize\n                property bool selected: index >= root.selectionStart && index < root.selectionEnd\n\n                color: ColorUtils.transparentize(root.selectionColor, selected ? 0 : 1)\n                \n                MaterialShape {\n                    id: materialShape\n                    anchors.centerIn: parent\n                    property list<var> charShapes: [\n                        MaterialShape.Shape.Clover4Leaf,\n                        MaterialShape.Shape.Arrow,\n                        MaterialShape.Shape.Pill,\n                        MaterialShape.Shape.SoftBurst,\n                        MaterialShape.Shape.Diamond,\n                        MaterialShape.Shape.ClamShell,\n                        MaterialShape.Shape.Pentagon,\n                    ]\n                    shape: charShapes[charItem.index % charShapes.length]\n                    // Animate on appearance\n                    color: charItem.selected ? root.selectedTextColor : root.color\n                    implicitSize: 0\n                    opacity: 0\n                    scale: 0.5\n                    Component.onCompleted: {\n                        appearAnim.start();\n                    }\n                    ParallelAnimation {\n                        id: appearAnim\n                        NumberAnimation {\n                            target: materialShape\n                            properties: \"opacity\"\n                            to: 1\n                            duration: 50\n                            easing.type: Appearance.animation.elementMoveFast.type\n                            easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                        }\n                        NumberAnimation {\n                            target: materialShape\n                            properties: \"scale\"\n                            to: 1\n                            duration: 200\n                            easing.type: Easing.BezierSpline\n                            easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n                        }\n                        NumberAnimation {\n                            target: materialShape\n                            properties: \"implicitSize\"\n                            to: 18\n                            easing.type: Easing.BezierSpline\n                            easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n                        }\n                        ColorAnimation {\n                            target: materialShape\n                            properties: \"color\"\n                            from: Appearance.colors.colPrimary\n                            to: Appearance.colors.colOnLayer1\n                            duration: 1000\n                            easing.type: Appearance.animation.elementMoveFast.type\n                            easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/mediaControls/MediaControls.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Services.Mpris\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n    property bool visible: false\n    readonly property MprisPlayer activePlayer: MprisController.activePlayer\n    readonly property var realPlayers: MprisController.players\n    readonly property var meaningfulPlayers: filterDuplicatePlayers(realPlayers)\n    readonly property real osdWidth: Appearance.sizes.osdWidth\n    readonly property real widgetWidth: Appearance.sizes.mediaControlsWidth\n    readonly property real widgetHeight: Appearance.sizes.mediaControlsHeight\n    property real popupRounding: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1\n    property list<real> visualizerPoints: []\n\n    function filterDuplicatePlayers(players) {\n        let filtered = [];\n        let used = new Set();\n\n        for (let i = 0; i < players.length; ++i) {\n            if (used.has(i))\n                continue;\n            let p1 = players[i];\n            let group = [i];\n\n            // Find duplicates by trackTitle prefix\n            for (let j = i + 1; j < players.length; ++j) {\n                let p2 = players[j];\n                if (p1.trackTitle && p2.trackTitle && (p1.trackTitle.includes(p2.trackTitle) || p2.trackTitle.includes(p1.trackTitle)) || (p1.position - p2.position <= 2 && p1.length - p2.length <= 2)) {\n                    group.push(j);\n                }\n            }\n\n            // Pick the one with non-empty trackArtUrl, or fallback to the first\n            let chosenIdx = group.find(idx => players[idx].trackArtUrl && players[idx].trackArtUrl.length > 0);\n            if (chosenIdx === undefined)\n                chosenIdx = group[0];\n\n            filtered.push(players[chosenIdx]);\n            group.forEach(idx => used.add(idx));\n        }\n        return filtered;\n    }\n\n    Process {\n        id: cavaProc\n        running: mediaControlsLoader.active\n        onRunningChanged: {\n            if (!cavaProc.running) {\n                root.visualizerPoints = [];\n            }\n        }\n        command: [\"cava\", \"-p\", `${FileUtils.trimFileProtocol(Directories.scriptPath)}/cava/raw_output_config.txt`]\n        stdout: SplitParser {\n            onRead: data => {\n                // Parse `;`-separated values into the visualizerPoints array\n                let points = data.split(\";\").map(p => parseFloat(p.trim())).filter(p => !isNaN(p));\n                root.visualizerPoints = points;\n            }\n        }\n    }\n\n    Loader {\n        id: mediaControlsLoader\n        active: GlobalStates.mediaControlsOpen\n        onActiveChanged: {\n            if (!mediaControlsLoader.active && root.realPlayers.length === 0) {\n                GlobalStates.mediaControlsOpen = false;\n            }\n        }\n\n        sourceComponent: PanelWindow {\n            id: panelWindow\n            visible: true\n\n            exclusionMode: ExclusionMode.Ignore\n            exclusiveZone: 0\n            implicitWidth: root.widgetWidth\n            implicitHeight: playerColumnLayout.implicitHeight\n            color: \"transparent\"\n            WlrLayershell.namespace: \"quickshell:mediaControls\"\n\n            anchors {\n                top: !Config.options.bar.bottom || Config.options.bar.vertical\n                bottom: Config.options.bar.bottom && !Config.options.bar.vertical\n                left: !(Config.options.bar.vertical && Config.options.bar.bottom)\n                right: Config.options.bar.vertical && Config.options.bar.bottom\n            }\n            margins {\n                top: Config.options.bar.vertical ? ((panelWindow.screen.height / 2) - widgetHeight * 1.5) : Appearance.sizes.barHeight\n                bottom: Appearance.sizes.barHeight\n                left: Config.options.bar.vertical ? Appearance.sizes.barHeight : ((panelWindow.screen.width / 2) - (osdWidth / 2) - widgetWidth)\n                right: Appearance.sizes.barHeight\n            }\n\n            mask: Region {\n                item: playerColumnLayout\n            }\n\n            Component.onCompleted: {\n                GlobalFocusGrab.addDismissable(panelWindow);\n            }\n            Component.onDestruction: {\n                GlobalFocusGrab.removeDismissable(panelWindow);\n            }\n            Connections {\n                target: GlobalFocusGrab\n                function onDismissed() {\n                    GlobalStates.mediaControlsOpen = false;\n                }\n            }\n\n            ColumnLayout {\n                id: playerColumnLayout\n                anchors.fill: parent\n                spacing: -Appearance.sizes.elevationMargin // Shadow overlap okay\n\n                Repeater {\n                    model: ScriptModel {\n                        values: root.meaningfulPlayers\n                    }\n                    delegate: PlayerControl {\n                        required property MprisPlayer modelData\n                        player: modelData\n                        visualizerPoints: root.visualizerPoints\n                        implicitWidth: root.widgetWidth\n                        implicitHeight: root.widgetHeight\n                        radius: root.popupRounding\n                    }\n                }\n\n                Item {\n                    // No player placeholder\n                    Layout.alignment: {\n                        if (panelWindow.anchors.left)\n                            return Qt.AlignLeft;\n                        if (panelWindow.anchors.right)\n                            return Qt.AlignRight;\n                        return Qt.AlignHCenter;\n                    }\n                    Layout.leftMargin: Appearance.sizes.hyprlandGapsOut\n                    Layout.rightMargin: Appearance.sizes.hyprlandGapsOut\n                    visible: root.meaningfulPlayers.length === 0\n                    implicitWidth: placeholderBackground.implicitWidth + Appearance.sizes.elevationMargin\n                    implicitHeight: placeholderBackground.implicitHeight + Appearance.sizes.elevationMargin\n\n                    StyledRectangularShadow {\n                        target: placeholderBackground\n                    }\n\n                    Rectangle {\n                        id: placeholderBackground\n                        anchors.centerIn: parent\n                        color: Appearance.colors.colLayer0\n                        radius: root.popupRounding\n                        property real padding: 20\n                        implicitWidth: placeholderLayout.implicitWidth + padding * 2\n                        implicitHeight: placeholderLayout.implicitHeight + padding * 2\n\n                        ColumnLayout {\n                            id: placeholderLayout\n                            anchors.centerIn: parent\n\n                            StyledText {\n                                text: Translation.tr(\"No active player\")\n                                font.pixelSize: Appearance.font.pixelSize.large\n                            }\n                            StyledText {\n                                color: Appearance.colors.colSubtext\n                                text: Translation.tr(\"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\")\n                                font.pixelSize: Appearance.font.pixelSize.small\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"mediaControls\"\n\n        function toggle(): void {\n            mediaControlsLoader.active = !mediaControlsLoader.active;\n            if (mediaControlsLoader.active)\n                Notifications.timeoutAll();\n        }\n\n        function close(): void {\n            mediaControlsLoader.active = false;\n        }\n\n        function open(): void {\n            mediaControlsLoader.active = true;\n            Notifications.timeoutAll();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"mediaControlsToggle\"\n        description: \"Toggles media controls on press\"\n\n        onPressed: {\n            GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"mediaControlsOpen\"\n        description: \"Opens media controls on press\"\n\n        onPressed: {\n            GlobalStates.mediaControlsOpen = true;\n        }\n    }\n    GlobalShortcut {\n        name: \"mediaControlsClose\"\n        description: \"Closes media controls on press\"\n\n        onPressed: {\n            GlobalStates.mediaControlsOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/mediaControls/PlayerControl.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.widgets\nimport qs.services\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Effects\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Services.Mpris\n\nItem { // Player instance\n    id: root\n    required property MprisPlayer player\n    property var artUrl: player?.trackArtUrl\n    property string artDownloadLocation: Directories.coverArt\n    property string artFileName: Qt.md5(artUrl)\n    property string artFilePath: `${artDownloadLocation}/${artFileName}`\n    property color artDominantColor: ColorUtils.mix((colorQuantizer?.colors[0] ?? Appearance.colors.colPrimary), Appearance.colors.colPrimaryContainer, 0.8) || Appearance.m3colors.m3secondaryContainer\n    property bool downloaded: false\n    property list<real> visualizerPoints: []\n    property real maxVisualizerValue: 1000 // Max value in the data points\n    property int visualizerSmoothing: 2 // Number of points to average for smoothing\n    property real radius\n\n    property string displayedArtFilePath: root.downloaded ? Qt.resolvedUrl(artFilePath) : \"\"\n\n    component TrackChangeButton: RippleButton {\n        implicitWidth: 24\n        implicitHeight: 24\n\n        property var iconName\n        colBackground: ColorUtils.transparentize(blendedColors.colSecondaryContainer, 1)\n        colBackgroundHover: blendedColors.colSecondaryContainerHover\n        colRipple: blendedColors.colSecondaryContainerActive\n\n        contentItem: MaterialSymbol {\n            iconSize: Appearance.font.pixelSize.huge\n            fill: 1\n            horizontalAlignment: Text.AlignHCenter\n            color: blendedColors.colOnSecondaryContainer\n            text: iconName\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n        }\n    }\n\n    Timer { // Force update for revision\n        running: root.player?.playbackState == MprisPlaybackState.Playing\n        interval: Config.options.resources.updateInterval\n        repeat: true\n        onTriggered: {\n            root.player.positionChanged()\n        }\n    }\n\n    onArtFilePathChanged: {\n        if (root.artUrl.length == 0) {\n            root.artDominantColor = Appearance.m3colors.m3secondaryContainer\n            return;\n        }\n\n        // Binding does not work in Process\n        coverArtDownloader.targetFile = root.artUrl \n        coverArtDownloader.artFilePath = root.artFilePath\n        // Download\n        root.downloaded = false\n        coverArtDownloader.running = true\n    }\n\n    Process { // Cover art downloader\n        id: coverArtDownloader\n        property string targetFile: root.artUrl\n        property string artFilePath: root.artFilePath\n        command: [ \"bash\", \"-c\", `[ -f ${artFilePath} ] || curl -4 -sSL '${targetFile}' -o '${artFilePath}'` ]\n        onExited: (exitCode, exitStatus) => {\n            root.downloaded = true\n        }\n    }\n\n    ColorQuantizer {\n        id: colorQuantizer\n        source: root.displayedArtFilePath\n        depth: 0 // 2^0 = 1 color\n        rescaleSize: 1 // Rescale to 1x1 pixel for faster processing\n    }\n\n    property QtObject blendedColors: AdaptedMaterialScheme {\n        color: artDominantColor\n    }\n\n    StyledRectangularShadow {\n        target: background\n    }\n    Rectangle { // Background\n        id: background\n        anchors.fill: parent\n        anchors.margins: Appearance.sizes.elevationMargin\n        color: ColorUtils.applyAlpha(blendedColors.colLayer0, 1)\n        radius: root.radius\n\n        layer.enabled: true\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                width: background.width\n                height: background.height\n                radius: background.radius\n            }\n        }\n\n        StyledImage {\n            id: blurredArt\n            anchors.fill: parent\n            source: root.displayedArtFilePath\n            fillMode: Image.PreserveAspectCrop\n            cache: false\n            antialiasing: true\n            asynchronous: true\n\n            layer.enabled: true\n            layer.effect: StyledBlurEffect {\n                source: blurredArt\n            }\n\n            Rectangle {\n                anchors.fill: parent\n                color: ColorUtils.transparentize(blendedColors.colLayer0, 0.3)\n                radius: root.radius\n            }\n        }\n\n        WaveVisualizer {\n            id: visualizerCanvas\n            anchors.fill: parent\n            live: root.player?.isPlaying\n            points: root.visualizerPoints\n            maxVisualizerValue: root.maxVisualizerValue\n            smoothing: root.visualizerSmoothing\n            color: blendedColors.colPrimary\n        }\n\n        RowLayout {\n            anchors.fill: parent\n            anchors.margins: 13\n            spacing: 15\n\n            Rectangle { // Art background\n                id: artBackground\n                Layout.fillHeight: true\n                implicitWidth: height\n                radius: Appearance.rounding.verysmall\n                color: ColorUtils.transparentize(blendedColors.colLayer1, 0.5)\n\n                layer.enabled: true\n                layer.effect: OpacityMask {\n                    maskSource: Rectangle {\n                        width: artBackground.width\n                        height: artBackground.height\n                        radius: artBackground.radius\n                    }\n                }\n\n                StyledImage { // Art image\n                    id: mediaArt\n                    property int size: parent.height\n                    anchors.fill: parent\n\n                    source: root.displayedArtFilePath\n                    fillMode: Image.PreserveAspectCrop\n                    cache: false\n                    antialiasing: true\n\n                    width: size\n                    height: size\n                }\n            }\n\n            ColumnLayout { // Info & controls\n                Layout.fillHeight: true\n                spacing: 2\n\n                StyledText {\n                    id: trackTitle\n                    Layout.fillWidth: true\n                    font.pixelSize: Appearance.font.pixelSize.large\n                    color: blendedColors.colOnLayer0\n                    elide: Text.ElideRight\n                    text: StringUtils.cleanMusicTitle(root.player?.trackTitle) || \"Untitled\"\n                    animateChange: true\n                    animationDistanceX: 6\n                    animationDistanceY: 0\n                }\n                StyledText {\n                    id: trackArtist\n                    Layout.fillWidth: true\n                    font.pixelSize: Appearance.font.pixelSize.smaller\n                    color: blendedColors.colSubtext\n                    elide: Text.ElideRight\n                    text: root.player?.trackArtist\n                    animateChange: true\n                    animationDistanceX: 6\n                    animationDistanceY: 0\n                }\n                Item { Layout.fillHeight: true }\n                Item {\n                    Layout.fillWidth: true\n                    implicitHeight: trackTime.implicitHeight + sliderRow.implicitHeight\n\n                    StyledText {\n                        id: trackTime\n                        anchors.bottom: sliderRow.top\n                        anchors.bottomMargin: 5\n                        anchors.left: parent.left\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        color: blendedColors.colSubtext\n                        elide: Text.ElideRight\n                        text: `${StringUtils.friendlyTimeForSeconds(root.player?.position)} / ${StringUtils.friendlyTimeForSeconds(root.player?.length)}`\n                    }\n                    RowLayout {\n                        id: sliderRow\n                        anchors {\n                            bottom: parent.bottom\n                            left: parent.left\n                            right: parent.right\n                        }\n                        TrackChangeButton {\n                            iconName: \"skip_previous\"\n                            downAction: () => root.player?.previous()\n                        }\n                        Item {\n                            id: progressBarContainer\n                            Layout.fillWidth: true\n                            implicitHeight: Math.max(sliderLoader.implicitHeight, progressBarLoader.implicitHeight)\n\n                            Loader {\n                                id: sliderLoader\n                                anchors.fill: parent\n                                active: root.player?.canSeek ?? false\n                                sourceComponent: StyledSlider { \n                                    configuration: StyledSlider.Configuration.Wavy\n                                    highlightColor: blendedColors.colPrimary\n                                    trackColor: blendedColors.colSecondaryContainer\n                                    handleColor: blendedColors.colPrimary\n                                    value: root.player?.position / root.player?.length\n                                    onMoved: {\n                                        root.player.position = value * root.player.length;\n                                    }\n                                }\n                            }\n\n                            Loader {\n                                id: progressBarLoader\n                                anchors {\n                                    verticalCenter: parent.verticalCenter\n                                    left: parent.left\n                                    right: parent.right\n                                }\n                                active: !(root.player?.canSeek ?? false)\n                                sourceComponent: StyledProgressBar { \n                                    wavy: root.player?.isPlaying\n                                    highlightColor: blendedColors.colPrimary\n                                    trackColor: blendedColors.colSecondaryContainer\n                                    value: root.player?.position / root.player?.length\n                                }\n                            }\n\n                            \n                        }\n                        TrackChangeButton {\n                            iconName: \"skip_next\"\n                            downAction: () => root.player?.next()\n                        }\n                    }\n\n                    RippleButton {\n                        id: playPauseButton\n                        anchors.right: parent.right\n                        anchors.bottom: sliderRow.top\n                        anchors.bottomMargin: 5\n                        property real size: 44\n                        implicitWidth: size\n                        implicitHeight: size\n                        downAction: () => root.player.togglePlaying();\n\n                        buttonRadius: root.player?.isPlaying ? Appearance?.rounding.normal : size / 2\n                        colBackground: root.player?.isPlaying ? blendedColors.colPrimary : blendedColors.colSecondaryContainer\n                        colBackgroundHover: root.player?.isPlaying ? blendedColors.colPrimaryHover : blendedColors.colSecondaryContainerHover\n                        colRipple: root.player?.isPlaying ? blendedColors.colPrimaryActive : blendedColors.colSecondaryContainerActive\n\n                        contentItem: MaterialSymbol {\n                            iconSize: Appearance.font.pixelSize.huge\n                            fill: 1\n                            horizontalAlignment: Text.AlignHCenter\n                            color: root.player?.isPlaying ? blendedColors.colOnPrimary : blendedColors.colOnSecondaryContainer\n                            text: root.player?.isPlaying ? \"pause\" : \"play_arrow\"\n\n                            Behavior on color {\n                                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/notificationPopup/NotificationPopup.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: notificationPopup\n\n    PanelWindow {\n        id: root\n        visible: (Notifications.popupList.length > 0) && !GlobalStates.screenLocked\n        screen: Quickshell.screens.find(s => Config.options.notifications.forceMonitor.enable ? s.name === Config.options.notifications.forceMonitor.name : s.name === Hyprland.focusedMonitor?.name) ?? null\n\n        WlrLayershell.namespace: \"quickshell:notificationPopup\"\n        WlrLayershell.layer: WlrLayer.Overlay\n        exclusiveZone: 0\n\n        anchors {\n            top: true\n            right: true\n            bottom: true\n        }\n\n        mask: Region {\n            item: listview.contentItem\n        }\n\n        color: \"transparent\"\n        implicitWidth: Appearance.sizes.notificationPopupWidth\n\n        NotificationListView {\n            id: listview\n            anchors {\n                top: parent.top\n                bottom: parent.bottom\n                right: parent.right\n                rightMargin: 4\n                topMargin: 4\n            }\n            implicitWidth: parent.width - Appearance.sizes.elevationMargin * 2\n            popup: true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenDisplay/OnScreenDisplay.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n    property string protectionMessage: \"\"\n    property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)\n\n    property string currentIndicator: \"volume\"\n    property var indicators: [\n        {\n            id: \"volume\",\n            sourceUrl: \"indicators/VolumeIndicator.qml\"\n        },\n        {\n            id: \"brightness\",\n            sourceUrl: \"indicators/BrightnessIndicator.qml\"\n        },\n        {\n            id: \"gamma\",\n            sourceUrl: \"indicators/GammaIndicator.qml\"\n        },\n    ]\n\n    function triggerOsd() {\n        GlobalStates.osdVolumeOpen = true;\n        osdTimeout.restart();\n    }\n\n    Timer {\n        id: osdTimeout\n        interval: Config.options.osd.timeout\n        repeat: false\n        running: false\n        onTriggered: {\n            GlobalStates.osdVolumeOpen = false;\n            root.protectionMessage = \"\";\n        }\n    }\n\n    Connections {\n        target: Brightness\n        function onBrightnessChanged() {\n            root.protectionMessage = \"\";\n            root.currentIndicator = \"brightness\";\n            root.triggerOsd();\n        }\n    }\n\n    Connections {\n        target: Hyprsunset\n        function onGammaChangeAttempt() {\n            root.protectionMessage = \"\";\n            root.currentIndicator = \"gamma\";\n            root.triggerOsd();\n        }\n    }\n\n    Connections {\n        // Listen to volume changes\n        target: Audio.sink?.audio ?? null\n        function onVolumeChanged() {\n            if (!Audio.ready)\n                return;\n            root.currentIndicator = \"volume\";\n            root.triggerOsd();\n        }\n        function onMutedChanged() {\n            if (!Audio.ready)\n                return;\n            root.currentIndicator = \"volume\";\n            root.triggerOsd();\n        }\n    }\n\n    Connections {\n        // Listen to protection triggers\n        target: Audio\n        function onSinkProtectionTriggered(reason) {\n            root.protectionMessage = reason;\n            root.currentIndicator = \"volume\";\n            root.triggerOsd();\n        }\n    }\n\n    Loader {\n        id: osdLoader\n        active: GlobalStates.osdVolumeOpen\n\n        sourceComponent: PanelWindow {\n            id: osdRoot\n            color: \"transparent\"\n\n            Connections {\n                target: root\n                function onFocusedScreenChanged() {\n                    osdRoot.screen = root.focusedScreen;\n                }\n            }\n\n            WlrLayershell.namespace: \"quickshell:onScreenDisplay\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            anchors {\n                top: !Config.options.bar.bottom\n                bottom: Config.options.bar.bottom\n            }\n            mask: Region {\n                item: osdValuesWrapper\n            }\n\n            exclusionMode: ExclusionMode.Ignore\n            exclusiveZone: 0\n            margins {\n                top: Appearance.sizes.barHeight\n                bottom: Appearance.sizes.barHeight\n            }\n\n            implicitWidth: columnLayout.implicitWidth\n            implicitHeight: columnLayout.implicitHeight\n            visible: osdLoader.active\n\n            ColumnLayout {\n                id: columnLayout\n                anchors.horizontalCenter: parent.horizontalCenter\n\n                Item {\n                    id: osdValuesWrapper\n                    // Extra space for shadow\n                    implicitHeight: contentColumnLayout.implicitHeight\n                    implicitWidth: contentColumnLayout.implicitWidth\n                    clip: true\n\n                    MouseArea {\n                        anchors.fill: parent\n                        hoverEnabled: true\n                        onEntered: GlobalStates.osdVolumeOpen = false\n                    }\n\n                    Column {\n                        id: contentColumnLayout\n                        anchors {\n                            top: parent.top\n                            left: parent.left\n                            right: parent.right\n                        }\n                        spacing: 0\n\n                        Loader {\n                            id: osdIndicatorLoader\n                            source: root.indicators.find(i => i.id === root.currentIndicator)?.sourceUrl\n                        }\n\n                        Item {\n                            id: protectionMessageWrapper\n                            anchors.horizontalCenter: parent.horizontalCenter\n                            implicitHeight: protectionMessageBackground.implicitHeight\n                            implicitWidth: protectionMessageBackground.implicitWidth\n                            opacity: root.protectionMessage !== \"\" ? 1 : 0\n\n                            StyledRectangularShadow {\n                                target: protectionMessageBackground\n                            }\n                            Rectangle {\n                                id: protectionMessageBackground\n                                anchors.centerIn: parent\n                                color: Appearance.m3colors.m3error\n                                property real padding: 10\n                                implicitHeight: protectionMessageRowLayout.implicitHeight + padding * 2\n                                implicitWidth: protectionMessageRowLayout.implicitWidth + padding * 2\n                                radius: Appearance.rounding.normal\n\n                                RowLayout {\n                                    id: protectionMessageRowLayout\n                                    anchors.centerIn: parent\n                                    MaterialSymbol {\n                                        id: protectionMessageIcon\n                                        text: \"dangerous\"\n                                        iconSize: Appearance.font.pixelSize.hugeass\n                                        color: Appearance.m3colors.m3onError\n                                    }\n                                    StyledText {\n                                        id: protectionMessageTextWidget\n                                        horizontalAlignment: Text.AlignHCenter\n                                        color: Appearance.m3colors.m3onError\n                                        wrapMode: Text.Wrap\n                                        text: root.protectionMessage\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"osdVolume\"\n\n        function trigger() {\n            root.triggerOsd();\n        }\n\n        function hide() {\n            GlobalStates.osdVolumeOpen = false;\n        }\n\n        function toggle() {\n            GlobalStates.osdVolumeOpen = !GlobalStates.osdVolumeOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"osdVolumeTrigger\"\n        description: \"Triggers volume OSD on press\"\n\n        onPressed: {\n            root.triggerOsd();\n        }\n    }\n    GlobalShortcut {\n        name: \"osdVolumeHide\"\n        description: \"Hides volume OSD on press\"\n\n        onPressed: {\n            GlobalStates.osdVolumeOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenDisplay/OsdValueIndicator.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell.Widgets\n\nItem {\n    id: root\n    required property real value\n    required property string icon\n    required property string name\n    property bool rotateIcon: false\n    property bool scaleIcon: false\n    property alias from: valueProgressBar.from\n    property alias to: valueProgressBar.to\n\n    property real valueIndicatorVerticalPadding: 9\n    property real valueIndicatorLeftPadding: 10\n    property real valueIndicatorRightPadding: 20 // An icon is circle ish, a column isn't, hence the extra padding\n\n    implicitWidth: Appearance.sizes.osdWidth + 2 * Appearance.sizes.elevationMargin\n    implicitHeight: valueIndicator.implicitHeight + 2 * Appearance.sizes.elevationMargin\n\n    StyledRectangularShadow {\n        target: valueIndicator\n    }\n    Rectangle {\n        id: valueIndicator\n        anchors {\n            fill: parent\n            margins: Appearance.sizes.elevationMargin\n        }\n        radius: Appearance.rounding.full\n        color: Appearance.colors.colLayer0\n\n        implicitWidth: valueRow.implicitWidth\n        implicitHeight: valueRow.implicitHeight\n\n        RowLayout { // Icon on the left, stuff on the right\n            id: valueRow\n            Layout.margins: 10\n            anchors.fill: parent\n            spacing: 10\n\n            Item {\n                implicitWidth: 30\n                implicitHeight: 30\n                Layout.alignment: Qt.AlignVCenter\n                Layout.leftMargin: valueIndicatorLeftPadding\n                Layout.topMargin: valueIndicatorVerticalPadding\n                Layout.bottomMargin: valueIndicatorVerticalPadding\n\n                MaterialSymbol { // Icon\n                    anchors {\n                        centerIn: parent\n                        alignWhenCentered: !root.rotateIcon\n                    }\n                    color: Appearance.colors.colOnLayer0\n                    renderType: Text.QtRendering\n\n                    text: root.icon\n                    iconSize: 20 + 10 * (root.scaleIcon ? value : 1)\n                    rotation: 180 * (root.rotateIcon ? value : 0)\n\n                    Behavior on iconSize {\n                        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n                    }\n                    Behavior on rotation {\n                        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n                    }\n                \n                }\n            }\n            ColumnLayout { // Stuff\n                Layout.alignment: Qt.AlignVCenter\n                Layout.rightMargin: valueIndicatorRightPadding\n                spacing: 5\n\n                RowLayout { // Name fill left, value on the right end\n                    Layout.leftMargin: valueProgressBar.height / 2 // Align text with progressbar radius curve's left end\n                    Layout.rightMargin: valueProgressBar.height / 2 // Align text with progressbar radius curve's left end\n\n                    StyledText {\n                        color: Appearance.colors.colOnLayer0\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        Layout.fillWidth: true\n                        text: root.name\n                    }\n\n                    StyledText {\n                        color: Appearance.colors.colOnLayer0\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        Layout.fillWidth: false\n                        text: Math.round(root.value * 100)\n                    }\n                }\n                \n                StyledProgressBar {\n                    id: valueProgressBar\n                    Layout.fillWidth: true\n                    value: root.value\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenDisplay/indicators/BrightnessIndicator.qml",
    "content": "import qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.ii.onScreenDisplay\n\nOsdValueIndicator {\n    id: root\n    property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)\n    property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)\n\n    icon: Hyprsunset.temperatureActive ? \"routine\" : \"light_mode\"\n    rotateIcon: true\n    scaleIcon: true\n    name: Translation.tr(\"Brightness\")\n    value: root.brightnessMonitor?.brightness ?? 50\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenDisplay/indicators/GammaIndicator.qml",
    "content": "import qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.ii.onScreenDisplay\n\nOsdValueIndicator {\n    id: rotateIcon\n\n    icon: \"wb_twilight\"\n    name: Translation.tr(\"Gamma\")\n    from: Hyprsunset.gammaLowerLimit / 100\n    value: Hyprsunset.gamma / 100 ?? 0.5\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenDisplay/indicators/VolumeIndicator.qml",
    "content": "import qs.services\nimport QtQuick\nimport qs.modules.ii.onScreenDisplay\n\nOsdValueIndicator {\n    id: osdValues\n    value: Audio.sink?.audio.volume ?? 0\n    icon: Audio.sink?.audio.muted ? \"volume_off\" : \"volume_up\"\n    name: Translation.tr(\"Volume\")\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OnScreenKeyboard.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope { // Scope\n    id: root\n    property bool pinned: Config.options?.osk.pinnedOnStartup ?? false\n\n    component OskControlButton: GroupButton { // Pin button\n        baseWidth: 40\n        baseHeight: 40\n        clickedWidth: baseWidth\n        clickedHeight: baseHeight + 10\n        buttonRadius: Appearance.rounding.normal\n    }\n\n    Loader {\n        id: oskLoader\n        active: GlobalStates.oskOpen\n        onActiveChanged: {\n            if (!oskLoader.active) {\n                Ydotool.releaseAllKeys();\n            }\n        }\n        \n        sourceComponent: PanelWindow { // Window\n            id: oskRoot\n            visible: oskLoader.active && !GlobalStates.screenLocked\n\n            anchors {\n                bottom: true\n                left: true\n                right: true\n            }\n\n            function hide() {\n                GlobalStates.oskOpen = false\n            }\n            exclusiveZone: root.pinned ? implicitHeight - Appearance.sizes.hyprlandGapsOut : 0\n            implicitWidth: oskBackground.width + Appearance.sizes.elevationMargin * 2\n            implicitHeight: oskBackground.height + Appearance.sizes.elevationMargin * 2\n            WlrLayershell.namespace: \"quickshell:osk\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            // Hyprland 0.49: Focus is always exclusive and setting this breaks mouse focus grab\n            // WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive\n            color: \"transparent\"\n\n            mask: Region {\n                item: oskBackground\n            }\n\n            // Make it usable with other panels\n            Component.onCompleted: {\n                GlobalFocusGrab.addPersistent(oskRoot);\n            }\n            Component.onDestruction: {\n                GlobalFocusGrab.removePersistent(oskRoot);\n            }\n\n            // Background\n            StyledRectangularShadow {\n                target: oskBackground\n            }\n            Rectangle {\n                id: oskBackground\n                anchors.centerIn: parent\n                color: Appearance.colors.colLayer0\n                radius: Appearance.rounding.windowRounding\n                property real padding: 10\n                implicitWidth: oskRowLayout.implicitWidth + padding * 2\n                implicitHeight: oskRowLayout.implicitHeight + padding * 2\n\n                Keys.onPressed: (event) => { // Esc to close\n                    if (event.key === Qt.Key_Escape) {\n                        oskRoot.hide()\n                    }\n                }\n\n                RowLayout {\n                    id: oskRowLayout\n                    anchors.centerIn: parent\n                    spacing: 5\n                    VerticalButtonGroup {\n                        OskControlButton { // Pin button\n                            toggled: root.pinned\n                            downAction: () => root.pinned = !root.pinned\n                            contentItem: MaterialSymbol {\n                                text: \"keep\"\n                                horizontalAlignment: Text.AlignHCenter\n                                iconSize: Appearance.font.pixelSize.larger\n                                color: root.pinned ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0\n                            }\n                        }\n                        OskControlButton {\n                            onClicked: () => {\n                                oskRoot.hide()\n                            }\n                            contentItem: MaterialSymbol {\n                                horizontalAlignment: Text.AlignHCenter\n                                text: \"keyboard_hide\"\n                                iconSize: Appearance.font.pixelSize.larger\n                            }\n                        }\n                    }\n                    Rectangle {\n                        Layout.topMargin: 20\n                        Layout.bottomMargin: 20\n                        Layout.fillHeight: true\n                        implicitWidth: 1\n                        color: Appearance.colors.colOutlineVariant\n                    }\n                    OskContent {\n                        id: oskContent\n                        Layout.fillWidth: true\n                    }\n                }\n            }\n\n        }\n    }\n\n    IpcHandler {\n        target: \"osk\"\n\n        function toggle(): void {\n            GlobalStates.oskOpen = !GlobalStates.oskOpen;\n        }\n\n        function close(): void {\n            GlobalStates.oskOpen = false\n        }\n\n        function open(): void {\n            GlobalStates.oskOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"oskToggle\"\n        description: \"Toggles on screen keyboard on press\"\n\n        onPressed: {\n            GlobalStates.oskOpen = !GlobalStates.oskOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"oskOpen\"\n        description: \"Opens on screen keyboard on press\"\n\n        onPressed: {\n            GlobalStates.oskOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"oskClose\"\n        description: \"Closes on screen keyboard on press\"\n\n        onPressed: {\n            GlobalStates.oskOpen = false\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OskContent.qml",
    "content": "import qs.modules.common\nimport \"layouts.js\" as Layouts\nimport QtQuick\nimport QtQuick.Layouts\n\nItem {\n    id: root    \n    property var layouts: Layouts.byName\n    property var activeLayoutName: (layouts.hasOwnProperty(Config.options?.osk.layout)) \n        ? Config.options?.osk.layout \n        : Layouts.defaultLayout\n    property var currentLayout: layouts[activeLayoutName]\n\n    implicitWidth: keyRows.implicitWidth\n    implicitHeight: keyRows.implicitHeight\n\n    ColumnLayout {\n        id: keyRows\n        anchors.fill: parent\n        spacing: 5\n\n        Repeater {\n            model: root.currentLayout.keys\n\n            delegate: RowLayout {\n                id: keyRow\n                required property var modelData\n                spacing: 5\n                \n                Repeater {\n                    model: modelData\n                    // A normal key looks like this: {label: \"a\", labelShift: \"A\", shape: \"normal\", keycode: 30, type: \"normal\"}\n                    delegate: OskKey { \n                        required property var modelData\n                        keyData: modelData\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/OskKey.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton {\n    id: root\n    property var keyData\n    property string key: keyData.label\n    property string type: keyData.keytype\n    property var keycode: keyData.keycode\n    property string shape: keyData.shape\n    property bool isShift: Ydotool.shiftKeys.includes(keycode)\n    property bool isBackspace: (key.toLowerCase() == \"backspace\")\n    property bool isEnter: (key.toLowerCase() == \"enter\" || key.toLowerCase() == \"return\")\n    property real baseWidth: 45\n    property real baseHeight: 45\n    property var widthMultiplier: ({\n        \"normal\": 1,\n        \"fn\": 1,\n        \"tab\": 1.6,\n        \"caps\": 1.9,\n        \"shift\": 2.5,\n        \"control\": 1.3\n    })\n    property var heightMultiplier: ({\n        \"normal\": 1,\n        \"fn\": 0.7,\n        \"tab\": 1,\n        \"caps\": 1,\n        \"shift\": 1,\n        \"control\": 1\n    })\n    toggled: isShift ? Ydotool.shiftMode : false\n\n    enabled: shape != \"empty\"\n    colBackground: shape == \"empty\" ? ColorUtils.transparentize(Appearance.colors.colLayer1) : Appearance.colors.colLayer1\n    buttonRadius: Appearance.rounding.small\n    implicitWidth: baseWidth * widthMultiplier[shape] || baseWidth\n    implicitHeight: baseHeight * heightMultiplier[shape] || baseHeight\n    Layout.fillWidth: shape == \"space\" || shape == \"expand\"\n\n    Connections {\n        target: Ydotool\n        enabled: isShift\n        function onShiftModeChanged() {\n            if (Ydotool.shiftMode == 0) {\n                capsLockTimer.hasStarted = false;\n            }\n        }\n    }\n\n    Timer {\n        id: capsLockTimer\n        property bool hasStarted: false\n        property bool canCaps: false\n        interval: 300\n        function startWaiting() {\n            hasStarted = true;\n            canCaps = true;\n            start();\n        }\n        onTriggered: {\n            canCaps = false;\n        }\n    }\n\n    downAction: () => {\n        Ydotool.press(root.keycode);\n        if (isShift && Ydotool.shiftMode == 0) Ydotool.shiftMode = 1;\n    }\n    releaseAction: () => {\n        if (root.type == \"normal\") {\n            Ydotool.release(root.keycode);\n            if (Ydotool.shiftMode == 1) {\n                Ydotool.releaseShiftKeys()\n            }\n        } else if (isShift) {\n            if (Ydotool.shiftMode == 1) {\n                if (!capsLockTimer.hasStarted) {\n                    capsLockTimer.startWaiting();\n                } else {\n                    if (capsLockTimer.canCaps) {\n                        Ydotool.shiftMode = 2; // Caps lock mode\n                    } else {\n                        Ydotool.releaseShiftKeys()\n                    }\n                }\n            } else if (Ydotool.shiftMode == 2) {\n                Ydotool.releaseShiftKeys();\n            }\n        } else if (root.type == \"modkey\") {\n            root.toggled = !root.toggled;\n            if (!root.toggled) {\n                if (isShift) {\n                    Ydotool.releaseShiftKeys();\n                } else { \n                    Ydotool.release(root.keycode);\n                }\n            }\n        }\n\n    }\n\n    contentItem: StyledText {\n        id: keyText\n        anchors.fill: parent\n        font.family: (isBackspace || isEnter) ? Appearance.font.family.iconMaterial : Appearance.font.family.main\n        font.pixelSize: root.shape == \"fn\" ? Appearance.font.pixelSize.small : \n            (isBackspace || isEnter) ? Appearance.font.pixelSize.huge :\n            Appearance.font.pixelSize.large\n        horizontalAlignment: Text.AlignHCenter\n        color: root.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1\n        text: root.isBackspace ? \"backspace\" : root.isEnter ? \"subdirectory_arrow_left\" :\n            Ydotool.shiftMode == 2 ? (root.keyData.labelCaps || root.keyData.labelShift || root.keyData.label) :\n            Ydotool.shiftMode == 1 ? (root.keyData.labelShift || root.keyData.label) : \n            root.keyData.label\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/onScreenKeyboard/layouts.js",
    "content": "// We're going to use ydotool\n// See /usr/include/linux/input-event-codes.h for keycodes\n\nconst defaultLayout = \"English (US)\";\nconst byName = {\n    \"English (US)\": {\n        name_short: \"US\",\n        description: \"QWERTY - Full\",\n        comment: \"Like physical keyboard\",\n        // A key looks like this: { k: \"a\", ks: \"A\", t: \"normal\" } (key, key-shift, type)\n        // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand\n        // keys: [\n        //     [{ k: \"Esc\", t: \"fn\" }, { k: \"F1\", t: \"fn\" }, { k: \"F2\", t: \"fn\" }, { k: \"F3\", t: \"fn\" }, { k: \"F4\", t: \"fn\" }, { k: \"F5\", t: \"fn\" }, { k: \"F6\", t: \"fn\" }, { k: \"F7\", t: \"fn\" }, { k: \"F8\", t: \"fn\" }, { k: \"F9\", t: \"fn\" }, { k: \"F10\", t: \"fn\" }, { k: \"F11\", t: \"fn\" }, { k: \"F12\", t: \"fn\" }, { k: \"PrtSc\", t: \"fn\" }, { k: \"Del\", t: \"fn\" }],\n        //     [{ k: \"`\", ks: \"~\", t: \"normal\" }, { k: \"1\", ks: \"!\", t: \"normal\" }, { k: \"2\", ks: \"@\", t: \"normal\" }, { k: \"3\", ks: \"#\", t: \"normal\" }, { k: \"4\", ks: \"$\", t: \"normal\" }, { k: \"5\", ks: \"%\", t: \"normal\" }, { k: \"6\", ks: \"^\", t: \"normal\" }, { k: \"7\", ks: \"&\", t: \"normal\" }, { k: \"8\", ks: \"*\", t: \"normal\" }, { k: \"9\", ks: \"(\", t: \"normal\" }, { k: \"0\", ks: \")\", t: \"normal\" }, { k: \"-\", ks: \"_\", t: \"normal\" }, { k: \"=\", ks: \"+\", t: \"normal\" }, { k: \"Backspace\", t: \"shift\" }],\n        //     [{ k: \"Tab\", t: \"tab\" }, { k: \"q\", ks: \"Q\", t: \"normal\" }, { k: \"w\", ks: \"W\", t: \"normal\" }, { k: \"e\", ks: \"E\", t: \"normal\" }, { k: \"r\", ks: \"R\", t: \"normal\" }, { k: \"t\", ks: \"T\", t: \"normal\" }, { k: \"y\", ks: \"Y\", t: \"normal\" }, { k: \"u\", ks: \"U\", t: \"normal\" }, { k: \"i\", ks: \"I\", t: \"normal\" }, { k: \"o\", ks: \"O\", t: \"normal\" }, { k: \"p\", ks: \"P\", t: \"normal\" }, { k: \"[\", ks: \"{\", t: \"normal\" }, { k: \"]\", ks: \"}\", t: \"normal\" }, { k: \"\\\\\", ks: \"|\", t: \"expand\" }],\n        //     [{ k: \"Caps\", t: \"caps\" }, { k: \"a\", ks: \"A\", t: \"normal\" }, { k: \"s\", ks: \"S\", t: \"normal\" }, { k: \"d\", ks: \"D\", t: \"normal\" }, { k: \"f\", ks: \"F\", t: \"normal\" }, { k: \"g\", ks: \"G\", t: \"normal\" }, { k: \"h\", ks: \"H\", t: \"normal\" }, { k: \"j\", ks: \"J\", t: \"normal\" }, { k: \"k\", ks: \"K\", t: \"normal\" }, { k: \"l\", ks: \"L\", t: \"normal\" }, { k: \";\", ks: \":\", t: \"normal\" }, { k: \"'\", ks: '\"', t: \"normal\" }, { k: \"Enter\", t: \"expand\" }],\n        //     [{ k: \"Shift\", t: \"shift\" }, { k: \"z\", ks: \"Z\", t: \"normal\" }, { k: \"x\", ks: \"X\", t: \"normal\" }, { k: \"c\", ks: \"C\", t: \"normal\" }, { k: \"v\", ks: \"V\", t: \"normal\" }, { k: \"b\", ks: \"B\", t: \"normal\" }, { k: \"n\", ks: \"N\", t: \"normal\" }, { k: \"m\", ks: \"M\", t: \"normal\" }, { k: \",\", ks: \"<\", t: \"normal\" }, { k: \".\", ks: \">\", t: \"normal\" }, { k: \"/\", ks: \"?\", t: \"normal\" }, { k: \"Shift\", t: \"expand\" }],\n        //     [{ k: \"Ctrl\", t: \"control\" }, { k: \"Fn\", t: \"normal\" }, { k: \"Win\", t: \"normal\" }, { k: \"Alt\", t: \"normal\" }, { k: \"Space\", t: \"space\" }, { k: \"Alt\", t: \"normal\" }, { k: \"Menu\", t: \"normal\" }, { k: \"Ctrl\", t: \"control\" }]\n        // ]\n        // A normal key looks like this: {label: \"a\", labelShift: \"A\", shape: \"normal\", keycode: 30, type: \"normal\"}\n        // A modkey looks like this: {label: \"Ctrl\", shape: \"control\", keycode: 29, type: \"modkey\"}\n        // key types are: normal, tab, caps, shift, control, fn (normal w/ half height), space, expand\n        keys: [\n            [\n                { keytype: \"normal\", label: \"Esc\", shape: \"fn\", keycode: 1 },\n                { keytype: \"normal\", label: \"F1\", shape: \"fn\", keycode: 59 },\n                { keytype: \"normal\", label: \"F2\", shape: \"fn\", keycode: 60 },\n                { keytype: \"normal\", label: \"F3\", shape: \"fn\", keycode: 61 },\n                { keytype: \"normal\", label: \"F4\", shape: \"fn\", keycode: 62 },\n                { keytype: \"normal\", label: \"F5\", shape: \"fn\", keycode: 63 },\n                { keytype: \"normal\", label: \"F6\", shape: \"fn\", keycode: 64 },\n                { keytype: \"normal\", label: \"F7\", shape: \"fn\", keycode: 65 },\n                { keytype: \"normal\", label: \"F8\", shape: \"fn\", keycode: 66 },\n                { keytype: \"normal\", label: \"F9\", shape: \"fn\", keycode: 67 },\n                { keytype: \"normal\", label: \"F10\", shape: \"fn\", keycode: 68 },\n                { keytype: \"normal\", label: \"F11\", shape: \"fn\", keycode: 87 },\n                { keytype: \"normal\", label: \"F12\", shape: \"fn\", keycode: 88 },\n                { keytype: \"normal\", label: \"PrtSc\", shape: \"fn\", keycode: 99 },\n                { keytype: \"normal\", label: \"Del\", shape: \"fn\", keycode: 111 }\n            ],\n            [\n                { keytype: \"normal\", label: \"`\", labelShift: \"~\", shape: \"normal\", keycode: 41 },\n                { keytype: \"normal\", label: \"1\", labelShift: \"!\", shape: \"normal\", keycode: 2 },\n                { keytype: \"normal\", label: \"2\", labelShift: \"@\", shape: \"normal\", keycode: 3 },\n                { keytype: \"normal\", label: \"3\", labelShift: \"#\", shape: \"normal\", keycode: 4 },\n                { keytype: \"normal\", label: \"4\", labelShift: \"$\", shape: \"normal\", keycode: 5 },\n                { keytype: \"normal\", label: \"5\", labelShift: \"%\", shape: \"normal\", keycode: 6 },\n                { keytype: \"normal\", label: \"6\", labelShift: \"^\", shape: \"normal\", keycode: 7 },\n                { keytype: \"normal\", label: \"7\", labelShift: \"&\", shape: \"normal\", keycode: 8 },\n                { keytype: \"normal\", label: \"8\", labelShift: \"*\", shape: \"normal\", keycode: 9 },\n                { keytype: \"normal\", label: \"9\", labelShift: \"(\", shape: \"normal\", keycode: 10 },\n                { keytype: \"normal\", label: \"0\", labelShift: \")\", shape: \"normal\", keycode: 11 },\n                { keytype: \"normal\", label: \"-\", labelShift: \"_\", shape: \"normal\", keycode: 12 },\n                { keytype: \"normal\", label: \"=\", labelShift: \"+\", shape: \"normal\", keycode: 13 },\n                { keytype: \"normal\", label: \"Backspace\", shape: \"expand\", keycode: 14 }\n            ],\n            [\n                { keytype: \"normal\", label: \"Tab\", shape: \"tab\", keycode: 15 },\n                { keytype: \"normal\", label: \"q\", labelShift: \"Q\", shape: \"normal\", keycode: 16 },\n                { keytype: \"normal\", label: \"w\", labelShift: \"W\", shape: \"normal\", keycode: 17 },\n                { keytype: \"normal\", label: \"e\", labelShift: \"E\", shape: \"normal\", keycode: 18 },\n                { keytype: \"normal\", label: \"r\", labelShift: \"R\", shape: \"normal\", keycode: 19 },\n                { keytype: \"normal\", label: \"t\", labelShift: \"T\", shape: \"normal\", keycode: 20 },\n                { keytype: \"normal\", label: \"y\", labelShift: \"Y\", shape: \"normal\", keycode: 21 },\n                { keytype: \"normal\", label: \"u\", labelShift: \"U\", shape: \"normal\", keycode: 22 },\n                { keytype: \"normal\", label: \"i\", labelShift: \"I\", shape: \"normal\", keycode: 23 },\n                { keytype: \"normal\", label: \"o\", labelShift: \"O\", shape: \"normal\", keycode: 24 },\n                { keytype: \"normal\", label: \"p\", labelShift: \"P\", shape: \"normal\", keycode: 25 },\n                { keytype: \"normal\", label: \"[\", labelShift: \"{\", shape: \"normal\", keycode: 26 },\n                { keytype: \"normal\", label: \"]\", labelShift: \"}\", shape: \"normal\", keycode: 27 },\n                { keytype: \"normal\", label: \"\\\\\", labelShift: \"|\", shape: \"expand\", keycode: 43 }\n            ],\n            [\n                //{ keytype: \"normal\", label: \"Caps\", shape: \"caps\", keycode: 58 }, // not needed as double-pressing shift does that\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                { keytype: \"normal\", label: \"a\", labelShift: \"A\", shape: \"normal\", keycode: 30 },\n                { keytype: \"normal\", label: \"s\", labelShift: \"S\", shape: \"normal\", keycode: 31 },\n                { keytype: \"normal\", label: \"d\", labelShift: \"D\", shape: \"normal\", keycode: 32 },\n                { keytype: \"normal\", label: \"f\", labelShift: \"F\", shape: \"normal\", keycode: 33 },\n                { keytype: \"normal\", label: \"g\", labelShift: \"G\", shape: \"normal\", keycode: 34 },\n                { keytype: \"normal\", label: \"h\", labelShift: \"H\", shape: \"normal\", keycode: 35 },\n                { keytype: \"normal\", label: \"j\", labelShift: \"J\", shape: \"normal\", keycode: 36 },\n                { keytype: \"normal\", label: \"k\", labelShift: \"K\", shape: \"normal\", keycode: 37 },\n                { keytype: \"normal\", label: \"l\", labelShift: \"L\", shape: \"normal\", keycode: 38 },\n                { keytype: \"normal\", label: \";\", labelShift: \":\", shape: \"normal\", keycode: 39 },\n                { keytype: \"normal\", label: \"'\", labelShift: '\"', shape: \"normal\", keycode: 40 },\n                { keytype: \"normal\", label: \"Enter\", shape: \"expand\", keycode: 28 }\n            ],\n            [\n                { keytype: \"modkey\", label: \"Shift\", labelShift: \"Shift\", labelCaps: \"Caps\", shape: \"shift\", keycode: 42 },\n                { keytype: \"normal\", label: \"z\", labelShift: \"Z\", shape: \"normal\", keycode: 44 },\n                { keytype: \"normal\", label: \"x\", labelShift: \"X\", shape: \"normal\", keycode: 45 },\n                { keytype: \"normal\", label: \"c\", labelShift: \"C\", shape: \"normal\", keycode: 46 },\n                { keytype: \"normal\", label: \"v\", labelShift: \"V\", shape: \"normal\", keycode: 47 },\n                { keytype: \"normal\", label: \"b\", labelShift: \"B\", shape: \"normal\", keycode: 48 },\n                { keytype: \"normal\", label: \"n\", labelShift: \"N\", shape: \"normal\", keycode: 49 },\n                { keytype: \"normal\", label: \"m\", labelShift: \"M\", shape: \"normal\", keycode: 50 },\n                { keytype: \"normal\", label: \",\", labelShift: \"<\", shape: \"normal\", keycode: 51 },\n                { keytype: \"normal\", label: \".\", labelShift: \">\", shape: \"normal\", keycode: 52 },\n                { keytype: \"normal\", label: \"/\", labelShift: \"?\", shape: \"normal\", keycode: 53 },\n                { keytype: \"modkey\", label: \"Shift\", labelShift: \"Shift\", labelCaps: \"Caps\", shape: \"expand\", keycode: 54 } // optional\n            ],\n            [\n                { keytype: \"modkey\", label: \"Ctrl\", shape: \"control\", keycode: 29 },\n                // { label: \"Super\", shape: \"normal\", keycode: 125 }, // dangerous\n                { keytype: \"modkey\", label: \"Alt\", shape: \"normal\", keycode: 56 },\n                { keytype: \"normal\", label: \"Space\", shape: \"space\", keycode: 57 },\n                { keytype: \"modkey\", label: \"Alt\", shape: \"normal\", keycode: 100 },\n                // { label: \"Super\", shape: \"normal\", keycode: 126 }, // dangerous\n                { keytype: \"normal\", label: \"Menu\", shape: \"normal\", keycode: 139 },\n                { keytype: \"modkey\", label: \"Ctrl\", shape: \"control\", keycode: 97 }\n            ]\n        ]\n    },\n    \"German\": {\n        name_short: \"DE\",\n        description: \"QWERTZ - Full\",\n        comment: \"Keyboard layout commonly used in German-speaking countries\",\n        keys: [\n            [\n                { keytype: \"normal\", label: \"Esc\", shape: \"fn\", keycode: 1 },\n                { keytype: \"normal\", label: \"F1\", shape: \"fn\", keycode: 59 },\n                { keytype: \"normal\", label: \"F2\", shape: \"fn\", keycode: 60 },\n                { keytype: \"normal\", label: \"F3\", shape: \"fn\", keycode: 61 },\n                { keytype: \"normal\", label: \"F4\", shape: \"fn\", keycode: 62 },\n                { keytype: \"normal\", label: \"F5\", shape: \"fn\", keycode: 63 },\n                { keytype: \"normal\", label: \"F6\", shape: \"fn\", keycode: 64 },\n                { keytype: \"normal\", label: \"F7\", shape: \"fn\", keycode: 65 },\n                { keytype: \"normal\", label: \"F8\", shape: \"fn\", keycode: 66 },\n                { keytype: \"normal\", label: \"F9\", shape: \"fn\", keycode: 67 },\n                { keytype: \"normal\", label: \"F10\", shape: \"fn\", keycode: 68 },\n                { keytype: \"normal\", label: \"F11\", shape: \"fn\", keycode: 87 },\n                { keytype: \"normal\", label: \"F12\", shape: \"fn\", keycode: 88 },\n                { keytype: \"normal\", label: \"Druck\", shape: \"fn\", keycode: 99 },\n                { keytype: \"normal\", label: \"Entf\", shape: \"fn\", keycode: 111 }\n            ],\n            [\n                { keytype: \"normal\", label: \"^\", labelShift: \"°\", labelAlt: \"′\", shape: \"normal\", keycode: 41 },\n                { keytype: \"normal\", label: \"1\", labelShift: \"!\", labelAlt: \"¹\", shape: \"normal\", keycode: 2 },\n                { keytype: \"normal\", label: \"2\", labelShift: \"\\\"\", labelAlt: \"²\", shape: \"normal\", keycode: 3 },\n                { keytype: \"normal\", label: \"3\", labelShift: \"§\", labelAlt: \"³\", shape: \"normal\", keycode: 4 },\n                { keytype: \"normal\", label: \"4\", labelShift: \"$\", labelAlt: \"¼\", shape: \"normal\", keycode: 5 },\n                { keytype: \"normal\", label: \"5\", labelShift: \"%\", labelAlt: \"½\", shape: \"normal\", keycode: 6 },\n                { keytype: \"normal\", label: \"6\", labelShift: \"&\", labelAlt: \"¬\", shape: \"normal\", keycode: 7 },\n                { keytype: \"normal\", label: \"7\", labelShift: \"/\", labelAlt: \"{\", shape: \"normal\", keycode: 8 },\n                { keytype: \"normal\", label: \"8\", labelShift: \"(\", labelAlt: \"[\", shape: \"normal\", keycode: 9 },\n                { keytype: \"normal\", label: \"9\", labelShift: \")\", labelAlt: \"]\", shape: \"normal\", keycode: 10 },\n                { keytype: \"normal\", label: \"0\", labelShift: \"=\", labelAlt: \"}\", shape: \"normal\", keycode: 11 },\n                { keytype: \"normal\", label: \"ß\", labelShift: \"?\", labelAlt: \"\\\\\", shape: \"normal\", keycode: 12 },\n                { keytype: \"normal\", label: \"´\", labelShift: \"`\", labelAlt: \"¸\", shape: \"normal\", keycode: 13 },\n                { keytype: \"normal\", label: \"⟵\", shape: \"expand\", keycode: 14 }\n            ],\n            [\n                { keytype: \"normal\", label: \"Tab ⇆\", shape: \"tab\", keycode: 15 },\n                { keytype: \"normal\", label: \"q\", labelShift: \"Q\", labelAlt: \"@\", shape: \"normal\", keycode: 16 },\n                { keytype: \"normal\", label: \"w\", labelShift: \"W\", labelAlt: \"ſ\", shape: \"normal\", keycode: 17 },\n                { keytype: \"normal\", label: \"e\", labelShift: \"E\", labelAlt: \"€\", shape: \"normal\", keycode: 18 },\n                { keytype: \"normal\", label: \"r\", labelShift: \"R\", labelAlt: \"¶\", shape: \"normal\", keycode: 19 },\n                { keytype: \"normal\", label: \"t\", labelShift: \"T\", labelAlt: \"ŧ\", shape: \"normal\", keycode: 20 },\n                { keytype: \"normal\", label: \"z\", labelShift: \"Z\", labelAlt: \"←\", shape: \"normal\", keycode: 21 },\n                { keytype: \"normal\", label: \"u\", labelShift: \"U\", labelAlt: \"↓\", shape: \"normal\", keycode: 22 },\n                { keytype: \"normal\", label: \"i\", labelShift: \"I\", labelAlt: \"→\", shape: \"normal\", keycode: 23 },\n                { keytype: \"normal\", label: \"o\", labelShift: \"O\", labelAlt: \"ø\", shape: \"normal\", keycode: 24 },\n                { keytype: \"normal\", label: \"p\", labelShift: \"P\", labelAlt: \"þ\", shape: \"normal\", keycode: 25 },\n                { keytype: \"normal\", label: \"ü\", labelShift: \"Ü\", labelAlt: \"¨\", shape: \"normal\", keycode: 26 },\n                { keytype: \"normal\", label: \"+\", labelShift: \"*\", labelAlt: \"~\", shape: \"normal\", keycode: 27 },\n                { keytype: \"normal\", label: \"↵\", shape: \"expand\", keycode: 28 }\n            ],\n            [\n                //{ keytype: \"normal\", label: \"Umschalt ⇩\", shape: \"caps\", keycode: 58 },\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                { keytype: \"normal\", label: \"a\", labelShift: \"A\", labelAlt: \"æ\", shape: \"normal\", keycode: 30 },\n                { keytype: \"normal\", label: \"s\", labelShift: \"S\", labelAlt: \"ſ\", shape: \"normal\", keycode: 31 },\n                { keytype: \"normal\", label: \"d\", labelShift: \"D\", labelAlt: \"ð\", shape: \"normal\", keycode: 32 },\n                { keytype: \"normal\", label: \"f\", labelShift: \"F\", labelAlt: \"đ\", shape: \"normal\", keycode: 33 },\n                { keytype: \"normal\", label: \"g\", labelShift: \"G\", labelAlt: \"ŋ\", shape: \"normal\", keycode: 34 },\n                { keytype: \"normal\", label: \"h\", labelShift: \"H\", labelAlt: \"ħ\", shape: \"normal\", keycode: 35 },\n                { keytype: \"normal\", label: \"j\", labelShift: \"J\", labelAlt: \"\", shape: \"normal\", keycode: 36 },\n                { keytype: \"normal\", label: \"k\", labelShift: \"K\", labelAlt: \"ĸ\", shape: \"normal\", keycode: 37 },\n                { keytype: \"normal\", label: \"l\", labelShift: \"L\", labelAlt: \"ł\", shape: \"normal\", keycode: 38 },\n                { keytype: \"normal\", label: \"ö\", labelShift: \"Ö\", labelAlt: \"˝\", shape: \"normal\", keycode: 39 },\n                { keytype: \"normal\", label: \"ä\", labelShift: 'Ä', labelAlt: \"^\", shape: \"normal\", keycode: 40 },\n                { keytype: \"normal\", label: \"#\", labelShift: '\\'', labelAlt: \"’\", shape: \"normal\", keycode: 43 },\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                //{ keytype: \"normal\", label: \"↵\", shape: \"expand\", keycode: 28 }\n            ],\n            [\n                { keytype: \"modkey\", label: \"Shift\", labelShift: \"Shift ⇧\", labelCaps: \"Locked ⇩\", shape: \"shift\", keycode: 42 },\n                { keytype: \"normal\", label: \"<\", labelShift: \">\", labelAlt: \"|\", shape: \"normal\", keycode: 86 },\n                { keytype: \"normal\", label: \"y\", labelShift: \"Y\", labelAlt: \"»\", shape: \"normal\", keycode: 44 },\n                { keytype: \"normal\", label: \"x\", labelShift: \"X\", labelAlt: \"«\", shape: \"normal\", keycode: 45 },\n                { keytype: \"normal\", label: \"c\", labelShift: \"C\", labelAlt: \"¢\", shape: \"normal\", keycode: 46 },\n                { keytype: \"normal\", label: \"v\", labelShift: \"V\", labelAlt: \"„\", shape: \"normal\", keycode: 47 },\n                { keytype: \"normal\", label: \"b\", labelShift: \"B\", labelAlt: \"“\", shape: \"normal\", keycode: 48 },\n                { keytype: \"normal\", label: \"n\", labelShift: \"N\", labelAlt: \"”\", shape: \"normal\", keycode: 49 },\n                { keytype: \"normal\", label: \"m\", labelShift: \"M\", labelAlt: \"µ\", shape: \"normal\", keycode: 50 },\n                { keytype: \"normal\", label: \",\", labelShift: \";\", labelAlt: \"·\", shape: \"normal\", keycode: 51 },\n                { keytype: \"normal\", label: \".\", labelShift: \":\", labelAlt: \"…\", shape: \"normal\", keycode: 52 },\n                { keytype: \"normal\", label: \"-\", labelShift: \"_\", labelAlt: \"–\", shape: \"normal\", keycode: 53 },\n                { keytype: \"modkey\", label: \"Shift\", labelShift: \"Shift ⇧\", labelCaps: \"Locked ⇩\", shape: \"expand\", keycode: 54 }, // optional\n            ],\n            [\n                { keytype: \"modkey\", label: \"Strg\", shape: \"control\", keycode: 29 },\n                //{ keytype: \"normal\", label: \"\", shape: \"normal\", keycode: 125 }, // dangerous\n                { keytype: \"modkey\", label: \"Alt\", shape: \"normal\", keycode: 56 },\n                { keytype: \"normal\", label: \"Leertaste\", shape: \"space\", keycode: 57 },\n                { keytype: \"modkey\", label: \"Alt Gr\", shape: \"normal\", keycode: 100 },\n                // { label: \"Super\", shape: \"normal\", keycode: 126 }, // dangerous\n                //{ keytype: \"normal\", label: \"Menu\", shape: \"normal\", keycode: 139 }, // doesn't work?\n                { keytype: \"modkey\", label: \"Strg\", shape: \"control\", keycode: 97 },\n                { keytype: \"normal\", label: \"⇦\", shape: \"normal\", keycode: 105 },\n                { keytype: \"normal\", label: \"⇨\", shape: \"normal\", keycode: 106 },\n            ]\n        ]\n    },\n    \"Russian\": {\n        name_short: \"RU\",\n        description: \"ЙЦУКЕН - Full\",\n        comment: \"Standard Russian keyboard layout\",\n        keys: [\n            [\n                { keytype: \"normal\", label: \"Esc\", shape: \"fn\", keycode: 1 },\n                { keytype: \"normal\", label: \"F1\", shape: \"fn\", keycode: 59 },\n                { keytype: \"normal\", label: \"F2\", shape: \"fn\", keycode: 60 },\n                { keytype: \"normal\", label: \"F3\", shape: \"fn\", keycode: 61 },\n                { keytype: \"normal\", label: \"F4\", shape: \"fn\", keycode: 62 },\n                { keytype: \"normal\", label: \"F5\", shape: \"fn\", keycode: 63 },\n                { keytype: \"normal\", label: \"F6\", shape: \"fn\", keycode: 64 },\n                { keytype: \"normal\", label: \"F7\", shape: \"fn\", keycode: 65 },\n                { keytype: \"normal\", label: \"F8\", shape: \"fn\", keycode: 66 },\n                { keytype: \"normal\", label: \"F9\", shape: \"fn\", keycode: 67 },\n                { keytype: \"normal\", label: \"F10\", shape: \"fn\", keycode: 68 },\n                { keytype: \"normal\", label: \"F11\", shape: \"fn\", keycode: 87 },\n                { keytype: \"normal\", label: \"F12\", shape: \"fn\", keycode: 88 },\n                { keytype: \"normal\", label: \"PrtSc\", shape: \"fn\", keycode: 99 },\n                { keytype: \"normal\", label: \"Del\", shape: \"fn\", keycode: 111 }\n            ],\n            [\n                { keytype: \"normal\", label: \"ё\", labelShift: \"Ё\", shape: \"normal\", keycode: 41 },\n                { keytype: \"normal\", label: \"1\", labelShift: \"!\", shape: \"normal\", keycode: 2 },\n                { keytype: \"normal\", label: \"2\", labelShift: \"\\\"\", shape: \"normal\", keycode: 3 },\n                { keytype: \"normal\", label: \"3\", labelShift: \"№\", shape: \"normal\", keycode: 4 },\n                { keytype: \"normal\", label: \"4\", labelShift: \";\", shape: \"normal\", keycode: 5 },\n                { keytype: \"normal\", label: \"5\", labelShift: \"%\", shape: \"normal\", keycode: 6 },\n                { keytype: \"normal\", label: \"6\", labelShift: \":\", shape: \"normal\", keycode: 7 },\n                { keytype: \"normal\", label: \"7\", labelShift: \"?\", shape: \"normal\", keycode: 8 },\n                { keytype: \"normal\", label: \"8\", labelShift: \"*\", shape: \"normal\", keycode: 9 },\n                { keytype: \"normal\", label: \"9\", labelShift: \"(\", shape: \"normal\", keycode: 10 },\n                { keytype: \"normal\", label: \"0\", labelShift: \")\", shape: \"normal\", keycode: 11 },\n                { keytype: \"normal\", label: \"-\", labelShift: \"_\", shape: \"normal\", keycode: 12 },\n                { keytype: \"normal\", label: \"=\", labelShift: \"+\", shape: \"normal\", keycode: 13 },\n                { keytype: \"normal\", label: \"Backspace\", shape: \"expand\", keycode: 14 }\n            ],\n            [\n                { keytype: \"normal\", label: \"Tab\", shape: \"tab\", keycode: 15 },\n                { keytype: \"normal\", label: \"й\", labelShift: \"Й\", shape: \"normal\", keycode: 16 },\n                { keytype: \"normal\", label: \"ц\", labelShift: \"Ц\", shape: \"normal\", keycode: 17 },\n                { keytype: \"normal\", label: \"у\", labelShift: \"У\", shape: \"normal\", keycode: 18 },\n                { keytype: \"normal\", label: \"к\", labelShift: \"К\", shape: \"normal\", keycode: 19 },\n                { keytype: \"normal\", label: \"е\", labelShift: \"Е\", shape: \"normal\", keycode: 20 },\n                { keytype: \"normal\", label: \"н\", labelShift: \"Н\", shape: \"normal\", keycode: 21 },\n                { keytype: \"normal\", label: \"г\", labelShift: \"Г\", shape: \"normal\", keycode: 22 },\n                { keytype: \"normal\", label: \"ш\", labelShift: \"Ш\", shape: \"normal\", keycode: 23 },\n                { keytype: \"normal\", label: \"щ\", labelShift: \"Щ\", shape: \"normal\", keycode: 24 },\n                { keytype: \"normal\", label: \"з\", labelShift: \"З\", shape: \"normal\", keycode: 25 },\n                { keytype: \"normal\", label: \"х\", labelShift: \"Х\", shape: \"normal\", keycode: 26 },\n                { keytype: \"normal\", label: \"ъ\", labelShift: \"Ъ\", shape: \"normal\", keycode: 27 },\n                { keytype: \"normal\", label: \"\\\\\", labelShift: \"/\", shape: \"expand\", keycode: 43 }\n            ],\n            [\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                { keytype: \"spacer\", label: \"\", shape: \"empty\" },\n                { keytype: \"normal\", label: \"ф\", labelShift: \"Ф\", shape: \"normal\", keycode: 30 },\n                { keytype: \"normal\", label: \"ы\", labelShift: \"Ы\", shape: \"normal\", keycode: 31 },\n                { keytype: \"normal\", label: \"в\", labelShift: \"В\", shape: \"normal\", keycode: 32 },\n                { keytype: \"normal\", label: \"а\", labelShift: \"А\", shape: \"normal\", keycode: 33 },\n                { keytype: \"normal\", label: \"п\", labelShift: \"П\", shape: \"normal\", keycode: 34 },\n                { keytype: \"normal\", label: \"р\", labelShift: \"Р\", shape: \"normal\", keycode: 35 },\n                { keytype: \"normal\", label: \"о\", labelShift: \"О\", shape: \"normal\", keycode: 36 },\n                { keytype: \"normal\", label: \"л\", labelShift: \"Л\", shape: \"normal\", keycode: 37 },\n                { keytype: \"normal\", label: \"д\", labelShift: \"Д\", shape: \"normal\", keycode: 38 },\n                { keytype: \"normal\", label: \"ж\", labelShift: \"Ж\", shape: \"normal\", keycode: 39 },\n                { keytype: \"normal\", label: \"э\", labelShift: \"Э\", shape: \"normal\", keycode: 40 },\n                { keytype: \"normal\", label: \"Enter\", shape: \"expand\", keycode: 28 }\n            ],\n            [\n                { keytype: \"modkey\", label: \"Shift\", shape: \"shift\", keycode: 42 },\n                { keytype: \"normal\", label: \"я\", labelShift: \"Я\", shape: \"normal\", keycode: 44 },\n                { keytype: \"normal\", label: \"ч\", labelShift: \"Ч\", shape: \"normal\", keycode: 45 },\n                { keytype: \"normal\", label: \"с\", labelShift: \"С\", shape: \"normal\", keycode: 46 },\n                { keytype: \"normal\", label: \"м\", labelShift: \"М\", shape: \"normal\", keycode: 47 },\n                { keytype: \"normal\", label: \"и\", labelShift: \"И\", shape: \"normal\", keycode: 48 },\n                { keytype: \"normal\", label: \"т\", labelShift: \"Т\", shape: \"normal\", keycode: 49 },\n                { keytype: \"normal\", label: \"ь\", labelShift: \"Ь\", shape: \"normal\", keycode: 50 },\n                { keytype: \"normal\", label: \"б\", labelShift: \"Б\", shape: \"normal\", keycode: 51 },\n                { keytype: \"normal\", label: \"ю\", labelShift: \"Ю\", shape: \"normal\", keycode: 52 },\n                { keytype: \"normal\", label: \".\", labelShift: \",\", shape: \"normal\", keycode: 53 },\n                { keytype: \"modkey\", label: \"Shift\", shape: \"expand\", keycode: 54 }\n            ],\n            [\n                { keytype: \"modkey\", label: \"Ctrl\", shape: \"control\", keycode: 29 },\n                { keytype: \"modkey\", label: \"Alt\", shape: \"normal\", keycode: 56 },\n                { keytype: \"normal\", label: \"Space\", shape: \"space\", keycode: 57 },\n                { keytype: \"modkey\", label: \"Alt\", shape: \"normal\", keycode: 100 },\n                { keytype: \"normal\", label: \"Menu\", shape: \"normal\", keycode: 139 },\n                { keytype: \"modkey\", label: \"Ctrl\", shape: \"control\", keycode: 97 }\n            ]\n        ]\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/Overlay.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n\n    property Component regionComponent: Component {\n        Region {}\n    }\n    \n    Loader {\n        id: overlayLoader\n        active: GlobalStates.overlayOpen || OverlayContext.hasPinnedWidgets\n        sourceComponent: PanelWindow {\n            id: overlayWindow\n            exclusionMode: ExclusionMode.Ignore\n            WlrLayershell.namespace: \"quickshell:overlay\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            // Use OnDemand for pinned widgets to allow focus switching with mouse clicks\n            WlrLayershell.keyboardFocus: GlobalStates.overlayOpen ? WlrKeyboardFocus.Exclusive : (OverlayContext.clickableWidgets.length > 0 ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None)\n            visible: true\n            color: \"transparent\"\n\n            mask: Region {\n                item: GlobalStates.overlayOpen ? overlayContent : null\n                regions: OverlayContext.clickableWidgets.map((widget) => regionComponent.createObject(this, {\n                    item: widget\n                }));\n            }\n\n            anchors {\n                top: true\n                bottom: true\n                left: true\n                right: true\n            }\n\n            HyprlandFocusGrab {\n                id: grab\n                windows: [overlayWindow]\n                active: false\n                onCleared: () => {\n                    if (!active) GlobalStates.overlayOpen = false;\n                }\n            }\n\n            Connections {\n                target: GlobalStates\n                function onOverlayOpenChanged() {\n                    delayedGrabTimer.restart();\n                }\n            }\n\n            Timer {\n                id: delayedGrabTimer\n                interval: Appearance.animation.elementMoveFast.duration\n                onTriggered: {\n                    grab.active = GlobalStates.overlayOpen;\n                }\n            }\n\n            OverlayContent {\n                id: overlayContent\n                anchors.fill: parent\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"overlay\"\n\n        function toggle(): void {\n            GlobalStates.overlayOpen = !GlobalStates.overlayOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"overlayToggle\"\n        description: \"Toggles overlay on press\"\n\n        onPressed: {\n            GlobalStates.overlayOpen = !GlobalStates.overlayOpen;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/OverlayBackground.qml",
    "content": "import QtQuick\nimport qs.modules.common\n\nRectangle {\n    id: contentItem\n    anchors.fill: parent\n    color: Appearance.m3colors.m3surfaceContainer\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/OverlayContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\n\nItem {\n    id: root\n    focus: true\n    readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true\n\n    Keys.onPressed: (event) => { // Esc to close\n        if (event.key === Qt.Key_Escape) {\n            GlobalStates.overlayOpen = false;\n        }\n    }\n\n    property real initScale: Config.options.overlay.openingZoomAnimation ? 1.08 : 1.000001\n    scale: initScale\n    Component.onCompleted: {\n        scale = 1\n    }\n    Behavior on scale {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n\n    Rectangle {\n        id: bg\n        anchors.fill: parent\n        color: Appearance.colors.colScrim\n        visible: Config.options.overlay.darkenScreen && opacity > 0\n        opacity: (GlobalStates.overlayOpen && root.scale !== initScale) ? 1 : 0\n        Behavior on opacity {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n    }\n\n    WidgetCanvas {\n        anchors.fill: parent\n        onClicked: GlobalStates.overlayOpen = false\n\n        OverlayTaskbar {\n            anchors {\n                horizontalCenter: parent.horizontalCenter\n                top: parent.top\n                topMargin: 50\n            }\n        }\n\n        Repeater {\n            model: ScriptModel {\n                values: Persistent.states.overlay.open.map(identifier => {\n                    return OverlayContext.availableWidgets.find(w => w.identifier === identifier);\n                })\n                objectProp: \"identifier\"\n            }\n            delegate: OverlayWidgetDelegateChooser {\n                \n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/OverlayContext.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport Quickshell\n\nSingleton {\n    id: root\n    \n    signal requestCenter(string identifier)\n\n    readonly property list<var> availableWidgets: [\n        { identifier: \"crosshair\", materialSymbol: \"point_scan\" },\n        { identifier: \"fpsLimiter\", materialSymbol: \"animation\" },\n        { identifier: \"floatingImage\", materialSymbol: \"imagesmode\" },\n        { identifier: \"recorder\", materialSymbol: \"screen_record\" },\n        { identifier: \"resources\", materialSymbol: \"browse_activity\" },\n        { identifier: \"notes\", materialSymbol: \"note_stack\" },\n        { identifier: \"volumeMixer\", materialSymbol: \"volume_up\" },\n    ]\n    \n    readonly property bool hasPinnedWidgets: root.pinnedWidgetIdentifiers.length > 0\n\n    property list<string> pinnedWidgetIdentifiers: []\n    property list<var> clickableWidgets: []\n\n    function pin(identifier: string, pin = true) {\n        if (pin) {\n            if (!root.pinnedWidgetIdentifiers.includes(identifier)) {\n                root.pinnedWidgetIdentifiers.push(identifier)\n            }\n        } else {\n            root.pinnedWidgetIdentifiers = root.pinnedWidgetIdentifiers.filter(id => id !== identifier)\n        }\n    }\n\n    function registerClickableWidget(widget: var, clickable = true) {\n        if (clickable) {\n            if (!root.clickableWidgets.includes(widget)) {\n                root.clickableWidgets.push(widget)\n            }\n        } else {\n            root.clickableWidgets = root.clickableWidgets.filter(w => w !== widget)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/OverlayTaskbar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\n\nRectangle {\n    id: root\n\n    property real padding: 8\n\n    opacity: GlobalStates.overlayOpen ? 1 : 0\n    implicitWidth: contentRow.implicitWidth + (padding * 2)\n    implicitHeight: contentRow.implicitHeight + (padding * 2)\n    color: Appearance.m3colors.m3surfaceContainer\n    radius: Appearance.rounding.large\n    border.color: Appearance.colors.colOutlineVariant\n    border.width: 1\n\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    RowLayout {\n        id: contentRow\n        anchors {\n            fill: parent\n            margins: root.padding\n        }\n        spacing: 6\n\n        Row {\n            spacing: 4\n            Repeater {\n                model: ScriptModel {\n                    values: OverlayContext.availableWidgets\n                }\n                delegate: WidgetButton {\n                    required property var modelData\n                    identifier: modelData.identifier\n                    materialSymbol: modelData.materialSymbol\n                }\n            }\n        }\n\n        Separator {}\n        TimeWidget {}\n        Separator {\n            visible: Battery.available\n        }\n        BatteryWidget {\n            visible: Battery.available\n        }\n    }\n\n    component Separator: Rectangle {\n        implicitWidth: 1\n        color: Appearance.colors.colOutlineVariant\n        Layout.fillHeight: true\n        Layout.topMargin: 10\n        Layout.bottomMargin: 10\n    }\n\n    component TimeWidget: StyledText {\n        Layout.alignment: Qt.AlignVCenter\n        Layout.leftMargin: 8\n        Layout.rightMargin: 6\n\n        text: DateTime.time\n        color: Appearance.colors.colOnSurface\n        font {\n            family: Appearance.font.family.numbers\n            variableAxes: Appearance.font.variableAxes.numbers\n            pixelSize: 22\n        }\n    }\n    \n    component BatteryWidget: Row {\n        id: batteryWidget\n        Layout.alignment: Qt.AlignVCenter\n        Layout.leftMargin: 6\n        Layout.rightMargin: 6\n        spacing: 2\n        property color colText: Battery.isLowAndNotCharging ? Appearance.colors.colError : Appearance.colors.colOnSurface\n\n        MaterialSymbol {\n            id: boltIcon\n            anchors.verticalCenter: parent.verticalCenter\n            fill: 1\n            text: Battery.isCharging ? \"bolt\" : \"battery_android_full\"\n            color: batteryWidget.colText\n            iconSize: 24\n            animateChange: true\n        }\n        \n        StyledText {\n            id: batteryText\n            anchors.verticalCenter: parent.verticalCenter\n            text: Math.round(Battery.percentage * 100) + \"%\"\n            color: batteryWidget.colText\n            font {\n                family: Appearance.font.family.numbers\n                variableAxes: Appearance.font.variableAxes.numbers\n                pixelSize: 18\n            }\n        }\n    }\n\n    component WidgetButton: RippleButton {\n        id: widgetButton\n        required property string identifier\n        required property string materialSymbol\n\n        Layout.alignment: Qt.AlignVCenter\n\n        toggled: Persistent.states.overlay.open.includes(identifier)\n        altAction: () => OverlayContext.requestCenter(identifier)\n        onClicked: {\n            if (widgetButton.toggled) {\n                Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== identifier);\n            } else {\n                Persistent.states.overlay.open.push(identifier);\n            }\n        }\n        implicitWidth: implicitHeight\n\n        colBackgroundToggled: Appearance.colors.colSecondaryContainer\n        colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n        colRippleToggled: Appearance.colors.colSecondaryContainerActive\n\n        buttonRadius: root.radius - (root.height - height) / 2\n\n        contentItem: Item {\n            anchors.centerIn: parent\n            implicitWidth: 32\n            implicitHeight: 32\n            MaterialSymbol {\n                id: iconWidget\n                anchors.centerIn: parent\n                iconSize: 24\n                text: widgetButton.materialSymbol\n                color: widgetButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurfaceVariant\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/OverlayWidgetDelegateChooser.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Bluetooth\nimport qs.modules.ii.overlay.crosshair\nimport qs.modules.ii.overlay.volumeMixer\nimport qs.modules.ii.overlay.floatingImage\nimport qs.modules.ii.overlay.fpsLimiter\nimport qs.modules.ii.overlay.recorder\nimport qs.modules.ii.overlay.resources\nimport qs.modules.ii.overlay.notes\n\nDelegateChooser {\n    id: root\n    role: \"identifier\"\n\n    DelegateChoice { roleValue: \"crosshair\"; Crosshair {} }\n    DelegateChoice { roleValue: \"floatingImage\"; FloatingImage {} }\n    DelegateChoice { roleValue: \"fpsLimiter\"; FpsLimiter {} }\n    DelegateChoice { roleValue: \"recorder\"; Recorder {} }\n    DelegateChoice { roleValue: \"resources\"; Resources {} }\n    DelegateChoice { roleValue: \"notes\"; Notes {} }\n    DelegateChoice { roleValue: \"volumeMixer\"; VolumeMixer {} }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/StyledOverlayWidget.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Qt5Compat.GraphicalEffects\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\n\n/*\n * To make an overlay widget:\n * 1. Create a modules/overlay/<yourWidget>/<YourWidget>.qml, using this as the base class and declare your widget content as contentItem\n * 2. Add an entry to OverlayContext.availableWidgets with identifier=<yourWidgetIdentifier>\n * 3. Add an entry in Persistent.states.overlay.<yourWidgetIdentifier> with x, y, width, height, pinned, clickthrough properties set to reasonable defaults\n * 4. Add an entry in OverlayWidgetDelegateChooser with roleValue=<yourWidgetIdentifier> and Declare your widget in there\n * Use existing entries as reference.\n */\nAbstractOverlayWidget {\n    id: root\n\n    // To be defined by subclasses\n    required property Item contentItem\n    property bool fancyBorders: true\n    property bool showCenterButton: false\n    property bool showClickabilityButton: true\n\n    // Defaults n stuff\n    required property var modelData\n    readonly property string identifier: modelData.identifier\n    readonly property string materialSymbol: modelData.materialSymbol ?? \"widgets\"\n    property string title: identifier.replace(/([A-Z])/g, \" $1\").replace(/^./, function(str){ return str.toUpperCase(); })\n    property var persistentStateEntry: Persistent.states.overlay[identifier]\n    property real radius: Appearance.rounding.windowRounding\n    property real minimumWidth: contentItem.implicitWidth\n    property real minimumHeight: contentItem.implicitHeight\n    property real resizeMargin: 8\n    property real padding: 6\n    property real contentRadius: radius - padding\n\n    // Resizing\n    function getXResizeDirection(x) {\n        return (x < root.resizeMargin) ? -1 : (x > root.width - root.resizeMargin) ? 1 : 0\n    }\n    function getYResizeDirection(y) {\n        return (y < root.resizeMargin) ? -1 : (y > root.height - root.resizeMargin) ? 1 : 0\n    }\n    hoverEnabled: true\n    property bool resizable: true\n    property bool resizing: false\n    property int resizeXDirection: getXResizeDirection(mouseX)\n    property int resizeYDirection: getYResizeDirection(mouseY)\n    draggable: GlobalStates.overlayOpen\n    drag.target: undefined\n    animateXPos: !dragHandler.active\n    animateYPos: !dragHandler.active\n    z: dragHandler.active ? 2 : 1\n    cursorShape: {\n        if (dragHandler.active) return root.resizing ? cursorShape : Qt.ArrowCursor;\n        if (resizeMargin < mouseX && mouseX < width - resizeMargin &&\n            resizeMargin < mouseY && mouseY < height - resizeMargin) {\n            return Qt.ArrowCursor;\n        } else {\n            if (!root.resizable) return Qt.ArrowCursor;\n            const dragIsLeft = mouseX < width / 2\n            const dragIsTop = mouseY < height / 2\n            if ((dragIsLeft && dragIsTop) || (!dragIsLeft && !dragIsTop)) {\n                return Qt.SizeFDiagCursor\n            } else {\n                return Qt.SizeBDiagCursor\n            }\n        }\n    }\n\n    // Positioning & sizing\n    x: Math.round(persistentStateEntry.x) // Round or it'll be blurry\n    y: Math.round(persistentStateEntry.y) // Round or it'll be blurry\n    pinned: persistentStateEntry.pinned\n    clickthrough: persistentStateEntry.clickthrough\n    drag {\n        minimumX: 0\n        minimumY: 0\n        maximumX: root.parent?.width - root.width\n        maximumY: root.parent?.height - root.height\n    }\n    opacity: (GlobalStates.overlayOpen || !clickthrough) ? 1.0 : Config.options.overlay.clickthroughOpacity\n\n    // Guarded states & registration funcs\n    property bool open: Persistent.states.overlay.open\n    property bool actuallyPinned: pinned && open\n    property bool actuallyClickable: !clickthrough && actuallyPinned && open\n    onActuallyPinnedChanged: reportPinnedState();\n    onActuallyClickableChanged: reportClickableState();\n    function reportPinnedState() {\n        OverlayContext.pin(identifier, actuallyPinned);\n    }\n    function reportClickableState() {\n        OverlayContext.registerClickableWidget(contentItem, actuallyClickable);\n    }\n\n    // Self-registeration with OverlayContext\n    Component.onCompleted: {\n        reportPinnedState();\n        reportClickableState();\n    }\n\n    Connections {\n        target: OverlayContext\n        function onRequestCenter(identifier) {\n            if (identifier === root.identifier) {\n                root.center()\n            }\n        }\n    }\n\n    // Hooks\n    onPressed: (event) => {\n        // We're only interested in handling resize here\n        // Early returns\n        if (!root.resizable) return;\n        if (root.resizeMargin < event.x && event.x < root.width - root.resizeMargin &&\n            root.resizeMargin < event.y && event.y < root.height - root.resizeMargin) {\n            return;\n        }\n        // Resizing setup\n        root.resizing = true;\n        root.resizeXDirection = getXResizeDirection(event.x);\n        root.resizeYDirection = getYResizeDirection(event.y);\n        if (root.resizeYDirection !== 0 && root.resizeXDirection === 0) {\n            root.resizeXDirection = event.x < root.width / 2 ? -1 : 1;\n        } else if (root.resizeXDirection !== 0 && root.resizeYDirection === 0) {\n            root.resizeYDirection = event.y < root.height / 2 ? -1 : 1;\n        }\n    }\n    onPositionChanged: (event) => {\n        if (!resizing) return;\n        contentContainer.implicitWidth = Math.max(root.persistentStateEntry.width + dragHandler.xAxis.activeValue * root.resizeXDirection, root.minimumWidth);\n        contentContainer.implicitHeight = Math.max(root.persistentStateEntry.height + dragHandler.yAxis.activeValue * root.resizeYDirection, root.minimumHeight);\n        const negativeXDrag = root.resizeXDirection === -1;\n        const negativeYDrag = root.resizeYDirection === -1;\n        const wantedX = root.persistentStateEntry.x + (negativeXDrag ? dragHandler.xAxis.activeValue : 0)\n        const wantedY = root.persistentStateEntry.y + (negativeYDrag ? dragHandler.yAxis.activeValue : 0)\n        const negativeXDragLimit = root.persistentStateEntry.x + root.persistentStateEntry.width - contentContainer.implicitWidth;\n        const negativeYDragLimit = root.persistentStateEntry.y + root.persistentStateEntry.height - contentContainer.implicitHeight;\n        root.x = negativeXDrag ? Math.min(wantedX, negativeXDragLimit) : wantedX;\n        root.y = negativeYDrag ? Math.min(wantedY, negativeYDragLimit) : wantedY;\n    }\n    DragHandler {\n        id: dragHandler\n        acceptedButtons: Qt.LeftButton | Qt.RightButton\n        target: (root.draggable && !root.resizing) ? root : null\n        onActiveChanged: { // Handle drag release\n            if (!active) {\n                root.resizing = false;\n                root.savePosition();\n            }\n        }\n        xAxis.minimum: 0\n        xAxis.maximum: root.parent?.width - root.width\n        yAxis.minimum: 0\n        yAxis.maximum: root.parent?.height - root.height\n    }\n\n    function close() {\n        Persistent.states.overlay.open = Persistent.states.overlay.open.filter(type => type !== root.identifier);\n    }\n\n    function togglePinned() {\n        persistentStateEntry.pinned = !persistentStateEntry.pinned;\n    }\n\n    function toggleClickthrough() {\n        persistentStateEntry.clickthrough = !persistentStateEntry.clickthrough;\n    }\n\n    function savePosition(xPos = root.x, yPos = root.y, width = contentContainer.implicitWidth, height = contentContainer.implicitHeight) {\n        persistentStateEntry.x = Math.round(xPos);\n        persistentStateEntry.y = Math.round(yPos);\n        persistentStateEntry.width = Math.round(width);\n        persistentStateEntry.height = Math.round(height);\n    }\n\n    function center() {\n        const targetX = (root.parent.width - contentColumn.width) / 2 - root.resizeMargin\n        const targetY = (root.parent.height - contentContainer.height) / 2 - titleBar.implicitHeight + border.border.width - root.resizeMargin\n        root.x = targetX\n        root.y = targetY\n        root.savePosition(targetX, targetY)\n    }\n\n    visible: GlobalStates.overlayOpen || actuallyPinned\n    implicitWidth: contentColumn.implicitWidth + resizeMargin * 2\n    implicitHeight: contentColumn.implicitHeight + resizeMargin * 2\n\n    Rectangle {\n        id: border\n        anchors {\n            fill: parent\n            margins: root.resizeMargin\n        }\n        color: ColorUtils.transparentize(Appearance.colors.colLayer1Base, (root.fancyBorders && GlobalStates.overlayOpen) ? 0 : 1)\n        radius: root.radius\n        border.color: ColorUtils.transparentize(Appearance.colors.colOutlineVariant, GlobalStates.overlayOpen ? 0 : 1)\n        border.width: 1\n\n        layer.enabled: GlobalStates.overlayOpen\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                width: border.width\n                height: border.height\n                radius: root.radius\n            }\n        }\n\n        ColumnLayout {\n            id: contentColumn\n            z: root.fancyBorders ? 0 : -1\n            anchors.fill: parent\n            spacing: 0\n\n            // Title bar\n            Rectangle {\n                id: titleBar\n                opacity: GlobalStates.overlayOpen ? 1 : 0\n                Layout.fillWidth: true\n                implicitWidth: titleBarRow.implicitWidth + root.padding * 2\n                implicitHeight: titleBarRow.implicitHeight + root.padding * 2\n                color: root.fancyBorders ? \"transparent\" : Appearance.colors.colLayer1Base\n                // border.color: Appearance.colors.colOutlineVariant\n                // border.width: 1\n                \n                RowLayout {\n                    id: titleBarRow\n                    anchors {\n                        fill: parent\n                        margins: root.padding\n                    }\n                    spacing: 2\n\n                    MaterialSymbol {\n                        text: root.materialSymbol\n                        Layout.leftMargin: 6\n                        iconSize: 20\n                        Layout.alignment: Qt.AlignVCenter\n                        Layout.rightMargin: 4\n                    }\n                    \n                    StyledText {\n                        Layout.fillWidth: true\n                        text: root.title\n                        elide: Text.ElideRight\n                    }\n\n                    TitlebarButton {\n                        visible: root.showCenterButton\n                        materialSymbol: \"recenter\"\n                        onClicked: root.center()\n                        StyledToolTip {\n                            text: \"Center\"\n                        }\n                    }\n\n                    TitlebarButton {\n                        visible: (root.pinned && root.showClickabilityButton)\n                        materialSymbol: \"mouse\"\n                        toggled: !root.clickthrough\n                        onClicked: root.toggleClickthrough()\n                        StyledToolTip {\n                            text: \"Clickable when pinned\"\n                        }\n                    }\n\n                    TitlebarButton {\n                        materialSymbol: \"keep\"\n                        toggled: root.pinned\n                        onClicked: root.togglePinned()\n                        StyledToolTip {\n                            text: \"Pin\"\n                        }\n                    }\n\n                    TitlebarButton {\n                        materialSymbol: \"close\"\n                        onClicked: root.close()\n                        StyledToolTip {\n                            text: \"Close\"\n                        }\n                    }\n                }\n            }\n\n            // Content\n            Item {\n                id: contentContainer\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                Layout.margins: root.fancyBorders ? root.padding : 0\n                Layout.topMargin: -border.border.width // Border of a rectangle is drawn inside its bounds, so we do this to make the gap not too big\n                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter\n                implicitWidth: Math.max(root.persistentStateEntry.width, root.minimumWidth)\n                implicitHeight: Math.max(root.persistentStateEntry.height, root.minimumHeight)\n                children: [root.contentItem]\n            }\n        }\n    }\n\n\n    component TitlebarButton: RippleButton {\n        id: titlebarButton\n        required property string materialSymbol\n        buttonRadius: height / 2\n        implicitHeight: contentItem.implicitHeight\n        implicitWidth: implicitHeight\n        padding: 0\n\n        colBackgroundToggled: Appearance.colors.colSecondaryContainer\n        colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n        colRippleToggled: Appearance.colors.colSecondaryContainerActive\n\n        contentItem: Item {\n            anchors.centerIn: parent\n            implicitWidth: 30\n            implicitHeight: 30\n\n            MaterialSymbol {\n                id: iconWidget\n                anchors.centerIn: parent\n                iconSize: 20\n                text: titlebarButton.materialSymbol\n                fill: titlebarButton.toggled\n                color: titlebarButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnSurface\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/crosshair/Crosshair.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.ii.overlay\n\nStyledOverlayWidget {\n    id: root\n    fancyBorders: false // Crosshair should be see-through\n    showCenterButton: true\n    opacity: 1 // The crosshair itself already has transparency if configured\n    showClickabilityButton: false\n    clickthrough: true\n    resizable: false\n\n    contentItem: CrosshairContent {\n        anchors.centerIn: parent\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/crosshair/CrosshairContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\n\nItem {\n    id: root\n\n    // Keys to props\n    // f, 0f, 1f, m are irrelevant as they're firing error stuff\n    // 0 is irrelevant because it's some profile stuff\n    property var propertyMap: ({\n        \"c\": \"color\",\n        \"u\": \"colorCode\",\n        \"h\": \"outline\",\n        \"o\": \"outlineOpacity\",\n        \"t\": \"outlineThickness\",\n        \"d\": \"centerDot\",\n        \"a\": \"centerDotOpacity\",\n        \"z\": \"centerDotSize\",\n        \"0a\": \"innerLineOpacity\",\n        \"0l\": \"innerLineLength\",\n        \"0v\": \"innerLineVerticalLength\",\n        \"0g\": \"innerLineUnbindAxesLengths\",\n        \"0t\": \"innerLineThickness\",\n        \"0o\": \"innerLineOffset\",\n        \"1b\": \"outerLines\",\n        \"1a\": \"outerLineOpacity\",\n        \"1l\": \"outerLineLength\",\n        \"1v\": \"outerLineVerticalLength\",\n        \"1g\": \"outerLineUnbindAxesLengths\",\n        \"1t\": \"outerLineThickness\",\n        \"1o\": \"outerLineOffset\",\n    })\n    property var colorMap: ({\n        0: \"#FFFFFF\",\n        1: \"#00FF00\",\n        2: \"#7FFF00\",\n        3: \"#DFFF00\",\n        4: \"#FFFF00\",\n        5: \"#00FFFF\",\n        6: \"#FF00FF\",\n        7: \"#FF0000\"\n    })\n\n    // Raw props\n    property int color: 0\n    property string colorCode: \"#FFFFFF\"\n    property bool outline: true\n    property real outlineOpacity: 0.5\n    property int outlineThickness: 1\n    property bool centerDot: false\n    property real centerDotOpacity: 1\n    property int centerDotSize: 2\n    property bool innerLines: true\n    property real innerLineOpacity: 0.8\n    property int innerLineLength: 6\n    property int innerLineVerticalLength: innerLineLength\n    property bool innerLineUnbindAxesLengths: false\n    property int innerLineThickness: 2\n    property int innerLineOffset: 3\n    property bool outerLines: true\n    property real outerLineOpacity: 0.35\n    property int outerLineLength: 2\n    property int outerLineVerticalLength: outerLineLength\n    property bool outerLineUnbindAxesLengths: false\n    property int outerLineThickness: 2\n    property int outerLineOffset: 10\n    property string defaultCode: \"c;0;u;FFFFFF;h;1;o;0.5;t;1;d;0;a;1;z;2;0a;0.8;0l;6;0v;6;0g;0;0t;2;0o;3;1b;1;1a;0.35;1l;2;1v;2;1g;0;1t;2;1o;10\"\n\n    function loadFromCode(code: string): void {\n        let args = code.split(\";\");\n        for (let i = 0; i < args.length; i+= 2) {\n            let key = args[i];\n            let value = args[i+1];\n            let targetKey = root.propertyMap[key];\n            let targetType = typeof root[targetKey];\n\n            if (targetKey === undefined) continue;\n\n            if (targetType === \"number\") {\n                value = parseFloat(value);\n            } else if (targetType === \"boolean\") {\n                value = (value === \"1\");\n            } \n            if (targetKey === \"colorCode\") {\n                value = \"#\" + value.slice(0, 6);\n            }\n            root[targetKey] = value;\n        }\n\n        if (!root.innerLineUnbindAxesLengths) {\n            root.innerLineVerticalLength = root.innerLineLength;\n        }\n        if (!root.outerLineUnbindAxesLengths) {\n            root.outerLineVerticalLength = root.outerLineLength;\n        }\n\n    }\n\n    // Update values from code\n    property var code: Config.options.crosshair.code\n    Component.onCompleted: reloadFromCode();\n    onCodeChanged: reloadFromCode();\n    function reloadFromCode() {\n        root.loadFromCode(root.defaultCode);\n        root.loadFromCode(root.code);\n    }\n\n    // Aggregated props\n    property color crosshairColor: {\n        if (colorMap[color] !== undefined) return root.colorMap[color];\n        if (color === 8) return colorCode;\n        return \"#FFFFFF\";\n    }\n    property int borderWidth: outline ? outlineThickness : 0\n    property color borderColor: ColorUtils.transparentize(\"black\", 1 - root.outlineOpacity)\n    property color innerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.innerLineOpacity)\n    property color outerLineColor: ColorUtils.transparentize(root.crosshairColor, 1 - root.outerLineOpacity)\n    property int innerLineTotalOffset: root.centerDotSize / 2 + 1 + root.innerLineOffset\n    property int outerLineTotalOffset: root.centerDotSize / 2 + 1 + root.outerLineOffset\n    property real centerDotTotalSize: root.centerDotSize + root.borderWidth * 2\n    property real innerLineTotalSize: (innerLineTotalOffset + root.innerLineLength + root.borderWidth) * 2\n    property real outerLineTotalSize: (outerLineTotalOffset + root.outerLineLength + root.borderWidth) * 2\n    implicitWidth: Math.max(centerDotTotalSize, innerLineTotalSize, outerLineTotalSize) + 2 // 2 for pixel correction\n    implicitHeight: implicitWidth\n    // width: implicitWidth\n    // height: implicitHeight\n\n    Rectangle {\n        id: centerDot\n        visible: root.centerDot\n        anchors.centerIn: parent\n\n        color: root.crosshairColor\n        opacity: root.centerDotOpacity\n        width: centerDotTotalSize\n        height: width\n\n        border.width: root.borderWidth\n        border.color: root.borderColor\n    }\n\n    Repeater {\n        id: innerLines\n        model: 4\n        Item {\n            id: innerHair\n            z: index % 2 // Vertical lines above horizontal lines\n            required property int index\n            property int pixelCorrection: (root.innerLineThickness % 2 === 1 && index > 1) ? 1 : 0\n            property int hairLength: (innerHair.index % 2 === 0 ? root.innerLineLength : root.innerLineVerticalLength)\n            visible: root.innerLines && hairLength > 0\n            anchors.fill: parent\n            rotation: index * 90\n            Rectangle {\n                x: parent.width / 2 + root.innerLineTotalOffset - root.borderWidth + innerHair.pixelCorrection\n                y: parent.height / 2 - height / 2\n\n                color: root.innerLineColor\n                width: innerHair.hairLength + root.borderWidth * 2\n                height: root.innerLineThickness + root.borderWidth * 2\n\n                border.width: root.borderWidth\n                border.color: root.borderColor\n            }\n        }\n    }\n\n    Repeater {\n        id: outerLines\n        model: 4\n        Item {\n            id: outerHair\n            z: index % 2 + 2 // Vertical lines above horizontal lines, above inner lines\n            required property int index\n            property int pixelCorrection: (root.outerLineThickness % 2 === 1 && index > 1) ? 1 : 0\n            property int hairLength: (outerHair.index % 2 === 0 ? root.outerLineLength : root.outerLineVerticalLength)\n            visible: root.outerLines && hairLength > 0\n            anchors.fill: parent\n            rotation: index * 90\n            Rectangle {\n                x: parent.width / 2 + root.outerLineTotalOffset - root.borderWidth + outerHair.pixelCorrection\n                y: parent.height / 2 - height / 2\n\n                color: root.outerLineColor\n                width: hairLength + root.borderWidth * 2\n                height: root.outerLineThickness + root.borderWidth * 2\n\n                border.width: root.borderWidth\n                border.color: root.borderColor\n            }\n        }\n    }\n\n    \n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/floatingImage/FloatingImage.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport Qt5Compat.GraphicalEffects\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.utils\nimport qs.modules.ii.overlay\n\nStyledOverlayWidget {\n    id: root\n    showClickabilityButton: false\n    resizable: false\n    clickthrough: true\n\n    property string imageSource: Config.options.overlay.floatingImage.imageSource\n    property real scaleFactor: Config.options.overlay.floatingImage.scale\n    property int imageWidth: 0\n    property int imageHeight: 0\n\n    // Override to always save 0 size\n    function savePosition(xPos = root.x, yPos = root.y, width = 0, height = 0) {\n        root.persistentStateEntry.x = Math.round(xPos);\n        root.persistentStateEntry.y = Math.round(yPos);\n        root.persistentStateEntry.width = 0\n        root.persistentStateEntry.height = 0\n    }\n\n    onImageSourceChanged: {\n        imageDownloader.running = false;\n        imageDownloader.sourceUrl = root.imageSource;\n        imageDownloader.filePath = Qt.resolvedUrl(Directories.tempImages + \"/\" + Qt.md5(root.imageSource))\n        imageDownloader.running = true;\n    }\n    onScaleFactorChanged: {\n        setSize();\n    }\n\n    function setSize() {\n        bg.implicitWidth = root.imageWidth * root.scaleFactor;\n        bg.implicitHeight = root.imageHeight * root.scaleFactor;\n    }\n\n    contentItem: OverlayBackground {\n        id: bg\n        color: ColorUtils.transparentize(Appearance.m3colors.m3surfaceContainer, root.actuallyPinned ? 1 : 0)\n        radius: root.contentRadius\n\n        WheelHandler {\n            onWheel: (event) => {\n                if (event.angleDelta.y < 0) {\n                    Config.options.overlay.floatingImage.scale = Math.max(0.1, Config.options.overlay.floatingImage.scale - 0.1);\n                }\n                else if (event.angleDelta.y > 0) {\n                    Config.options.overlay.floatingImage.scale = Math.min(5.0, Config.options.overlay.floatingImage.scale + 0.1);\n                }\n            }\n            acceptedDevices: PointerDevice.Mouse | PointerDevice.TouchPad\n        }\n\n        layer.enabled: true\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                width: bg.width\n                height: bg.height\n                radius: bg.radius\n            }\n        }\n\n        AnimatedImage {\n            id: animatedImage\n            anchors.centerIn: parent\n            width: root.imageWidth * root.scaleFactor\n            height: root.imageHeight * root.scaleFactor\n            sourceSize: {\n                const dpr = (QsWindow.window as QsWindow)?.devicePixelRatio ?? 1;\n                return Qt.size(width * dpr, height * dpr);\n            }\n\n            playing: visible\n            asynchronous: true\n            source: \"\"\n\n            ImageDownloaderProcess {\n                id: imageDownloader\n                filePath: Qt.resolvedUrl(Directories.tempImages + \"/\" + Qt.md5(root.imageSource))\n                sourceUrl: root.imageSource\n\n                onDone: (path, width, height) => {\n                    root.imageWidth = width;\n                    root.imageHeight = height;\n                    root.setSize();\n                    animatedImage.source = path;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/fpsLimiter/FpsLimiter.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.ii.overlay\n\nStyledOverlayWidget {\n    id: root\n    title: \"MangoHud FPS\"\n    minimumWidth: 275\n    minimumHeight: 100\n    contentItem: FpsLimiterContent {\n        radius: root.contentRadius\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/fpsLimiter/FpsLimiterContent.qml",
    "content": "import qs.services\nimport QtQuick\nimport QtQuick.Layouts\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.ii.overlay\n\nOverlayBackground {\n    id: root\n\n    enum State { Normal, Success, Error }\n\n    property real padding: 16\n    property var currentState: FpsLimiterContent.State.Normal\n    implicitWidth: content.implicitWidth + (padding * 2)\n    implicitHeight: content.implicitHeight + (padding * 2)\n\n    Timer {\n        id: iconResetTimer\n        interval: 1000\n        onTriggered: {\n            root.currentState = FpsLimiterContent.State.Normal;\n        }\n    }\n\n    function applyLimit() {\n        var fpsValue = parseInt(fpsField.text);\n        if (isNaN(fpsValue) || fpsValue < 0) {\n            root.currentState = FpsLimiterContent.State.Error;\n            iconResetTimer.restart();\n            fpsField.text = \"\";\n            return;\n        }\n\n        var cfgPaths = [\n            \"~/.config/MangoHud/MangoHud.conf\",\n        ]; // MangoHud config files\n\n        var updateCommands = cfgPaths.map(path => {\n            return \"if grep -q '^fps_limit=' \" + path + \"; \" +\n            \"then sed -i 's/^fps_limit=.*/fps_limit=\" + fpsValue + \"/' \" + path + \"; \" +\n            \"else echo 'fps_limit=\" + fpsValue + \"' >> \" + path + \"; fi\";\n        }).join(\"; \");\n\n        var cmd = updateCommands + \"; pkill -SIGUSR2 mangohud\";\n\n        fpsSetter.command = [\"bash\", \"-c\", cmd];\n        fpsSetter.startDetached();\n\n        root.currentState = FpsLimiterContent.State.Success;\n        iconResetTimer.restart();\n\n        // Clear the field after applying\n        fpsField.text = \"\";\n    }\n\n    Process {\n        id: fpsSetter\n    }\n\n    RowLayout {\n        id: content\n        anchors.centerIn: parent\n        spacing: 4\n\n        ToolbarTextField {\n            id: fpsField\n            Layout.fillWidth: true\n            Layout.preferredWidth: 200\n            placeholderText: root.currentState === FpsLimiterContent.State.Error ? Translation.tr(\"Enter a valid number\") : Translation.tr(\"Set FPS limit\")\n            inputMethodHints: Qt.ImhDigitsOnly\n            focus: true\n\n            onAccepted: {\n                root.applyLimit();\n            }\n        }\n\n        IconToolbarButton {\n            id: applyButton\n            text: switch (root.currentState) {\n                case FpsLimiterContent.State.Error: return \"close\";\n                case FpsLimiterContent.State.Success: return \"check\";\n                case FpsLimiterContent.State.Normal:\n                default: return \"save\";\n            }\n            enabled: root.currentState === FpsLimiterContent.State.Normal && fpsField.text.length > 0\n            onClicked: {\n                root.applyLimit();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/notes/Notes.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.ii.overlay\n\nStyledOverlayWidget {\n    id: root\n    title: Translation.tr(\"Notes\")\n    showCenterButton: true\n\n    contentItem: NotesContent {\n        radius: root.contentRadius\n        isClickthrough: root.clickthrough\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/notes/NotesContent.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.ii.overlay\n\nOverlayBackground {\n    id: root\n\n    property alias content: textInput.text\n    property bool pendingReload: false\n    property var copyListEntries: []\n    property string lastParsedCopylistText: \"\"\n    property var parsedCopylistLines: []\n    property bool isClickthrough: false\n    property real maxCopyButtonSize: 20\n\n    Component.onCompleted: {\n        noteFile.reload();\n        updateCopyListEntries();\n    }\n\n    function saveContent() {\n        if (!textInput)\n            return;\n        noteFile.setText(root.content);\n    }\n\n    function focusAtEnd() {\n        if (!textInput)\n            return;\n        textInput.forceActiveFocus();\n        const endPos = root.content.length;\n        applySelection(endPos, endPos);\n    }\n\n    function applySelection(cursorPos, anchorPos) {\n        if (!textInput)\n            return;\n        const textLength = root.content.length;\n        const cursor = Math.max(0, Math.min(cursorPos, textLength));\n        const anchor = Math.max(0, Math.min(anchorPos, textLength));\n        textInput.select(anchor, cursor);\n        if (cursor === anchor)\n            textInput.deselect();\n    }\n\n    function scheduleCopylistUpdate(immediate = false) {\n        if (!textInput)\n            return;\n        if (immediate) {\n            copyListDebounce?.stop();\n            updateCopyListEntries();\n        } else {\n            copyListDebounce.restart();\n        }\n    }\n\n    function updateCopyListEntries() {\n        if (!textInput)\n            return;\n        const textValue = root.content;\n        if (!textValue || textValue.length === 0) {\n            lastParsedCopylistText = \"\";\n            parsedCopylistLines = [];\n            root.copyListEntries = [];\n            return;\n        }\n\n        if (textValue !== lastParsedCopylistText) {\n            const lineRegex = /(.*?)(\\r?\\n|$)/g;\n            let match = null;\n            const parsed = [];\n            while ((match = lineRegex.exec(textValue)) !== null) {\n                const lineText = match[1];\n                const newlineText = match[2];\n                const lineStart = match.index;\n                const lineEnd = lineStart + lineText.length;\n                const bulletMatch = lineText.match(/^\\s*-\\s+(.*\\S)\\s*$/);\n                if (bulletMatch) {\n                    parsed.push({\n                        content: bulletMatch[1].trim(),\n                        start: lineStart,\n                        end: lineEnd\n                    });\n                }\n                if (newlineText === \"\")\n                    break;\n            }\n            lastParsedCopylistText = textValue;\n            parsedCopylistLines = parsed;\n            if (parsed.length === 0) {\n                root.copyListEntries = [];\n                return;\n            }\n        }\n\n        updateCopylistPositions();\n    }\n\n    function updateCopylistPositions() {\n        if (!textInput || parsedCopylistLines.length === 0)\n            return;\n        const rawSelectionStart = textInput.selectionStart;\n        const rawSelectionEnd = textInput.selectionEnd;\n        const selectionStart = rawSelectionStart === -1 ? textInput.cursorPosition : rawSelectionStart;\n        const selectionEnd = rawSelectionEnd === -1 ? textInput.cursorPosition : rawSelectionEnd;\n        const rangeStart = Math.min(selectionStart, selectionEnd);\n        const rangeEnd = Math.max(selectionStart, selectionEnd);\n\n        const entries = parsedCopylistLines.map(line => {\n            // Don't show copy button if line is (partially) selected\n            const caretIntersects = rangeEnd > line.start && rangeStart <= line.end;\n            if (caretIntersects)\n                return null;\n            const startRect = textInput.positionToRectangle(line.start);\n            let endRect = textInput.positionToRectangle(line.end);\n            if (!isFinite(startRect.y))\n                return null;\n            if (!isFinite(endRect.y))\n                endRect = startRect;\n            const lineBottom = endRect.y + endRect.height;\n            const rectHeight = Math.max(lineBottom - startRect.y, textInput.font.pixelSize + 8);\n            return {\n                content: line.content,\n                y: startRect.y,\n                height: rectHeight\n            };\n        }).filter(entry => entry !== null);\n\n        root.copyListEntries = entries;\n    }\n\n    implicitWidth: 300\n    implicitHeight: 200\n\n    ColumnLayout {\n        id: contentItem\n        anchors.fill: parent\n        spacing: -16\n\n        ScrollView {\n            id: editorScrollView\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            clip: true\n            ScrollBar.vertical.policy: ScrollBar.AsNeeded\n            onWidthChanged: root.scheduleCopylistUpdate(true)\n\n            StyledTextArea { // This has to be a direct child of ScrollView for proper scrolling\n                id: textInput\n                anchors {\n                    left: parent.left\n                    right: parent.right\n                }\n                wrapMode: TextEdit.Wrap\n                placeholderText: Translation.tr(\"Write something here...\\nUse '-' to create copyable bullet points, like this:\\n\\nSheep fricker\\n- 4x Slab\\n- 1x Boat\\n- 4x Redstone Dust\\n- 1x Sticky Piston\\n- 1x End Rod\\n- 4x Redstone Repeater\\n- 1x Redstone Torch\\n- 1x Sheep\")\n                selectByMouse: true\n                persistentSelection: true\n                textFormat: TextEdit.PlainText\n                background: null\n                padding: 24\n\n                onTextChanged: {\n                    if (textInput.activeFocus) {\n                        saveDebounce.restart();\n                    }\n                    root.scheduleCopylistUpdate(true);\n                }\n                \n                onHeightChanged: root.scheduleCopylistUpdate(true)\n                onContentHeightChanged: root.scheduleCopylistUpdate(true)\n                onCursorPositionChanged: root.scheduleCopylistUpdate()\n                onSelectionStartChanged: root.scheduleCopylistUpdate()\n                onSelectionEndChanged: root.scheduleCopylistUpdate()\n            }\n\n            Item {\n                anchors.fill: parent\n                visible: root.copyListEntries.length > 0\n                clip: true\n\n                Repeater {\n                    model: ScriptModel {\n                        values: root.copyListEntries\n                    }\n                    delegate: RippleButton {\n                        id: copyButton\n                        required property var modelData\n                        readonly property real lineHeight: Math.min(Math.max(modelData.height, Appearance.font.pixelSize.normal + 6), root.maxCopyButtonSize)\n                        readonly property real iconSizeLocal: Appearance.font.pixelSize.normal\n                        readonly property real hitPadding: 6\n                        property bool justCopied: false\n\n                        implicitHeight: lineHeight\n                        implicitWidth: lineHeight\n                        buttonRadius: height / 2\n                        y: modelData.y\n                        anchors.right: parent.right\n                        anchors.rightMargin: 10\n                        z: 5\n\n                        Timer {\n                            id: resetState\n                            interval: 700\n                            onTriggered: {\n                                copyButton.justCopied = false;\n                            }\n                        }\n\n                        onClicked: {\n                            Quickshell.clipboardText = copyButton.modelData.content;\n                            justCopied = true;\n                            resetState.start();\n                        }\n\n                        contentItem: Item {\n                            anchors.centerIn: parent\n                            MaterialSymbol {\n                                id: iconItem\n                                anchors.centerIn: parent\n                                text: copyButton.justCopied ? \"check\" : \"content_copy\"\n                                iconSize: copyButton.iconSizeLocal\n                                color: Appearance.colors.colOnLayer1\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        StyledText {\n            id: statusLabel\n            Layout.fillWidth: true\n            Layout.margins: 16\n            horizontalAlignment: Text.AlignRight\n            text: saveDebounce.running ? Translation.tr(\"Saving...\") : Translation.tr(\"Saved    \")\n            color: Appearance.colors.colSubtext\n        }\n    }\n\n    Timer {\n        id: saveDebounce\n        interval: 500\n        repeat: false\n        onTriggered: saveContent()\n    }\n\n    Timer {\n        id: copyListDebounce\n        interval: 100\n        repeat: false\n        onTriggered: updateCopylistPositions()\n    }\n\n    FileView {\n        id: noteFile\n        path: Qt.resolvedUrl(Directories.notesPath)\n        onLoaded: {\n            root.content = noteFile.text();\n            if (root.content !== root.content) {\n                const previousCursor = textInput.cursorPosition;\n                const previousAnchor = textInput.selectionStart;\n                root.content = root.content;\n                applySelection(previousCursor, previousAnchor);\n            }\n            if (pendingReload) {\n                pendingReload = false;\n                Qt.callLater(root.focusAtEnd);\n            }\n            Qt.callLater(root.updateCopyListEntries);\n        }\n        onLoadFailed: error => {\n            if (error === FileViewError.FileNotFound) {\n                root.content = \"\";\n                noteFile.setText(root.content);\n                if (pendingReload) {\n                    pendingReload = false;\n                    Qt.callLater(root.focusAtEnd);\n                }\n                Qt.callLater(root.updateCopyListEntries);\n            } else {\n                console.log(\"[Overlay Notes] Error loading file: \" + error);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/recorder/Recorder.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.ii.overlay\n\nStyledOverlayWidget {\n    id: root\n    minimumWidth: 310\n    minimumHeight: 130\n\n    contentItem: OverlayBackground {\n        id: contentItem\n        radius: root.contentRadius\n        property real padding: 8\n        ColumnLayout {\n            id: contentColumn\n            anchors.centerIn: parent\n            spacing: 10\n\n            Row {\n                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter\n                spacing: 10\n\n                BigRecorderButton {\n                    materialSymbol: \"screenshot_region\"\n                    name: \"Screenshot region\"\n                    onClicked: {\n                        GlobalStates.overlayOpen = false;\n                        Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"region\", \"screenshot\"]);\n                    }\n                }\n\n                BigRecorderButton {\n                    materialSymbol: \"photo_camera\"\n                    name: \"Screenshot\"\n                    onClicked: {\n                        GlobalStates.overlayOpen = false;\n                        Quickshell.execDetached([\"bash\", \"-c\", \"grim - | wl-copy\"]);\n                    }\n                }\n\n                BigRecorderButton {\n                    materialSymbol: \"screen_record\"\n                    name: \"Record region\"\n                    onClicked: {\n                        GlobalStates.overlayOpen = false;\n                        Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"region\", \"recordWithSound\"]);\n                    }\n                }\n                \n                BigRecorderButton {\n                    materialSymbol: \"capture\"\n                    name: \"Record screen\"\n                    onClicked: {\n                        GlobalStates.overlayOpen = false;\n                        Quickshell.execDetached([Directories.recordScriptPath, \"--fullscreen\", \"--sound\"]);\n                    }\n                }\n            }\n\n            RippleButton {\n                Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter\n                Layout.fillWidth: false\n                buttonRadius: height / 2\n                colBackground: Appearance.colors.colLayer3\n                colBackgroundHover: Appearance.colors.colLayer3Hover\n                colRipple: Appearance.colors.colLayer3Active\n                onClicked: {\n                    GlobalStates.overlayOpen = false;\n                    Qt.openUrlExternally(`file://${Config.options.screenRecord.savePath}`);\n                }\n                contentItem: Row {\n                    anchors.centerIn: parent\n                    spacing: 6\n                    MaterialSymbol {\n                        anchors.verticalCenter: parent.verticalCenter\n                        text: \"animated_images\"\n                        iconSize: 20\n                    }\n                    StyledText {\n                        anchors.verticalCenter: parent.verticalCenter\n                        text: Translation.tr(\"Open recordings folder\")\n                    }\n                }\n            }\n        }\n    }\n\n    component BigRecorderButton: RippleButton {\n        id: bigButton\n        required property string materialSymbol\n        required property string name\n        implicitHeight: 66\n        implicitWidth: 66\n        buttonRadius: height / 2\n\n        colBackground: Appearance.colors.colLayer3\n        colBackgroundHover: Appearance.colors.colLayer3Hover\n        colRipple: Appearance.colors.colLayer3Active\n\n        contentItem: MaterialSymbol {\n            anchors.centerIn: parent\n            horizontalAlignment: Text.AlignHCenter\n            verticalAlignment: Text.AlignVCenter\n            text: bigButton.materialSymbol\n            iconSize: 28\n        }\n\n        StyledToolTip {\n            text: bigButton.name\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/resources/Resources.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport Qt5Compat.GraphicalEffects\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.ii.overlay\n\nStyledOverlayWidget {\n    id: root\n    minimumWidth: 300\n    minimumHeight: 200\n    property list<var> resources: [\n        {\n            \"icon\": \"planner_review\",\n            \"name\": Translation.tr(\"CPU\"),\n            \"history\": ResourceUsage.cpuUsageHistory,\n            \"maxAvailableString\": ResourceUsage.maxAvailableCpuString\n        },\n        {\n            \"icon\": \"memory\",\n            \"name\": Translation.tr(\"RAM\"),\n            \"history\": ResourceUsage.memoryUsageHistory,\n            \"maxAvailableString\": ResourceUsage.maxAvailableMemoryString\n        },\n        {\n            \"icon\": \"swap_horiz\",\n            \"name\": Translation.tr(\"Swap\"),\n            \"history\": ResourceUsage.swapUsageHistory,\n            \"maxAvailableString\": ResourceUsage.maxAvailableSwapString\n        },\n    ]\n\n    contentItem: OverlayBackground {\n        id: contentItem\n        radius: root.contentRadius\n        property real padding: 4\n        ColumnLayout {\n            id: contentColumn\n            anchors {\n                fill: parent\n                margins: parent.padding\n            }\n            spacing: 8\n\n            SecondaryTabBar {\n                id: tabBar\n\n                currentIndex: Persistent.states.overlay.resources.tabIndex\n                onCurrentIndexChanged: {\n                    Persistent.states.overlay.resources.tabIndex = tabBar.currentIndex;\n                }\n\n                Repeater {\n                    model: root.resources.length\n                    delegate: SecondaryTabButton {\n                        required property int index\n                        property var modelData: root.resources[index]\n                        buttonIcon: modelData.icon\n                        buttonText: modelData.name\n                    }\n                }\n            }\n\n            ResourceSummary {\n                Layout.margins: 8\n                history: root.resources[tabBar.currentIndex]?.history ?? []\n                maxAvailableString: root.resources[tabBar.currentIndex]?.maxAvailableString ?? \"--\"\n            }\n        }\n    }\n\n    component ResourceSummary: RowLayout {\n        id: resourceSummary\n        required property list<real> history\n        required property string maxAvailableString\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        spacing: 12\n\n        ColumnLayout {\n            spacing: 2\n            StyledText {\n                text: (resourceSummary.history[resourceSummary.history.length - 1] * 100).toFixed(1) + \"%\"\n                font {\n                    family: Appearance.font.family.numbers\n                    variableAxes: Appearance.font.variableAxes.numbers\n                    pixelSize: Appearance.font.pixelSize.huge\n                }\n            }\n            StyledText {\n                text: Translation.tr(\"of %1\").arg(resourceSummary.maxAvailableString)\n                font {\n                    // family: Appearance.font.family.numbers\n                    // variableAxes: Appearance.font.variableAxes.numbers\n                    pixelSize: Appearance.font.pixelSize.smallie\n                }\n                color: Appearance.colors.colSubtext\n            }\n            Item {\n                Layout.fillHeight: true\n            }\n        }\n        Rectangle {\n            id: graphBg\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            radius: Appearance.rounding.small\n            color: Appearance.colors.colSecondaryContainer\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: graphBg.width\n                    height: graphBg.height\n                    radius: graphBg.radius\n                }\n            }\n            Graph {\n                anchors.fill: parent\n                values: root.resources[tabBar.currentIndex]?.history ?? []\n                points: ResourceUsage.historyLength\n                alignment: Graph.Alignment.Right\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overlay/volumeMixer/VolumeMixer.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.ii.overlay\nimport qs.modules.ii.sidebarRight.volumeMixer\n\nStyledOverlayWidget {\n    id: root\n    minimumWidth: 300\n    minimumHeight: 380\n\n    contentItem: OverlayBackground {\n        radius: root.contentRadius\n        property real padding: 6\n\n        ColumnLayout {\n            id: contentColumn\n            anchors {\n                fill: parent\n                margins: parent.padding\n            }\n            spacing: 8\n\n            SecondaryTabBar {\n                id: tabBar\n\n                currentIndex: Persistent.states.overlay.volumeMixer.tabIndex\n                onCurrentIndexChanged: {\n                    Persistent.states.overlay.volumeMixer.tabIndex = tabBar.currentIndex;\n                }\n\n                SecondaryTabButton {\n                    buttonIcon: \"media_output\"\n                    buttonText: Translation.tr(\"Output\")\n                }\n                SecondaryTabButton {\n                    buttonIcon: \"mic\"\n                    buttonText: Translation.tr(\"Input\")\n                }\n            }\n            SwipeView {\n                id: swipeView\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                currentIndex: Persistent.states.overlay.volumeMixer.tabIndex\n                onCurrentIndexChanged: {\n                    Persistent.states.overlay.volumeMixer.tabIndex = swipeView.currentIndex;\n                }\n                clip: true\n\n                PaddedVolumeDialogContent { \n                    isSink: true \n                }\n                PaddedVolumeDialogContent { \n                    isSink: false \n                }\n            }\n        }\n    }\n\n    component PaddedVolumeDialogContent: Item {\n        id: paddedVolumeDialogContent\n        property alias isSink: volDialogContent.isSink\n        property real padding: 12\n        implicitWidth: volDialogContent.implicitWidth + padding * 2\n        implicitHeight: volDialogContent.implicitHeight + padding * 2\n\n        VolumeDialogContent {\n            id: volDialogContent\n            anchors {\n                fill: parent\n                margins: paddedVolumeDialogContent.padding\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overview/Overview.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport Qt.labs.synchronizer\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: overviewScope\n    property bool dontAutoCancelSearch: false\n\n    PanelWindow {\n        id: panelWindow\n        property string searchingText: \"\"\n        readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)\n        property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)\n        visible: GlobalStates.overviewOpen\n\n        WlrLayershell.namespace: \"quickshell:overview\"\n        WlrLayershell.layer: WlrLayer.Top\n        WlrLayershell.keyboardFocus: GlobalStates.overviewOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None\n        color: \"transparent\"\n\n        mask: Region {\n            item: GlobalStates.overviewOpen ? columnLayout : null\n        }\n\n        anchors {\n            top: true\n            bottom: true\n            left: true\n            right: true\n        }\n\n        Connections {\n            target: GlobalStates\n            function onOverviewOpenChanged() {\n                if (!GlobalStates.overviewOpen) {\n                    searchWidget.disableExpandAnimation();\n                    overviewScope.dontAutoCancelSearch = false;\n                    GlobalFocusGrab.dismiss();\n                } else {\n                    if (!overviewScope.dontAutoCancelSearch) {\n                        searchWidget.cancelSearch();\n                    }\n                    GlobalFocusGrab.addDismissable(panelWindow);\n                }\n            }\n        }\n\n        Connections {\n            target: GlobalFocusGrab\n            function onDismissed() {\n                GlobalStates.overviewOpen = false;\n            }\n        }\n        implicitWidth: columnLayout.implicitWidth\n        implicitHeight: columnLayout.implicitHeight\n\n        function setSearchingText(text) {\n            searchWidget.setSearchingText(text);\n            searchWidget.focusFirstItem();\n        }\n\n        Column {\n            id: columnLayout\n            visible: GlobalStates.overviewOpen\n            anchors {\n                horizontalCenter: parent.horizontalCenter\n                top: parent.top\n            }\n            spacing: -8\n\n            Keys.onPressed: event => {\n                if (event.key === Qt.Key_Escape) {\n                    GlobalStates.overviewOpen = false;\n                }\n            }\n\n            SearchWidget {\n                id: searchWidget\n                anchors.horizontalCenter: parent.horizontalCenter\n                Synchronizer on searchingText {\n                    property alias source: panelWindow.searchingText\n                }\n            }\n\n            Loader {\n                id: overviewLoader\n                anchors.horizontalCenter: parent.horizontalCenter\n                active: GlobalStates.overviewOpen && (Config?.options.overview.enable ?? true)\n                sourceComponent: OverviewWidget {\n                    screen: panelWindow.screen\n                    visible: (panelWindow.searchingText == \"\")\n                }\n            }\n        }\n    }\n\n    function toggleClipboard() {\n        if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) {\n            GlobalStates.overviewOpen = false;\n            return;\n        }\n        overviewScope.dontAutoCancelSearch = true;\n        panelWindow.setSearchingText(Config.options.search.prefix.clipboard);\n        GlobalStates.overviewOpen = true;\n    }\n\n    function toggleEmojis() {\n        if (GlobalStates.overviewOpen && overviewScope.dontAutoCancelSearch) {\n            GlobalStates.overviewOpen = false;\n            return;\n        }\n        overviewScope.dontAutoCancelSearch = true;\n        panelWindow.setSearchingText(Config.options.search.prefix.emojis);\n        GlobalStates.overviewOpen = true;\n    }\n\n    IpcHandler {\n        target: \"search\"\n\n        function toggle() {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n        function workspacesToggle() {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n        function close() {\n            GlobalStates.overviewOpen = false;\n        }\n        function open() {\n            GlobalStates.overviewOpen = true;\n        }\n        function toggleReleaseInterrupt() {\n            GlobalStates.superReleaseMightTrigger = false;\n        }\n        function clipboardToggle() {\n            overviewScope.toggleClipboard();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"searchToggle\"\n        description: \"Toggles search on press\"\n\n        onPressed: {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"overviewWorkspacesClose\"\n        description: \"Closes overview on press\"\n\n        onPressed: {\n            GlobalStates.overviewOpen = false;\n        }\n    }\n    GlobalShortcut {\n        name: \"overviewWorkspacesToggle\"\n        description: \"Toggles overview on press\"\n\n        onPressed: {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"searchToggleRelease\"\n        description: \"Toggles search on release\"\n\n        onPressed: {\n            GlobalStates.superReleaseMightTrigger = true;\n        }\n\n        onReleased: {\n            if (!GlobalStates.superReleaseMightTrigger) {\n                GlobalStates.superReleaseMightTrigger = true;\n                return;\n            }\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"searchToggleReleaseInterrupt\"\n        description: \"Interrupts possibility of search being toggled on release. \" + \"This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. \" + \"To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.\"\n\n        onPressed: {\n            GlobalStates.superReleaseMightTrigger = false;\n        }\n    }\n    GlobalShortcut {\n        name: \"overviewClipboardToggle\"\n        description: \"Toggle clipboard query on overview widget\"\n\n        onPressed: {\n            overviewScope.toggleClipboard();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"overviewEmojiToggle\"\n        description: \"Toggle emoji query on overview widget\"\n\n        onPressed: {\n            overviewScope.toggleEmojis();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overview/OverviewWidget.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nItem {\n    id: root\n    required property var screen\n    readonly property HyprlandMonitor monitor: Hyprland.monitorFor(screen)\n    readonly property var toplevels: ToplevelManager.toplevels\n    // Clamp to avoid lock-screen temp workspace (2147483647 - N) leaking into UI\n    readonly property int effectiveActiveWorkspaceId: Math.max(1, Math.min(100, monitor?.activeWorkspace?.id ?? 1))\n    readonly property int workspacesShown: Config.options.overview.rows * Config.options.overview.columns\n    readonly property int workspaceGroup: Math.floor((effectiveActiveWorkspaceId - 1) / workspacesShown)\n    property bool monitorIsFocused: (Hyprland.focusedMonitor?.name == monitor.name)\n    property var windows: HyprlandData.windowList\n    property var windowByAddress: HyprlandData.windowByAddress\n    property var windowAddresses: HyprlandData.addresses\n    property var monitorData: HyprlandData.monitors.find(m => m.id === root.monitor?.id)\n    property real scale: Config.options.overview.scale\n    property color activeBorderColor: Appearance.colors.colSecondary\n\n    property real workspaceImplicitWidth: (monitorData?.transform % 2 === 1) ? \n        ((monitor.height - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale) :\n        ((monitor.width - monitorData?.reserved[0] - monitorData?.reserved[2]) * root.scale / monitor.scale)\n    property real workspaceImplicitHeight: (monitorData?.transform % 2 === 1) ? \n        ((monitor.width - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale) :\n        ((monitor.height - monitorData?.reserved[1] - monitorData?.reserved[3]) * root.scale / monitor.scale)\n    property real largeWorkspaceRadius: Appearance.rounding.large\n    property real smallWorkspaceRadius: Appearance.rounding.verysmall\n\n    property real workspaceNumberMargin: 80\n    property real workspaceNumberSize: 250 * monitor.scale\n    property int workspaceZ: 0\n    property int windowZ: 1\n    property int windowDraggingZ: 99999\n    property real workspaceSpacing: 5\n\n    property int draggingFromWorkspace: -1\n    property int draggingTargetWorkspace: -1\n\n    implicitWidth: overviewBackground.implicitWidth + Appearance.sizes.elevationMargin * 2\n    implicitHeight: overviewBackground.implicitHeight + Appearance.sizes.elevationMargin * 2\n\n    property Component windowComponent: OverviewWindow {}\n    property list<OverviewWindow> windowWidgets: []\n    \n    function getWsRow(ws) {\n        // 1-indexed workspace, 0-indexed row\n        var normalRow = Math.floor((ws - 1) / Config.options.overview.columns) % Config.options.overview.rows;\n        return (Config.options.overview.orderBottomUp ? Config.options.overview.rows - normalRow - 1 : normalRow);\n    }\n    function getWsColumn(ws) {\n        // 1-indexed workspace, 0-indexed column\n        var normalCol = (ws - 1) % Config.options.overview.columns;\n        return (Config.options.overview.orderRightLeft ? Config.options.overview.columns - normalCol - 1 : normalCol);\n    }\n    function getWsInCell(ri, ci) {\n        // 1-indexed workspace, 0-indexed row and column index\n        return (Config.options.overview.orderBottomUp ? Config.options.overview.rows - ri - 1 : ri) * Config.options.overview.columns + (Config.options.overview.orderRightLeft ? Config.options.overview.columns - ci - 1 : ci) + 1\n    }\n\n    StyledRectangularShadow {\n        target: overviewBackground\n    }\n    Rectangle { // Background\n        id: overviewBackground\n        property real padding: 10\n        anchors.fill: parent\n        anchors.margins: Appearance.sizes.elevationMargin\n\n        implicitWidth: workspaceColumnLayout.implicitWidth + padding * 2\n        implicitHeight: workspaceColumnLayout.implicitHeight + padding * 2\n        radius: root.largeWorkspaceRadius + padding\n        color: Appearance.colors.colBackgroundSurfaceContainer\n\n        Column { // Workspaces\n            id: workspaceColumnLayout\n\n            z: root.workspaceZ\n            anchors.centerIn: parent\n            spacing: workspaceSpacing\n            \n            Repeater {\n                model: Config.options.overview.rows\n                delegate: Row {\n                    id: row\n                    required property int index\n                    spacing: workspaceSpacing\n\n                    Repeater { // Workspace repeater\n                        model: Config.options.overview.columns\n                        Rectangle { // Workspace\n                            id: workspace\n                            required property int index\n                            property int colIndex: index\n                            property int workspaceValue: root.workspaceGroup * root.workspacesShown + getWsInCell(row.index, colIndex)\n                            property color defaultWorkspaceColor: Appearance.colors.colSurfaceContainerLow\n                            property color hoveredWorkspaceColor: ColorUtils.mix(defaultWorkspaceColor, Appearance.colors.colLayer1Hover, 0.1)\n                            property color hoveredBorderColor: Appearance.colors.colLayer2Hover\n                            property bool hoveredWhileDragging: false\n\n                            implicitWidth: root.workspaceImplicitWidth\n                            implicitHeight: root.workspaceImplicitHeight\n                            color: hoveredWhileDragging ? hoveredWorkspaceColor : defaultWorkspaceColor\n                            property bool workspaceAtLeft: colIndex === 0\n                            property bool workspaceAtRight: colIndex === Config.options.overview.columns - 1\n                            property bool workspaceAtTop: row.index === 0\n                            property bool workspaceAtBottom: row.index === Config.options.overview.rows - 1\n                            topLeftRadius: (workspaceAtLeft && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                            topRightRadius: (workspaceAtRight && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                            bottomLeftRadius: (workspaceAtLeft && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                            bottomRightRadius: (workspaceAtRight && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                            border.width: 2\n                            border.color: hoveredWhileDragging ? hoveredBorderColor : \"transparent\"\n\n                            StyledText {\n                                anchors.centerIn: parent\n                                text: workspace.workspaceValue\n                                font {\n                                    pixelSize: root.workspaceNumberSize * root.scale\n                                    weight: Font.DemiBold\n                                    family: Appearance.font.family.expressive\n                                }\n                                color: ColorUtils.transparentize(Appearance.colors.colOnLayer1, 0.8)\n                                horizontalAlignment: Text.AlignHCenter\n                                verticalAlignment: Text.AlignVCenter\n                            }\n\n                            MouseArea {\n                                id: workspaceArea\n                                anchors.fill: parent\n                                acceptedButtons: Qt.LeftButton\n                                onPressed: {\n                                    if (root.draggingTargetWorkspace === -1) {\n                                        GlobalStates.overviewOpen = false\n                                        Hyprland.dispatch(`hl.dsp.focus({ workspace = ${workspace.workspaceValue} })`)\n                                    }\n                                }\n                            }\n\n                            DropArea {\n                                anchors.fill: parent\n                                onEntered: {\n                                    root.draggingTargetWorkspace = workspace.workspaceValue\n                                    if (root.draggingFromWorkspace == root.draggingTargetWorkspace) return;\n                                    hoveredWhileDragging = true\n                                }\n                                onExited: {\n                                    hoveredWhileDragging = false\n                                    if (root.draggingTargetWorkspace == workspace.workspaceValue) root.draggingTargetWorkspace = -1\n                                }\n                            }\n\n                        }\n                    }\n                }\n            }\n        }\n\n        Item { // Windows & focused workspace indicator\n            id: windowSpace\n            anchors.centerIn: parent\n            implicitWidth: workspaceColumnLayout.implicitWidth\n            implicitHeight: workspaceColumnLayout.implicitHeight\n\n            Repeater { // Window repeater\n                model: ScriptModel {\n                    values: {\n                        // console.log(JSON.stringify(ToplevelManager.toplevels.values.map(t => t), null, 2))\n                        return ToplevelManager.toplevels.values.filter((toplevel) => {\n                            const address = `0x${toplevel.HyprlandToplevel?.address}`\n                            var win = windowByAddress[address]\n                            const inWorkspaceGroup = (root.workspaceGroup * root.workspacesShown < win?.workspace?.id && win?.workspace?.id <= (root.workspaceGroup + 1) * root.workspacesShown)\n                            return inWorkspaceGroup;\n                        })\n                    }\n                }\n                delegate: OverviewWindow {\n                    id: window\n                    required property var modelData\n                    property int monitorId: windowData?.monitor\n                    property var monitor: HyprlandData.monitors.find(m => m.id == monitorId)\n                    property var address: `0x${modelData.HyprlandToplevel.address}`\n                    toplevel: modelData\n                    monitorData: this.monitor\n                    scale: root.scale\n                    widgetMonitor: HyprlandData.monitors.find(m => m.id == root.monitor.id)\n                    windowData: windowByAddress[address]\n\n                    property bool atInitPosition: (initX == x && initY == y)\n\n                    // Offset on the canvas\n                    property int workspaceColIndex: getWsColumn(windowData?.workspace.id)\n                    property int workspaceRowIndex: getWsRow(windowData?.workspace.id)\n                    xOffset: (root.workspaceImplicitWidth + workspaceSpacing) * workspaceColIndex\n                    yOffset: (root.workspaceImplicitHeight + workspaceSpacing) * workspaceRowIndex\n                    property real xWithinWorkspaceWidget: Math.max((windowData?.at[0] - (monitor?.x ?? 0) - monitorData?.reserved[0]) * root.scale, 0)\n                    property real yWithinWorkspaceWidget: Math.max((windowData?.at[1] - (monitor?.y ?? 0) - monitorData?.reserved[1]) * root.scale, 0)\n\n                    // Radius\n                    property real minRadius: Appearance.rounding.small\n                    property bool workspaceAtLeft: workspaceColIndex === 0\n                    property bool workspaceAtRight: workspaceColIndex === Config.options.overview.columns - 1\n                    property bool workspaceAtTop: workspaceRowIndex === 0\n                    property bool workspaceAtBottom: workspaceRowIndex === Config.options.overview.rows - 1\n                    property bool workspaceAtTopLeft: (workspaceAtLeft && workspaceAtTop) \n                    property bool workspaceAtTopRight: (workspaceAtRight && workspaceAtTop) \n                    property bool workspaceAtBottomLeft: (workspaceAtLeft && workspaceAtBottom) \n                    property bool workspaceAtBottomRight: (workspaceAtRight && workspaceAtBottom) \n                    property real distanceFromLeftEdge: xWithinWorkspaceWidget\n                    property real distanceFromRightEdge: root.workspaceImplicitWidth - (xWithinWorkspaceWidget + targetWindowWidth)\n                    property real distanceFromTopEdge: yWithinWorkspaceWidget\n                    property real distanceFromBottomEdge: root.workspaceImplicitHeight - (yWithinWorkspaceWidget + targetWindowHeight)\n                    property real distanceFromTopLeftCorner: Math.max(distanceFromLeftEdge, distanceFromTopEdge)\n                    property real distanceFromTopRightCorner: Math.max(distanceFromRightEdge, distanceFromTopEdge)\n                    property real distanceFromBottomLeftCorner: Math.max(distanceFromLeftEdge, distanceFromBottomEdge)\n                    property real distanceFromBottomRightCorner: Math.max(distanceFromRightEdge, distanceFromBottomEdge)\n                    topLeftRadius: Math.max((workspaceAtTopLeft ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromTopLeftCorner, minRadius)\n                    topRightRadius: Math.max((workspaceAtTopRight ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromTopRightCorner, minRadius)\n                    bottomLeftRadius: Math.max((workspaceAtBottomLeft ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromBottomLeftCorner, minRadius)\n                    bottomRightRadius: Math.max((workspaceAtBottomRight ? root.largeWorkspaceRadius : root.smallWorkspaceRadius) - distanceFromBottomRightCorner, minRadius)\n\n                    Timer {\n                        id: updateWindowPosition\n                        interval: Config.options.hacks.arbitraryRaceConditionDelay\n                        repeat: false\n                        running: false\n                        onTriggered: {\n                            window.x = Math.round(xWithinWorkspaceWidget + xOffset)\n                            window.y = Math.round(yWithinWorkspaceWidget + yOffset)\n                        }\n                    }\n\n                    z: Drag.active ? root.windowDraggingZ : (root.windowZ + windowData?.floating + windowData?.fullscreen * 2)\n                    Drag.hotSpot.x: width / 2\n                    Drag.hotSpot.y: height / 2\n                    MouseArea {\n                        id: dragArea\n                        anchors.fill: parent\n                        hoverEnabled: true\n                        onEntered: hovered = true // For hover color change\n                        onExited: hovered = false // For hover color change\n                        acceptedButtons: Qt.LeftButton | Qt.MiddleButton\n                        drag.target: parent\n                        onPressed: (mouse) => {\n                            root.draggingFromWorkspace = windowData?.workspace.id\n                            window.pressed = true\n                            window.Drag.active = true\n                            window.Drag.source = window\n                            window.Drag.hotSpot.x = mouse.x\n                            window.Drag.hotSpot.y = mouse.y\n                            // console.log(`[OverviewWindow] Dragging window ${windowData?.address} from position (${window.x}, ${window.y})`)\n                        }\n                        onReleased: {\n                            const targetWorkspace = root.draggingTargetWorkspace\n                            window.pressed = false\n                            window.Drag.active = false\n                            root.draggingFromWorkspace = -1\n                            if (targetWorkspace !== -1 && targetWorkspace !== windowData?.workspace.id) {\n                                Hyprland.dispatch(`hl.dsp.window.move({ workspace = ${targetWorkspace}, follow = false, window = \"address:${window.windowData?.address}\" })`)\n                                updateWindowPosition.restart()\n                            }\n                            else {\n                                if (!window.windowData.floating) {\n                                    updateWindowPosition.restart()\n                                    return\n                                }\n                                const percentageX = (window.x - xOffset) / root.workspaceImplicitWidth\n                                const percentageY = (window.y - yOffset) / root.workspaceImplicitHeight\n                                Hyprland.dispatch(`hl.dsp.window.move({ x = \"${percentageX * root.screen.width}\", y = \"${percentageY * root.screen.height}\", window = \"address:${window.windowData?.address}\" })`)\n                            }\n                        }\n                        onClicked: (event) => {\n                            if (!windowData) return;\n\n                            if (event.button === Qt.LeftButton) {\n                                GlobalStates.overviewOpen = false\n                                Hyprland.dispatch(`hl.dsp.focus({window = \"address:${windowData.address}\"})`)\n                                event.accepted = true\n                            } else if (event.button === Qt.MiddleButton) {\n                                Hyprland.dispatch(`hl.dsp.window.close({window = \"address:${windowData.address}\"})`)\n                                event.accepted = true\n                            }\n                        }\n\n                        StyledToolTip {\n                            extraVisibleCondition: false\n                            alternativeVisibleCondition: dragArea.containsMouse && !window.Drag.active\n                            text: `${windowData?.title}\\n[${windowData?.class}] ${windowData?.xwayland ? \"[XWayland] \" : \"\"}`\n                        }\n                    }\n                }\n            }\n\n            Rectangle { // Focused workspace indicator\n                id: focusedWorkspaceIndicator\n                property int rowIndex: getWsRow(root.effectiveActiveWorkspaceId)\n                property int colIndex: getWsColumn(root.effectiveActiveWorkspaceId)\n                x: (root.workspaceImplicitWidth + workspaceSpacing) * colIndex\n                y: (root.workspaceImplicitHeight + workspaceSpacing) * rowIndex\n                z: root.windowZ\n                width: root.workspaceImplicitWidth\n                height: root.workspaceImplicitHeight\n                color: \"transparent\"\n                property bool workspaceAtLeft: colIndex === 0\n                property bool workspaceAtRight: colIndex === Config.options.overview.columns - 1\n                property bool workspaceAtTop: rowIndex === 0\n                property bool workspaceAtBottom: rowIndex === Config.options.overview.rows - 1\n                topLeftRadius: (workspaceAtLeft && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                topRightRadius: (workspaceAtRight && workspaceAtTop) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                bottomLeftRadius: (workspaceAtLeft && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                bottomRightRadius: (workspaceAtRight && workspaceAtBottom) ? root.largeWorkspaceRadius : root.smallWorkspaceRadius\n                border.width: 2\n                border.color: root.activeBorderColor\n                Behavior on x {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                Behavior on y {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                Behavior on topLeftRadius {\n                    animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n                }\n                Behavior on topRightRadius {\n                    animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n                }\n                Behavior on bottomLeftRadius {\n                    animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n                }\n                Behavior on bottomRightRadius {\n                    animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overview/OverviewWindow.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Wayland\n\nItem { // Window\n    id: root\n    property var toplevel\n    property var windowData\n    property var monitorData\n    property var scale\n    property bool restrictToWorkspace: true\n    property real widthRatio: {\n        const widgetWidth = widgetMonitor.transform & 1 ? widgetMonitor.height : widgetMonitor.width;\n        const monitorWidth = monitorData.transform & 1 ? monitorData.height : monitorData.width;\n        return (widgetWidth * monitorData.scale) / (monitorWidth * widgetMonitor.scale);\n    }\n    property real heightRatio: {\n        const widgetHeight = widgetMonitor.transform & 1 ? widgetMonitor.width : widgetMonitor.height;\n        const monitorHeight = monitorData.transform & 1 ? monitorData.width : monitorData.height;\n        return (widgetHeight * monitorData.scale) / (monitorHeight * widgetMonitor.scale);\n    }\n    property real initX: {\n        return Math.max((windowData?.at[0] - (monitorData?.x ?? 0) - monitorData?.reserved[0]) * widthRatio * root.scale, 0) + xOffset;\n    }\n\n    property real initY: {\n        return Math.max((windowData?.at[1] - (monitorData?.y ?? 0) - monitorData?.reserved[1]) * heightRatio * root.scale, 0) + yOffset;\n    }\n    property real xOffset: 0\n    property real yOffset: 0\n    property var widgetMonitor\n    property int widgetMonitorId: widgetMonitor.id\n\n    property var targetWindowWidth: windowData?.size[0] * scale * widthRatio\n    property var targetWindowHeight: windowData?.size[1] * scale * heightRatio\n    property bool hovered: false\n    property bool pressed: false\n\n    property bool centerIcons: Config.options.overview.centerIcons\n    property real iconGapRatio: 0.06\n    property real iconToWindowRatio: centerIcons ? 0.35 : 0.15\n    property real xwaylandIndicatorToIconRatio: 0.35\n    property real iconToWindowRatioCompact: 0.6\n    property string iconPath: Quickshell.iconPath(AppSearch.guessIcon(windowData?.class), \"image-missing\")\n    property bool compactMode: Appearance.font.pixelSize.smaller * 4 > targetWindowHeight || Appearance.font.pixelSize.smaller * 4 > targetWindowWidth\n\n    property bool indicateXWayland: windowData?.xwayland ?? false\n\n    x: initX\n    y: initY\n    width: targetWindowWidth\n    height: targetWindowHeight\n    opacity: windowData.monitor == widgetMonitorId ? 1 : 0.4\n\n    property real topLeftRadius\n    property real topRightRadius\n    property real bottomLeftRadius\n    property real bottomRightRadius\n\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Rectangle {\n            width: root.width\n            height: root.height\n            topLeftRadius: root.topLeftRadius\n            topRightRadius: root.topRightRadius\n            bottomRightRadius: root.bottomRightRadius\n            bottomLeftRadius: root.bottomLeftRadius\n        }\n    }\n\n    Behavior on x {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n    Behavior on y {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n    Behavior on width {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n    Behavior on height {\n        animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n    }\n\n    ScreencopyView {\n        id: windowPreview\n        anchors.fill: parent\n        captureSource: GlobalStates.overviewOpen ? root.toplevel : null\n        live: true\n\n        // Color overlay for interactions\n        Rectangle {\n            anchors.fill: parent\n            topLeftRadius: root.topLeftRadius\n            topRightRadius: root.topRightRadius\n            bottomRightRadius: root.bottomRightRadius\n            bottomLeftRadius: root.bottomLeftRadius\n            color: pressed ? ColorUtils.transparentize(Appearance.colors.colLayer2Active, 0.5) : \n                hovered ? ColorUtils.transparentize(Appearance.colors.colLayer2Hover, 0.7) : \n                ColorUtils.transparentize(Appearance.colors.colLayer2)\n            border.color : ColorUtils.transparentize(Appearance.m3colors.m3outline, 0.88)\n            border.width : 1\n        }\n\n        StyledImage {\n            id: windowIcon\n            property real baseSize: Math.min(root.targetWindowWidth, root.targetWindowHeight)\n            anchors {\n                top: root.centerIcons ? undefined : parent.top\n                left: root.centerIcons ? undefined : parent.left\n                centerIn: root.centerIcons ? parent : undefined\n                margins: baseSize * root.iconGapRatio\n            }\n            property var iconSize: {\n                // console.log(\"-=-=-\", root.toplevel.title, \"-=-=-\")\n                // console.log(\"Target window size:\", targetWindowWidth, targetWindowHeight)\n                // console.log(\"Icon ratio:\", root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio)\n                // console.log(\"Scale:\", root.monitorData.scale)\n                // console.log(\"Final:\", Math.min(targetWindowWidth, targetWindowHeight) * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio) / root.monitorData.scale)\n                return baseSize * (root.compactMode ? root.iconToWindowRatioCompact : root.iconToWindowRatio);\n            }\n            mipmap: true\n            Layout.alignment: Qt.AlignHCenter\n            source: root.iconPath\n            width: iconSize\n            height: iconSize\n\n            Behavior on width {\n                animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n            }\n            Behavior on height {\n                animation: Appearance.animation.elementMoveEnter.numberAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overview/SearchBar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nRowLayout {\n    id: root\n    spacing: 6\n    property bool animateWidth: false\n    property alias searchInput: searchInput\n    property string searchingText\n\n    function forceFocus() {\n        searchInput.forceActiveFocus();\n    }\n\n    enum SearchPrefixType { Action, App, Clipboard, Emojis, Math, ShellCommand, WebSearch, DefaultSearch }\n\n    property var searchPrefixType: {\n        if (root.searchingText.startsWith(Config.options.search.prefix.action)) return SearchBar.SearchPrefixType.Action;\n        if (root.searchingText.startsWith(Config.options.search.prefix.app)) return SearchBar.SearchPrefixType.App;\n        if (root.searchingText.startsWith(Config.options.search.prefix.clipboard)) return SearchBar.SearchPrefixType.Clipboard;\n        if (root.searchingText.startsWith(Config.options.search.prefix.emojis)) return SearchBar.SearchPrefixType.Emojis;\n        if (root.searchingText.startsWith(Config.options.search.prefix.math)) return SearchBar.SearchPrefixType.Math;\n        if (root.searchingText.startsWith(Config.options.search.prefix.shellCommand)) return SearchBar.SearchPrefixType.ShellCommand;\n        if (root.searchingText.startsWith(Config.options.search.prefix.webSearch)) return SearchBar.SearchPrefixType.WebSearch;\n        return SearchBar.SearchPrefixType.DefaultSearch;\n    }\n    \n    MaterialShapeWrappedMaterialSymbol {\n        id: searchIcon\n        Layout.alignment: Qt.AlignVCenter\n        iconSize: Appearance.font.pixelSize.huge\n        shape: switch(root.searchPrefixType) {\n            case SearchBar.SearchPrefixType.Action: return MaterialShape.Shape.Pill;\n            case SearchBar.SearchPrefixType.App: return MaterialShape.Shape.Clover4Leaf;\n            case SearchBar.SearchPrefixType.Clipboard: return MaterialShape.Shape.Gem;\n            case SearchBar.SearchPrefixType.Emojis: return MaterialShape.Shape.Sunny;\n            case SearchBar.SearchPrefixType.Math: return MaterialShape.Shape.PuffyDiamond;\n            case SearchBar.SearchPrefixType.ShellCommand: return MaterialShape.Shape.PixelCircle;\n            case SearchBar.SearchPrefixType.WebSearch: return MaterialShape.Shape.SoftBurst;\n            default: return MaterialShape.Shape.Cookie7Sided;\n        }\n        text: switch (root.searchPrefixType) {\n            case SearchBar.SearchPrefixType.Action: return \"settings_suggest\";\n            case SearchBar.SearchPrefixType.App: return \"apps\";\n            case SearchBar.SearchPrefixType.Clipboard: return \"content_paste_search\";\n            case SearchBar.SearchPrefixType.Emojis: return \"add_reaction\";\n            case SearchBar.SearchPrefixType.Math: return \"calculate\";\n            case SearchBar.SearchPrefixType.ShellCommand: return \"terminal\";\n            case SearchBar.SearchPrefixType.WebSearch: return \"travel_explore\";\n            case SearchBar.SearchPrefixType.DefaultSearch: return \"search\";\n            default: return \"search\";\n        }\n    }\n    ToolbarTextField { // Search box\n        id: searchInput\n        Layout.topMargin: 4\n        Layout.bottomMargin: 4\n        implicitHeight: 40\n        focus: GlobalStates.overviewOpen\n        font.pixelSize: Appearance.font.pixelSize.small\n        placeholderText: Translation.tr(\"Search, calculate or run\")\n        implicitWidth: root.searchingText == \"\" ? Appearance.sizes.searchWidthCollapsed : Appearance.sizes.searchWidth\n\n        Behavior on implicitWidth {\n            id: searchWidthBehavior\n            enabled: root.animateWidth\n            NumberAnimation {\n                duration: 300\n                easing.type: Appearance.animation.elementMove.type\n                easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n            }\n        }\n\n        onTextChanged: LauncherSearch.query = text\n\n        onAccepted: {\n            if (appResults.count > 0) {\n                // Get the first visible delegate and trigger its click\n                let firstItem = appResults.itemAtIndex(0);\n                if (firstItem && firstItem.clicked) {\n                    firstItem.clicked();\n                }\n            }\n        }\n\n        Keys.onPressed: event => {\n            if (event.key === Qt.Key_Tab) {\n                if (LauncherSearch.results.length === 0) return;\n                const tabbedText = LauncherSearch.results[0].name;\n                LauncherSearch.query = tabbedText;\n                searchInput.text = tabbedText;\n                event.accepted = true;\n            }\n        }\n    }\n\n    IconToolbarButton {\n        Layout.topMargin: 4\n        Layout.bottomMargin: 4\n        onClicked: {\n            GlobalStates.overviewOpen = false;\n            Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"region\", \"search\"]);\n        }\n        text: \"image_search\"\n        StyledToolTip {\n            text: Translation.tr(\"Google Lens\")\n        }\n    }\n\n    IconToolbarButton {\n        id: songRecButton\n        Layout.topMargin: 4\n        Layout.bottomMargin: 4\n        Layout.rightMargin: 4\n        toggled: SongRec.running\n        onClicked: SongRec.toggleRunning()\n        text: \"music_cast\"\n\n        StyledToolTip {\n            text: Translation.tr(\"Recognize music\")\n        }\n\n        colText: toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSurfaceVariant\n        background: MaterialShape {\n            RotationAnimation on rotation {\n                running: songRecButton.toggled\n                duration: 12000\n                easing.type: Easing.Linear\n                loops: Animation.Infinite\n                from: 0\n                to: 360\n            }\n            shape: {\n                if (songRecButton.down) {\n                    return songRecButton.toggled ? MaterialShape.Shape.Circle : MaterialShape.Shape.Square\n                } else {\n                    return songRecButton.toggled ? MaterialShape.Shape.SoftBurst : MaterialShape.Shape.Circle\n                }\n            }\n            color: {\n                if (songRecButton.toggled) {\n                    return songRecButton.hovered ? Appearance.colors.colPrimaryHover : Appearance.colors.colPrimary\n                } else {\n                    return songRecButton.hovered ? Appearance.colors.colSurfaceContainerHigh : ColorUtils.transparentize(Appearance.colors.colSurfaceContainerHigh)\n                }\n            }\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overview/SearchItem.qml",
    "content": "// pragma NativeMethodBehavior: AcceptThisObject\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\nimport Quickshell.Hyprland\n\nRippleButton {\n    id: root\n    property LauncherSearchResult entry\n    property string query\n    property bool entryShown: entry?.shown ?? true\n    property string itemType: entry?.type ?? Translation.tr(\"App\")\n    property string itemName: entry?.name ?? \"\"\n    property var iconType: entry?.iconType\n    property string iconName: entry?.iconName ?? \"\"\n    property var itemExecute: entry?.execute\n    property var fontType: switch(entry?.fontType) {\n        case LauncherSearchResult.FontType.Monospace:\n            return \"monospace\"\n        case LauncherSearchResult.FontType.Normal:\n            return \"main\"\n        default:\n            return \"main\"\n    }\n    property string itemClickActionName: entry?.verb ?? \"Open\"\n    property string bigText: entry?.iconType === LauncherSearchResult.IconType.Text ? entry?.iconName ?? \"\" : \"\"\n    property string materialSymbol: entry.iconType === LauncherSearchResult.IconType.Material ? entry?.iconName ?? \"\" : \"\"\n    property string cliphistRawString: entry?.rawValue ?? \"\"\n    property bool blurImage: entry?.blurImage ?? false\n    \n    visible: root.entryShown\n    property int horizontalMargin: 10\n    property int buttonHorizontalPadding: 10\n    property int buttonVerticalPadding: 6\n    property bool keyboardDown: false\n    readonly property bool selected: (root.hovered || root.focus)\n\n    implicitHeight: rowLayout.implicitHeight + root.buttonVerticalPadding * 2\n    implicitWidth: rowLayout.implicitWidth + root.buttonHorizontalPadding * 2\n    buttonRadius: Appearance.rounding.normal\n    colBackground: (root.down || root.keyboardDown) ? Appearance.colors.colPrimaryContainerActive : \n        (selected ? Appearance.colors.colPrimaryContainer : \n        ColorUtils.transparentize(Appearance.colors.colPrimaryContainer, 1))\n    colBackgroundHover: Appearance.colors.colPrimaryContainer\n    colRipple: Appearance.colors.colPrimaryContainerActive\n    property color colForeground: selected ? Appearance.colors.colOnPrimaryContainer : Appearance.m3colors.m3onSurface\n\n    readonly property string highlightPrefix: `<u><font color=\"${Appearance.colors.colPrimary}\">`\n    readonly property string highlightSuffix: `</font></u>`\n    // Note that this highlighting is independent from the search\n    // It's close, but does not accurately represent how the fuzzy algorithm works\n    function highlightContent(content, query) {\n        if (!query || query.length === 0 || content == query || fontType === \"monospace\")\n            return StringUtils.escapeHtml(content);\n\n        let contentLower = content.toLowerCase();\n        let queryLower = query.toLowerCase();\n\n        let result = \"\";\n        let lastIndex = 0;\n        let qIndex = 0;\n\n        for (let i = 0; i < content.length && qIndex < query.length; i++) {\n            if (contentLower[i] === queryLower[qIndex]) {\n                // Add non-highlighted part (escaped)\n                if (i > lastIndex)\n                    result += StringUtils.escapeHtml(content.slice(lastIndex, i));\n                // Add highlighted character (escaped)\n                result += root.highlightPrefix + StringUtils.escapeHtml(content[i]) + root.highlightSuffix;\n                lastIndex = i + 1;\n                qIndex++;\n            }\n        }\n        // Add the rest of the string (escaped)\n        if (lastIndex < content.length)\n            result += StringUtils.escapeHtml(content.slice(lastIndex));\n\n        return result;\n    }\n    property string displayContent: highlightContent(root.itemName, root.query)\n\n    property list<string> urls: {\n        if (!root.itemName) return [];\n        // Regular expression to match URLs\n        const urlRegex = /https?:\\/\\/[^\\s<>\"{}|\\\\^`[\\]]+/gi;\n        const matches = root.itemName?.match(urlRegex)\n            ?.filter(url => !url.includes(\"…\")) // Elided = invalid\n        return matches ? matches : [];\n    }\n    \n    PointingHandInteraction {}\n\n    background {\n        anchors.fill: root\n        anchors.leftMargin: root.horizontalMargin\n        anchors.rightMargin: root.horizontalMargin\n    }\n\n    onClicked: {\n        GlobalStates.overviewOpen = false\n        root.itemExecute()\n    }\n    Keys.onPressed: (event) => {\n        if (event.key === Qt.Key_Delete && event.modifiers === Qt.ShiftModifier) {\n            const deleteAction = root.entry.actions.find(action => action.name == Translation.tr(\"Delete\"));\n\n            if (deleteAction) {\n                deleteAction.execute()\n            }\n        } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            root.keyboardDown = true\n            root.clicked()\n            event.accepted = true;\n        }\n    }\n    Keys.onReleased: (event) => {\n        if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            root.keyboardDown = false\n            event.accepted = true;\n        }\n    }\n\n    RowLayout {\n        id: rowLayout\n        spacing: iconLoader.sourceComponent === null ? 0 : 10\n        anchors.fill: parent\n        anchors.leftMargin: root.horizontalMargin + root.buttonHorizontalPadding\n        anchors.rightMargin: root.horizontalMargin + root.buttonHorizontalPadding\n\n        // Icon\n        Loader {\n            id: iconLoader\n            active: true\n            sourceComponent: switch(root.iconType) {\n                case LauncherSearchResult.IconType.Material:\n                    return materialSymbolComponent\n                case LauncherSearchResult.IconType.Text:\n                    return bigTextComponent\n                case LauncherSearchResult.IconType.System:\n                    return iconImageComponent\n                case LauncherSearchResult.IconType.None:\n                    return null\n                default:\n                    return null\n            }\n        }\n\n        Component {\n            id: iconImageComponent\n            IconImage {\n                source: Quickshell.iconPath(root.iconName, \"image-missing\")\n                width: 35\n                height: 35\n            }\n        }\n\n        Component {\n            id: materialSymbolComponent\n            MaterialSymbol {\n                text: root.materialSymbol\n                iconSize: 30\n                color: root.colForeground\n            }\n        }\n\n        Component {\n            id: bigTextComponent\n            StyledText {\n                text: root.bigText\n                font.pixelSize: Appearance.font.pixelSize.larger\n                color: root.colForeground\n            }\n        }\n\n        // Main text\n        ColumnLayout {\n            id: contentColumn\n            Layout.fillWidth: true\n            Layout.alignment: Qt.AlignVCenter\n            spacing: 0\n            StyledText {\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                color: root.selected ? Appearance.colors.colOnPrimaryContainer : Appearance.colors.colSubtext\n                visible: root.itemType && root.itemType != Translation.tr(\"App\")\n                text: root.itemType\n            }\n            RowLayout {\n                Loader { // Checkmark for copied clipboard entry\n                    visible: itemName == Quickshell.clipboardText && root.cliphistRawString\n                    active: itemName == Quickshell.clipboardText && root.cliphistRawString\n                    sourceComponent: Rectangle {\n                        implicitWidth: activeText.implicitHeight\n                        implicitHeight: activeText.implicitHeight\n                        radius: Appearance.rounding.full\n                        color: Appearance.colors.colPrimary\n                        MaterialSymbol {\n                            id: activeText\n                            anchors.centerIn: parent\n                            text: \"check\"\n                            font.pixelSize: Appearance.font.pixelSize.normal\n                            color: Appearance.m3colors.m3onPrimary\n                        }\n                    }\n                }\n                Repeater { // Favicons for links\n                    model: root.query == root.itemName ? [] : root.urls\n                    Favicon {\n                        required property var modelData\n                        size: parent.height\n                        url: modelData\n                    }\n                }\n                StyledText { // Item name/content\n                    Layout.fillWidth: true\n                    id: nameText\n                    textFormat: Text.StyledText // RichText also works, but StyledText ensures elide work\n                    font.pixelSize: Appearance.font.pixelSize.small\n                    font.family: Appearance.font.family[root.fontType]\n                    color: root.colForeground\n                    horizontalAlignment: Text.AlignLeft\n                    elide: Text.ElideRight\n                    text: root.selected ? root.itemName : root.displayContent\n                }\n            }\n            Loader { // Clipboard image preview\n                active: root.cliphistRawString && Cliphist.entryIsImage(root.cliphistRawString)\n                sourceComponent: CliphistImage {\n                    Layout.fillWidth: true\n                    entry: root.cliphistRawString\n                    maxWidth: contentColumn.width\n                    maxHeight: 140\n                    blur: root.blurImage\n                }\n            }\n        }\n\n        // Action text\n        StyledText {\n            Layout.fillWidth: false\n            visible: root.selected\n            id: clickAction\n            font.pixelSize: Appearance.font.pixelSize.normal\n            color: Appearance.colors.colOnPrimaryContainer\n            horizontalAlignment: Text.AlignRight\n            text: root.itemClickActionName\n        }\n\n        RowLayout {\n            Layout.alignment: Qt.AlignTop\n            Layout.topMargin: root.buttonVerticalPadding\n            Layout.bottomMargin: -root.buttonVerticalPadding // Why is this necessary? Good question.\n            spacing: 4\n            Repeater {\n                model: (root.entry.actions ?? []).slice(0, 4)\n                delegate: RippleButton {\n                    id: actionButton\n                    required property var modelData\n                    property var iconType: modelData.iconType\n                    property string iconName: modelData.iconName ?? \"\"\n                    implicitHeight: 34\n                    implicitWidth: 34\n\n                    colBackgroundHover: Appearance.colors.colSecondaryContainerHover\n                    colRipple: Appearance.colors.colSecondaryContainerActive\n\n                    contentItem: Item {\n                        id: actionContentItem\n                        anchors.centerIn: parent\n                        Loader {\n                            anchors.centerIn: parent\n                            active: actionButton.iconType === LauncherSearchResult.IconType.Material || actionButton.iconName === \"\"\n                            sourceComponent: MaterialSymbol {\n                                text: actionButton.iconName || \"video_settings\"\n                                font.pixelSize: Appearance.font.pixelSize.hugeass\n                                color: root.colForeground\n                            }\n                        }\n                        Loader {\n                            anchors.centerIn: parent\n                            active: actionButton.iconType === LauncherSearchResult.IconType.System && actionButton.iconName !== \"\"\n                            sourceComponent: IconImage {\n                                source: Quickshell.iconPath(actionButton.iconName)\n                                implicitSize: 20\n                            }\n                        }\n                    }\n\n                    onClicked: modelData.execute()\n\n                    StyledToolTip {\n                        text: modelData.name\n                    }\n                }\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/overview/SearchWidget.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport Qt.labs.synchronizer\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\n\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nItem { // Wrapper\n    id: root\n\n    readonly property string xdgConfigHome: Directories.config\n    readonly property int typingDebounceInterval: 200\n    readonly property int typingResultLimit: 15 // Should be enough to cover the whole view\n\n    property string searchingText: LauncherSearch.query\n    property bool showResults: searchingText != \"\"\n    implicitWidth: searchWidgetContent.implicitWidth + Appearance.sizes.elevationMargin * 2\n    implicitHeight: searchWidgetContent.implicitHeight + searchBar.verticalPadding * 2 + Appearance.sizes.elevationMargin * 2\n\n    function focusFirstItem() {\n        appResults.currentIndex = 0;\n    }\n\n    function focusSearchInput() {\n        searchBar.forceFocus();\n    }\n\n    function disableExpandAnimation() {\n        searchBar.animateWidth = false;\n    }\n\n    function cancelSearch() {\n        searchBar.searchInput.selectAll();\n        LauncherSearch.query = \"\";\n        searchBar.animateWidth = true;\n    }\n\n    function setSearchingText(text) {\n        searchBar.searchInput.text = text;\n        LauncherSearch.query = text;\n    }\n\n    Keys.onPressed: event => {\n        // Prevent Esc and Backspace from registering\n        if (event.key === Qt.Key_Escape)\n            return;\n\n        // Handle Backspace: focus and delete character if not focused\n        if (event.key === Qt.Key_Backspace) {\n            if (!searchBar.searchInput.activeFocus) {\n                root.focusSearchInput();\n                if (event.modifiers & Qt.ControlModifier) {\n                    // Delete word before cursor\n                    let text = searchBar.searchInput.text;\n                    let pos = searchBar.searchInput.cursorPosition;\n                    if (pos > 0) {\n                        // Find the start of the previous word\n                        let left = text.slice(0, pos);\n                        let match = left.match(/(\\s*\\S+)\\s*$/);\n                        let deleteLen = match ? match[0].length : 1;\n                        searchBar.searchInput.text = text.slice(0, pos - deleteLen) + text.slice(pos);\n                        searchBar.searchInput.cursorPosition = pos - deleteLen;\n                    }\n                } else {\n                    // Delete character before cursor if any\n                    if (searchBar.searchInput.cursorPosition > 0) {\n                        searchBar.searchInput.text = searchBar.searchInput.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.searchInput.text.slice(searchBar.searchInput.cursorPosition);\n                        searchBar.searchInput.cursorPosition -= 1;\n                    }\n                }\n                // Always move cursor to end after programmatic edit\n                searchBar.searchInput.cursorPosition = searchBar.searchInput.text.length;\n                event.accepted = true;\n            }\n            // If already focused, let TextField handle it\n            return;\n        }\n\n        // Only handle visible printable characters (ignore control chars, arrows, etc.)\n        if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc.\n        {\n            if (!searchBar.searchInput.activeFocus) {\n                root.focusSearchInput();\n                // Insert the character at the cursor position\n                searchBar.searchInput.text = searchBar.searchInput.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.searchInput.text.slice(searchBar.searchInput.cursorPosition);\n                searchBar.searchInput.cursorPosition += 1;\n                event.accepted = true;\n                root.focusFirstItem();\n            }\n        }\n    }\n\n    StyledRectangularShadow {\n        target: searchWidgetContent\n    }\n    Rectangle { // Background\n        id: searchWidgetContent\n        anchors {\n            top: parent.top\n            horizontalCenter: parent.horizontalCenter\n            topMargin: Appearance.sizes.elevationMargin\n        }\n        clip: true\n        implicitWidth: columnLayout.implicitWidth\n        implicitHeight: columnLayout.implicitHeight\n        radius: searchBar.height / 2 + searchBar.verticalPadding\n        color: Appearance.colors.colBackgroundSurfaceContainer\n\n        Behavior on implicitHeight {\n            id: searchHeightBehavior\n            enabled: GlobalStates.overviewOpen && root.showResults\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n\n        ColumnLayout {\n            id: columnLayout\n            anchors {\n                top: parent.top\n                horizontalCenter: parent.horizontalCenter\n            }\n            spacing: 0\n\n            // clip: true\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: searchWidgetContent.width\n                    height: searchWidgetContent.width\n                    radius: searchWidgetContent.radius\n                }\n            }\n\n            SearchBar {\n                id: searchBar\n                property real verticalPadding: 4\n                Layout.fillWidth: true\n                Layout.leftMargin: 10\n                Layout.rightMargin: 4\n                Layout.topMargin: verticalPadding\n                Layout.bottomMargin: verticalPadding\n                Synchronizer on searchingText {\n                    property alias source: root.searchingText\n                }\n            }\n\n            Rectangle {\n                // Separator\n                visible: root.showResults\n                Layout.fillWidth: true\n                height: 1\n                color: Appearance.colors.colOutlineVariant\n            }\n\n            ListView { // App results\n                id: appResults\n                visible: root.showResults\n                Layout.fillWidth: true\n                implicitHeight: Math.min(600, appResults.contentHeight + topMargin + bottomMargin)\n                clip: true\n                topMargin: 10\n                bottomMargin: 10\n                spacing: 2\n                KeyNavigation.up: searchBar\n                highlightMoveDuration: 100\n\n                onFocusChanged: {\n                    if (focus)\n                        appResults.currentIndex = 1;\n                }\n\n                Connections {\n                    target: root\n                    function onSearchingTextChanged() {\n                        if (appResults.count > 0)\n                            appResults.currentIndex = 0;\n                    }\n                }\n\n                Timer {\n                    id: debounceTimer\n                    interval: root.typingDebounceInterval\n                    onTriggered: {\n                        resultModel.values = LauncherSearch.results ?? [];\n                    }\n                }\n\n                Connections {\n                    target: LauncherSearch\n                    function onResultsChanged() {\n                        resultModel.values = LauncherSearch.results.slice(0, root.typingResultLimit);\n                        root.focusFirstItem();\n                        debounceTimer.restart();\n                    }\n                }\n\n                model: ScriptModel {\n                    id: resultModel\n                    objectProp: \"key\"\n                }\n\n                delegate: SearchItem {\n                    id: searchItem\n                    // The selectable item for each search result\n                    required property var modelData\n                    anchors.left: parent?.left\n                    anchors.right: parent?.right\n                    entry: modelData\n                    query: StringUtils.cleanOnePrefix(root.searchingText, [Config.options.search.prefix.action, Config.options.search.prefix.app, Config.options.search.prefix.clipboard, Config.options.search.prefix.emojis, Config.options.search.prefix.math, Config.options.search.prefix.shellCommand, Config.options.search.prefix.webSearch])\n\n                    Keys.onPressed: event => {\n                        if (event.key === Qt.Key_Tab) {\n                            if (LauncherSearch.results.length === 0)\n                                return;\n                            const tabbedText = searchItem.modelData.name;\n                            LauncherSearch.query = tabbedText;\n                            searchBar.searchInput.text = tabbedText;\n                            event.accepted = true;\n                            root.focusSearchInput();\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/polkit/Polkit.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Wayland\n\nFullscreenPolkitWindow {\n    id: root\n    contentComponent: Component {\n        PolkitContent {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/polkit/PolkitContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nItem {\n    id: root\n    readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true\n\n    Keys.onPressed: event => { // Esc to close\n        if (event.key === Qt.Key_Escape) {\n            PolkitService.cancel();\n        }\n    }\n\n    function submit() {\n        PolkitService.submit(inputField.text);\n    }\n    Connections {\n        target: PolkitService\n        function onInteractionAvailableChanged() {\n            if (!PolkitService.interactionAvailable) return;\n            inputField.text = \"\";\n            inputField.forceActiveFocus();\n        }\n    }\n\n    Rectangle {\n        id: bg\n        anchors.fill: parent\n        color: Appearance.colors.colScrim\n        opacity: 0\n        Component.onCompleted: {\n            opacity = 1\n        }\n        Behavior on opacity {\n            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n        }\n    }\n\n    WindowDialog {\n        anchors.centerIn: parent\n        backgroundWidth: 450\n        show: false\n        Component.onCompleted: {\n            show = true\n        }\n\n        MaterialSymbol {\n            Layout.alignment: Qt.AlignHCenter\n            iconSize: 26\n            text: \"security\"\n            color: Appearance.colors.colSecondary\n        }\n\n        WindowDialogTitle {\n            id: titleText\n            Layout.fillWidth: true\n            horizontalAlignment: Text.AlignHCenter\n            text: Translation.tr(\"Authentication\")\n        }\n\n        WindowDialogParagraph {\n            Layout.fillWidth: true\n            horizontalAlignment: Text.AlignLeft\n            text: PolkitService.cleanMessage\n        }\n\n        MaterialTextField {\n            id: inputField\n            Layout.fillWidth: true\n            focus: true\n            enabled: PolkitService.interactionAvailable\n            placeholderText: PolkitService.cleanPrompt\n            echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal\n            onAccepted: root.submit();\n\n            Keys.onPressed: event => { // Esc to close\n                if (event.key === Qt.Key_Escape) {\n                    PolkitService.cancel();\n                }\n            }\n        }\n\n        WindowDialogButtonRow {\n            Layout.bottomMargin: 10 // I honestly don't know why this is necessary\n            Item {\n                Layout.fillWidth: true\n            }\n            DialogButton {\n                buttonText: Translation.tr(\"Cancel\")\n                onClicked: PolkitService.cancel();\n            }\n            DialogButton {\n                enabled: PolkitService.interactionAvailable\n                buttonText: Translation.tr(\"OK\")\n                onClicked: root.submit();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/CircleSelectionDetails.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Shapes\nimport Quickshell\n\nItem {\n    id: root\n    required property color color\n    required property color overlayColor\n    required property list<point> points\n    property int strokeWidth: Config.options.regionSelector.circle.strokeWidth\n\n    function updatePoints() {\n        if (!root.dragging) return;\n        root.points.push({ x: root.mouseX, y: root.mouseY });\n    }\n\n    Rectangle {\n        id: darkenOverlay\n        z: 1\n        anchors.fill: parent\n        color: root.overlayColor\n    }\n\n    Shape {\n        id: shape\n        z: 2\n        anchors.fill: parent\n        layer.enabled: true\n        layer.smooth: true\n        preferredRendererType: Shape.CurveRenderer\n\n        ShapePath {\n            id: shapePath\n            strokeWidth: root.strokeWidth\n            pathHints: ShapePath.PathLinear\n            fillColor: \"transparent\"\n            strokeColor: root.color\n            capStyle: ShapePath.RoundCap\n            joinStyle: ShapePath.RoundJoin\n\n            PathPolyline {\n                path: root.points\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/CursorGuide.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    property var action\n    property var selectionMode\n\n    property string description: switch (root.action) {\n    case RegionSelection.SnipAction.Copy:\n    case RegionSelection.SnipAction.Edit:\n        return Translation.tr(\"Copy region (LMB) or annotate (RMB)\");\n    case RegionSelection.SnipAction.Search:\n        return Translation.tr(\"Search with Google Lens\");\n    case RegionSelection.SnipAction.CharRecognition:\n        return Translation.tr(\"Recognize text\");\n    case RegionSelection.SnipAction.Record:\n    case RegionSelection.SnipAction.RecordWithSound:\n        return Translation.tr(\"Record region\");\n    }\n    property string materialSymbol: switch (root.action) {\n    case RegionSelection.SnipAction.Copy:\n    case RegionSelection.SnipAction.Edit:\n        return \"content_cut\";\n    case RegionSelection.SnipAction.Search:\n        return \"image_search\";\n    case RegionSelection.SnipAction.CharRecognition:\n        return \"document_scanner\";\n    case RegionSelection.SnipAction.Record:\n    case RegionSelection.SnipAction.RecordWithSound:\n        return \"videocam\";\n    default:\n        return \"\";\n    }\n\n    property bool showDescription: true\n    function hideDescription() {\n        root.showDescription = false\n    }\n    Timer {\n        id: descTimeout\n        interval: 1000\n        running: true\n        onTriggered: {\n            root.hideDescription()\n        }\n    }\n    onActionChanged: {\n        root.showDescription = true\n        descTimeout.restart()\n    }\n\n    property int margins: 8\n    implicitWidth: content.implicitWidth + margins * 2\n    implicitHeight: content.implicitHeight + margins * 2\n\n    Rectangle {\n        id: content\n        anchors.centerIn: parent\n\n        property real padding: 8\n        implicitHeight: 38\n        implicitWidth: root.showDescription ? contentRow.implicitWidth + padding * 2 : implicitHeight\n        clip: true\n\n        topLeftRadius: 6\n        bottomLeftRadius: implicitHeight - topLeftRadius\n        bottomRightRadius: bottomLeftRadius\n        topRightRadius: bottomLeftRadius\n\n        color: Appearance.colors.colPrimary\n\n        Behavior on topLeftRadius {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n        Behavior on implicitWidth {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n\n        Row {\n            id: contentRow\n            anchors {\n                verticalCenter: parent.verticalCenter\n                left: parent.left\n                leftMargin: content.padding\n            }\n            spacing: 12\n\n            MaterialSymbol {\n                anchors.verticalCenter: parent.verticalCenter\n                iconSize: 22\n                color: Appearance.colors.colOnPrimary\n                animateChange: true\n                text: root.materialSymbol\n            }\n\n            FadeLoader {\n                id: descriptionLoader\n                anchors.verticalCenter: parent.verticalCenter\n                shown: root.showDescription\n                sourceComponent: StyledText {\n                    color: Appearance.colors.colOnPrimary\n                    text: root.description\n                    anchors.right: parent.right\n                    anchors.rightMargin: 6\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/OptionsToolbar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\n// Options toolbar\nToolbar {\n    id: root\n\n    // Use a synchronizer on these\n    property var action\n    property var selectionMode\n    // Signals\n    signal dismiss()\n\n    ToolbarTabBar {\n        id: tabBar\n        tabButtonList: [\n            {\"icon\": \"activity_zone\", \"name\": Translation.tr(\"Rect\")},\n            {\"icon\": \"gesture\", \"name\": Translation.tr(\"Circle\")}\n        ]\n        currentIndex: root.selectionMode === RegionSelection.SelectionMode.RectCorners ? 0 : 1\n        onCurrentIndexChanged: {\n            root.selectionMode = currentIndex === 0 ? RegionSelection.SelectionMode.RectCorners : RegionSelection.SelectionMode.Circle;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/RectCornersSelectionDetails.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    required property real regionX\n    required property real regionY\n    required property real regionWidth\n    required property real regionHeight\n    required property real mouseX\n    required property real mouseY\n    required property color color\n    required property color overlayColor\n    property bool showAimLines: Config.options.regionSelector.rect.showAimLines\n\n    property bool breathingBorderOnly: false\n\n    // Overlay to darken screen\n    // Base dark overlay around region\n    Rectangle {\n        id: darkenOverlay\n        z: 1\n        visible: !root.breathingBorderOnly\n        anchors {\n            left: parent.left\n            top: parent.top\n            leftMargin: root.regionX - darkenOverlay.border.width\n            topMargin: root.regionY - darkenOverlay.border.width\n        }\n        width: root.regionWidth + darkenOverlay.border.width * 2\n        height: root.regionHeight + darkenOverlay.border.width * 2\n        color: \"transparent\"\n        border.color: root.overlayColor\n        border.width: Math.max(root.width, root.height)\n    }\n\n    DashedBorder {\n        id: selectionBorder\n        z: 9\n        anchors {\n            left: parent.left\n            top: parent.top\n            leftMargin: Math.round(root.regionX) - borderWidth\n            topMargin: Math.round(root.regionY) - borderWidth\n        }\n        width: Math.round(root.regionWidth) + borderWidth * 2\n        height: Math.round(root.regionHeight) + borderWidth * 2\n\n        color: root.color\n        dashLength: 8\n        gapLength: 4\n        borderWidth: 1\n\n        // Breathing\n        opacity: 0.9\n        SequentialAnimation on opacity {\n            running: root.breathingBorderOnly\n            loops: Animation.Infinite\n            NumberAnimation { from: 0.9; to: 0.3; duration: 1200; easing.type: Easing.InOutQuad }\n            NumberAnimation { from: 0.3; to: 0.9; duration: 1200; easing.type: Easing.InOutQuad }\n        }\n    }\n\n    StyledText {\n        z: 2\n        visible: !root.breathingBorderOnly\n        anchors {\n            top: selectionBorder.bottom\n            right: selectionBorder.right\n            margins: 8\n        }\n        color: root.color\n        text: `${Math.round(root.regionWidth)} x ${Math.round(root.regionHeight)}`\n    }\n\n    // Coord lines\n    Rectangle { // Vertical\n        visible: root.showAimLines && !root.breathingBorderOnly\n        opacity: 0.2\n        z: 2\n        x: root.mouseX\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n        }\n        width: 1\n        color: root.color\n    }\n    Rectangle { // Horizontal\n        visible: root.showAimLines && !root.breathingBorderOnly\n        opacity: 0.2\n        z: 2\n        y: root.mouseY\n        anchors {\n            left: parent.left\n            right: parent.right\n        }\n        height: 1\n        color: root.color\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/RegionFunctions.qml",
    "content": "pragma Singleton\nimport Quickshell\n\nSingleton {\n    id: root\n\n    function intersectionOverUnion(regionA, regionB) {\n        // region: { at: [x, y], size: [w, h] }\n        const ax1 = regionA.at[0], ay1 = regionA.at[1];\n        const ax2 = ax1 + regionA.size[0], ay2 = ay1 + regionA.size[1];\n        const bx1 = regionB.at[0], by1 = regionB.at[1];\n        const bx2 = bx1 + regionB.size[0], by2 = by1 + regionB.size[1];\n\n        const interX1 = Math.max(ax1, bx1);\n        const interY1 = Math.max(ay1, by1);\n        const interX2 = Math.min(ax2, bx2);\n        const interY2 = Math.min(ay2, by2);\n\n        const interArea = Math.max(0, interX2 - interX1) * Math.max(0, interY2 - interY1);\n        const areaA = (ax2 - ax1) * (ay2 - ay1);\n        const areaB = (bx2 - bx1) * (by2 - by1);\n        const unionArea = areaA + areaB - interArea;\n\n        return unionArea > 0 ? interArea / unionArea : 0;\n    }\n\n    function filterOverlappingImageRegions(regions) {\n        let keep = [];\n        let removed = new Set();\n        for (let i = 0; i < regions.length; ++i) {\n            if (removed.has(i)) continue;\n            let regionA = regions[i];\n            for (let j = i + 1; j < regions.length; ++j) {\n                if (removed.has(j)) continue;\n                let regionB = regions[j];\n                if (intersectionOverUnion(regionA, regionB) > 0) {\n                    // Compare areas\n                    let areaA = regionA.size[0] * regionA.size[1];\n                    let areaB = regionB.size[0] * regionB.size[1];\n                    if (areaA <= areaB) {\n                        removed.add(j);\n                    } else {\n                        removed.add(i);\n                    }\n                }\n            }\n        }\n        for (let i = 0; i < regions.length; ++i) {\n            if (!removed.has(i)) keep.push(regions[i]);\n        }\n        return keep;\n    }\n\n    function filterWindowRegionsByLayers(windowRegions, layerRegions) {\n        return windowRegions.filter(windowRegion => {\n            for (let i = 0; i < layerRegions.length; ++i) {\n                if (intersectionOverUnion(windowRegion, layerRegions[i]) > 0)\n                    return false;\n            }\n            return true;\n        });\n    }\n\n    function filterImageRegions(regions, windowRegions, threshold = 0.1) {\n        // Remove image regions that overlap too much with any window region\n        let filtered = regions.filter(region => {\n            for (let i = 0; i < windowRegions.length; ++i) {\n                if (intersectionOverUnion(region, windowRegions[i]) > threshold)\n                    return false;\n            }\n            return true;\n        });\n        // Remove overlapping image regions, keep only the smaller one\n        return filterOverlappingImageRegions(filtered);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelection.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.utils\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport Qt.labs.synchronizer\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nPanelWindow {\n    id: root\n    visible: false\n    color: \"transparent\"\n    WlrLayershell.namespace: \"quickshell:regionSelector\"\n    WlrLayershell.layer: WlrLayer.Overlay\n    WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n    exclusionMode: ExclusionMode.Ignore\n    anchors {\n        left: true\n        right: true\n        top: true\n        bottom: true\n    }\n\n    // Modes\n    // TODO: Ask: sidebar AI\n    enum SnipAction { Copy, Edit, Search, CharRecognition, Record, RecordWithSound } \n    enum SelectionMode { RectCorners, Circle }\n    enum Phase { Select, Post }\n    property var action: RegionSelection.SnipAction.Copy\n    property var selectionMode: RegionSelection.SelectionMode.RectCorners\n    property var phase: RegionSelection.Phase.Select\n    signal dismiss()\n\n    // Styles\n    property string screenshotDir: Directories.screenshotTemp\n    property color overlayColor: ColorUtils.transparentize(\"#000000\", 0.4)\n    property color brightText: Appearance.m3colors.darkmode ? Appearance.colors.colOnLayer0 : Appearance.colors.colLayer0\n    property color brightSecondary: Appearance.m3colors.darkmode ? Appearance.colors.colSecondary : Appearance.colors.colOnSecondary\n    property color brightTertiary: Appearance.m3colors.darkmode ? Appearance.colors.colTertiary : Qt.lighter(Appearance.colors.colPrimary)\n    property color selectionBorderColor: ColorUtils.mix(brightText, brightSecondary, 0.5)\n    property color selectionFillColor: \"#33ffffff\"\n    property color windowBorderColor: brightSecondary\n    property color windowFillColor: ColorUtils.transparentize(windowBorderColor, 0.85)\n    property color imageBorderColor: brightTertiary\n    property color imageFillColor: ColorUtils.transparentize(imageBorderColor, 0.85)\n    property color onBorderColor: \"#ff000000\"\n    property real targetRegionOpacity: Config.options.regionSelector.targetRegions.opacity\n    property bool contentRegionOpacity: Config.options.regionSelector.targetRegions.contentRegionOpacity\n\n    // Vars for indicators\n    readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {\n        // Sort floating=true windows before others\n        if (a.floating === b.floating) return 0;\n        return a.floating ? -1 : 1;\n    })\n    readonly property var layers: HyprlandData.layers\n    readonly property real falsePositivePreventionRatio: 0.5\n\n    // Screen & interaction vars\n    readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)\n    readonly property real monitorScale: hyprlandMonitor.scale\n    readonly property real monitorOffsetX: hyprlandMonitor.x\n    readonly property real monitorOffsetY: hyprlandMonitor.y\n    property int activeWorkspaceId: hyprlandMonitor.activeWorkspace?.id ?? 0\n    property string screenshotPath: `${root.screenshotDir}/image-${screen.name}`\n    property real dragStartX: 0\n    property real dragStartY: 0\n    property real draggingX: 0\n    property real draggingY: 0\n    property real dragDiffX: 0\n    property real dragDiffY: 0\n    property bool draggedAway: (dragDiffX !== 0 || dragDiffY !== 0)\n    property bool dragging: false\n    property list<point> points: []\n    property var mouseButton: null\n    property var imageRegions: []\n    readonly property list<var> windowRegions: RegionFunctions.filterWindowRegionsByLayers(\n        root.windows.filter(w => w.workspace.id === root.activeWorkspaceId),\n        root.layerRegions\n    ).map(window => {\n        return {\n            at: [window.at[0] - root.monitorOffsetX, window.at[1] - root.monitorOffsetY],\n            size: [window.size[0], window.size[1]],\n            class: window.class,\n            title: window.title,\n        }\n    })\n    readonly property list<var> layerRegions: {\n        const layersOfThisMonitor = root.layers[root.hyprlandMonitor.name]\n        const topLayers = layersOfThisMonitor?.levels[\"2\"]\n        if (!topLayers) return [];\n        const nonBarTopLayers = topLayers\n            .filter(layer => !(layer.namespace.includes(\":bar\") || layer.namespace.includes(\":verticalBar\") || layer.namespace.includes(\":dock\")))\n            .map(layer => {\n            return {\n                at: [layer.x, layer.y],\n                size: [layer.w, layer.h],\n                namespace: layer.namespace,\n            }\n        })\n        const offsetAdjustedLayers = nonBarTopLayers.map(layer => {\n            return {\n                at: [layer.at[0] - root.monitorOffsetX, layer.at[1] - root.monitorOffsetY],\n                size: layer.size,\n                namespace: layer.namespace,\n            }\n        });\n        return offsetAdjustedLayers;\n    }\n\n    // Config\n    property bool isCircleSelection: (root.selectionMode === RegionSelection.SelectionMode.Circle)\n    property bool enableWindowRegions: Config.options.regionSelector.targetRegions.windows && !isCircleSelection\n    property bool enableLayerRegions: Config.options.regionSelector.targetRegions.layers && !isCircleSelection\n    property bool enableContentRegions: Config.options.regionSelector.targetRegions.content\n\n    // Target\n    property real targetedRegionX: -1\n    property real targetedRegionY: -1\n    property real targetedRegionWidth: 0\n    property real targetedRegionHeight: 0\n    function targetedRegionValid() {\n        return (root.targetedRegionX >= 0 && root.targetedRegionY >= 0)\n    }\n    function setRegionToTargeted() {\n        const padding = Config.options.regionSelector.targetRegions.selectionPadding; // Make borders not cut off n stuff\n        root.regionX = root.targetedRegionX - padding;\n        root.regionY = root.targetedRegionY - padding;\n        root.regionWidth = root.targetedRegionWidth + padding * 2;\n        root.regionHeight = root.targetedRegionHeight + padding * 2;\n    }\n\n    function updateTargetedRegion(x, y) {\n        // Image regions\n        const clickedRegion = root.imageRegions.find(region => {\n            return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];\n        });\n        if (clickedRegion) {\n            root.targetedRegionX = clickedRegion.at[0];\n            root.targetedRegionY = clickedRegion.at[1];\n            root.targetedRegionWidth = clickedRegion.size[0];\n            root.targetedRegionHeight = clickedRegion.size[1];\n            return;\n        }\n\n        // Layer regions\n        const clickedLayer = root.layerRegions.find(region => {\n            return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];\n        });\n        if (clickedLayer) {\n            root.targetedRegionX = clickedLayer.at[0];\n            root.targetedRegionY = clickedLayer.at[1];\n            root.targetedRegionWidth = clickedLayer.size[0];\n            root.targetedRegionHeight = clickedLayer.size[1];\n            return;\n        }\n\n        // Window regions\n        const clickedWindow = root.windowRegions.find(region => {\n            return region.at[0] <= x && x <= region.at[0] + region.size[0] && region.at[1] <= y && y <= region.at[1] + region.size[1];\n        });\n        if (clickedWindow) {\n            root.targetedRegionX = clickedWindow.at[0];\n            root.targetedRegionY = clickedWindow.at[1];\n            root.targetedRegionWidth = clickedWindow.size[0];\n            root.targetedRegionHeight = clickedWindow.size[1];\n            return;\n        }\n\n        root.targetedRegionX = -1;\n        root.targetedRegionY = -1;\n        root.targetedRegionWidth = 0;\n        root.targetedRegionHeight = 0;\n    }\n\n    property real regionWidth: Math.abs(draggingX - dragStartX)\n    property real regionHeight: Math.abs(draggingY - dragStartY)\n    property real regionX: Math.min(dragStartX, draggingX)\n    property real regionY: Math.min(dragStartY, draggingY)\n\n    // Screenshot stuff\n    TempScreenshotProcess {\n        id: screenshotProc\n        running: true\n        screen: root.screen\n        screenshotDir: root.screenshotDir\n        screenshotPath: root.screenshotPath\n        onExited: (exitCode, exitStatus) => {\n            if (root.enableContentRegions) imageDetectionProcess.running = true;\n            root.preparationDone = !checkRecordingProc.running;\n        }\n    }\n    property bool isRecording: root.action === RegionSelection.SnipAction.Record || root.action === RegionSelection.SnipAction.RecordWithSound\n    property bool recordingShouldStop: false\n    Process {\n        id: checkRecordingProc\n        running: isRecording\n        command: [\"pidof\", \"wf-recorder\"]\n        onExited: (exitCode, exitStatus) => {\n            root.preparationDone = !screenshotProc.running\n            root.recordingShouldStop = (exitCode === 0);\n        }\n    }\n    property bool preparationDone: false\n    onPreparationDoneChanged: {\n        if (!preparationDone) return;\n        if (root.isRecording && root.recordingShouldStop) {\n            Quickshell.execDetached([Directories.recordScriptPath]);\n            root.dismiss();\n            return;\n        }\n        root.visible = true;\n    }\n\n    Process {\n        id: imageDetectionProcess\n        command: [\"bash\", \"-c\", `${Directories.scriptPath}/images/find-regions-venv.sh ` \n            + `--hyprctl ` \n            + `--image '${StringUtils.shellSingleQuoteEscape(root.screenshotPath)}' ` \n            + `--max-width ${Math.round(root.screen.width * root.falsePositivePreventionRatio)} ` \n            + `--max-height ${Math.round(root.screen.height * root.falsePositivePreventionRatio)} `]\n        stdout: StdioCollector {\n            id: imageDimensionCollector\n            onStreamFinished: {\n                imageRegions = RegionFunctions.filterImageRegions(\n                    JSON.parse(imageDimensionCollector.text),\n                    root.windowRegions\n                );\n            }\n        }\n    }\n\n    function getScreenshotAction() {\n        switch(root.action) {\n            case RegionSelection.SnipAction.Copy:\n                return ScreenshotAction.Action.Copy;\n            case RegionSelection.SnipAction.Edit:\n                return ScreenshotAction.Action.Edit;\n            case RegionSelection.SnipAction.Search:\n                return ScreenshotAction.Action.Search;\n            case RegionSelection.SnipAction.CharRecognition:\n                return ScreenshotAction.Action.CharRecognition;\n            case RegionSelection.SnipAction.Record:\n                return ScreenshotAction.Action.Record;\n            case RegionSelection.SnipAction.RecordWithSound:\n                return ScreenshotAction.Action.RecordWithSound;\n            default:\n                console.warn(\"[Region Selector] Unknown snip action, skipping snip.\");\n                root.dismiss();\n                return;\n        }\n    }\n\n    // Execution after selection\n    function snip() {\n        // Validity check\n        if (root.regionWidth <= 0 || root.regionHeight <= 0) {\n            console.warn(\"[Region Selector] Invalid region size, skipping snip.\");\n            root.dismiss();\n        }\n\n        // Clamp region to screen bounds\n        root.regionX = Math.max(0, Math.min(root.regionX, root.screen.width - root.regionWidth));\n        root.regionY = Math.max(0, Math.min(root.regionY, root.screen.height - root.regionHeight));\n        root.regionWidth = Math.max(0, Math.min(root.regionWidth, root.screen.width - root.regionX));\n        root.regionHeight = Math.max(0, Math.min(root.regionHeight, root.screen.height - root.regionY));\n\n        // Adjust action\n        if (root.action === RegionSelection.SnipAction.Copy || root.action === RegionSelection.SnipAction.Edit) {\n            root.action = root.mouseButton === Qt.RightButton ? RegionSelection.SnipAction.Edit : RegionSelection.SnipAction.Copy;\n        }\n        \n        const screenshotDir = Config.options.screenSnip.savePath !== \"\" ? //\n            Config.options.screenSnip.savePath : \"\";\n        var screenshotAction = root.getScreenshotAction();\n        const command = ScreenshotAction.getCommand(\n            root.regionX * root.monitorScale, //\n            root.regionY * root.monitorScale, //\n            root.regionWidth * root.monitorScale,// \n            root.regionHeight * root.monitorScale, //\n            root.screenshotPath, //\n            screenshotAction, //\n            screenshotDir\n        )\n        Quickshell.execDetached(command);\n        if (root.action == RegionSelection.SnipAction.Record || root.action == RegionSelection.SnipAction.RecordWithSound) {\n            root.phase = RegionSelection.Phase.Post\n            root.selectionMode = RegionSelection.SelectionMode.RectCorners\n        } else {\n            root.dismiss();\n        }\n    }\n\n    // Only clickable in Selection phase\n    mask: Region {\n        item: switch(root.phase) {\n            case RegionSelection.Phase.Select: return mouseArea;\n            case RegionSelection.Phase.Post: return null;\n        }\n    }\n\n    ScreencopyView { // For freezing\n        anchors.fill: parent\n        live: false\n        captureSource: root.screen\n        visible: root.phase === RegionSelection.Phase.Select\n\n        focus: root.visible\n        Keys.onPressed: (event) => { // Esc to close\n            if (event.key === Qt.Key_Escape) {\n                root.dismiss();\n            }\n        }\n    }\n\n    MouseArea {\n        id: mouseArea\n        anchors.fill: parent\n        cursorShape: Qt.CrossCursor\n        acceptedButtons: Qt.LeftButton | Qt.RightButton\n        hoverEnabled: true\n\n        // Controls\n        onPressed: (mouse) => {\n            root.dragStartX = mouse.x;\n            root.dragStartY = mouse.y;\n            root.draggingX = mouse.x;\n            root.draggingY = mouse.y;\n            root.dragging = true;\n            root.mouseButton = mouse.button;\n        }\n        onReleased: (mouse) => {\n            // Detect if it was a click -> Try to select targeted region\n            if (root.draggingX === root.dragStartX && root.draggingY === root.dragStartY) {\n                if (root.targetedRegionValid()) {\n                    root.setRegionToTargeted();\n                }\n            }\n            // Circle dragging?\n            else if (root.selectionMode === RegionSelection.SelectionMode.Circle) {\n                const padding = Config.options.regionSelector.circle.padding + Config.options.regionSelector.circle.strokeWidth / 2;\n                const dragPoints = (root.points.length > 0) ? root.points : [{ x: mouseArea.mouseX, y: mouseArea.mouseY }];\n                const maxX = Math.max(...dragPoints.map(p => p.x));\n                const minX = Math.min(...dragPoints.map(p => p.x));\n                const maxY = Math.max(...dragPoints.map(p => p.y));\n                const minY = Math.min(...dragPoints.map(p => p.y));\n                root.regionX = minX - padding;\n                root.regionY = minY - padding;\n                root.regionWidth = maxX - minX + padding * 2;\n                root.regionHeight = maxY - minY + padding * 2;\n            }\n            root.snip();\n        }\n        onPositionChanged: (mouse) => {\n            root.updateTargetedRegion(mouse.x, mouse.y);\n            if (!root.dragging) return;\n            root.draggingX = mouse.x;\n            root.draggingY = mouse.y;\n            root.dragDiffX = mouse.x - root.dragStartX;\n            root.dragDiffY = mouse.y - root.dragStartY;\n            root.points.push({ x: mouse.x, y: mouse.y });\n        }\n        \n        Loader {\n            z: 2\n            anchors.fill: parent\n            active: root.selectionMode === RegionSelection.SelectionMode.RectCorners\n            sourceComponent: RectCornersSelectionDetails {\n                regionX: root.regionX\n                regionY: root.regionY\n                regionWidth: root.regionWidth\n                regionHeight: root.regionHeight\n                mouseX: mouseArea.mouseX\n                mouseY: mouseArea.mouseY\n                color: root.selectionBorderColor\n                overlayColor: root.overlayColor\n                breathingBorderOnly: root.phase === RegionSelection.Phase.Post\n            }\n        }\n\n        Loader {\n            z: 2\n            anchors.fill: parent\n            active: root.selectionMode === RegionSelection.SelectionMode.Circle\n            sourceComponent: CircleSelectionDetails {\n                color: root.selectionBorderColor\n                overlayColor: root.overlayColor\n                points: root.points\n            }\n        }\n\n        // The thing to the bottom-right with an icon\n        CursorGuide {\n            z: 9999\n            visible: root.phase === RegionSelection.Phase.Select\n            x: root.dragging ? root.regionX + root.regionWidth : mouseArea.mouseX\n            y: root.dragging ? root.regionY + root.regionHeight : mouseArea.mouseY\n            action: root.action\n            selectionMode: root.selectionMode\n        }\n\n        // Window regions\n        Repeater {\n            model: ScriptModel {\n                values: {\n                    if (root.phase === RegionSelection.Phase.Select && root.enableWindowRegions) {\n                        return root.windowRegions\n                    } else {\n                        return []\n                    }\n                }\n            }\n            delegate: TargetRegion {\n                z: 2\n                required property var modelData\n                clientDimensions: modelData\n                showIcon: true\n                targeted: !root.draggedAway && //\n                    (root.targetedRegionX === modelData.at[0]  //\n                    && root.targetedRegionY === modelData.at[1] //\n                    && root.targetedRegionWidth === modelData.size[0] //\n                    && root.targetedRegionHeight === modelData.size[1])\n\n                opacity: root.draggedAway ? 0 : root.targetRegionOpacity\n                borderColor: root.windowBorderColor\n                fillColor: targeted ? root.windowFillColor : \"transparent\"\n                text: `${modelData.class}`\n                radius: Appearance.rounding.windowRounding\n            }\n        }\n\n        // Layer regions\n        Repeater {\n            model: ScriptModel {\n                values: {\n                    if (root.phase === RegionSelection.Phase.Select && root.enableLayerRegions) {\n                        return root.layerRegions\n                    } else {\n                        return []\n                    }\n                }\n            }\n            delegate: TargetRegion {\n                z: 3\n                required property var modelData\n                clientDimensions: modelData\n                targeted: !root.draggedAway &&\n                    (root.targetedRegionX === modelData.at[0] \n                    && root.targetedRegionY === modelData.at[1]\n                    && root.targetedRegionWidth === modelData.size[0]\n                    && root.targetedRegionHeight === modelData.size[1])\n\n                opacity: root.draggedAway ? 0 : root.targetRegionOpacity\n                borderColor: root.windowBorderColor\n                fillColor: targeted ? root.windowFillColor : \"transparent\"\n                text: `${modelData.namespace}`\n                radius: Appearance.rounding.windowRounding\n            }\n        }\n\n        // Content regions\n        Repeater {\n            model: ScriptModel {\n                values: {\n                    if (root.phase === RegionSelection.Phase.Select && root.enableContentRegions) {\n                        return root.imageRegions\n                    } else {\n                        return []\n                    }\n                }\n            }\n            delegate: TargetRegion {\n                z: 4\n                required property var modelData\n                clientDimensions: modelData\n                targeted: !root.draggedAway &&\n                    (root.targetedRegionX === modelData.at[0] \n                    && root.targetedRegionY === modelData.at[1]\n                    && root.targetedRegionWidth === modelData.size[0]\n                    && root.targetedRegionHeight === modelData.size[1])\n\n                opacity: root.draggedAway ? 0 : root.contentRegionOpacity\n                borderColor: root.imageBorderColor\n                fillColor: targeted ? root.imageFillColor : \"transparent\"\n                text: Translation.tr(\"Content region\")\n            }\n        }\n\n        // Controls\n        Row {\n            id: regionSelectionControls\n            z: 10\n            visible: root.phase === RegionSelection.Phase.Select\n            anchors {\n                horizontalCenter: parent.horizontalCenter\n                bottom: parent.bottom\n                bottomMargin: -height\n            }\n            opacity: 0\n            Connections {\n                target: root\n                function onVisibleChanged() {\n                    if (!visible) return;\n                    regionSelectionControls.anchors.bottomMargin = 8;\n                    regionSelectionControls.opacity = 1;\n                }\n            }\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n            Behavior on anchors.bottomMargin {\n                animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n            }\n            spacing: 6\n\n            OptionsToolbar {\n                Synchronizer on action {\n                    property alias source: root.action\n                }\n                Synchronizer on selectionMode {\n                    property alias source: root.selectionMode\n                }\n                onDismiss: root.dismiss();\n            }\n            ToolbarPairedFab {\n                anchors.verticalCenter: parent.verticalCenter\n                iconText: \"close\"\n                onClicked: root.dismiss();\n                StyledToolTip {\n                    text: Translation.tr(\"Close\")\n                }\n            }\n        }\n        \n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/RegionSelector.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n\n    function dismiss() {\n        GlobalStates.regionSelectorOpen = false\n    }\n\n    property var action: RegionSelection.SnipAction.Copy\n    property var selectionMode: RegionSelection.SelectionMode.RectCorners\n    \n    Variants {\n        model: Quickshell.screens\n        delegate: Loader {\n            id: regionSelectorLoader\n            required property var modelData\n            active: GlobalStates.regionSelectorOpen\n\n            sourceComponent: RegionSelection {\n                screen: regionSelectorLoader.modelData\n                onDismiss: root.dismiss()\n                action: root.action\n                selectionMode: root.selectionMode\n            }\n        }\n    }\n\n    function screenshot() {\n        root.action = RegionSelection.SnipAction.Copy\n        root.selectionMode = RegionSelection.SelectionMode.RectCorners\n        GlobalStates.regionSelectorOpen = true\n    }\n\n    function search() {\n        root.action = RegionSelection.SnipAction.Search\n        if (Config.options.search.imageSearch.useCircleSelection) {\n            root.selectionMode = RegionSelection.SelectionMode.Circle\n        } else {\n            root.selectionMode = RegionSelection.SelectionMode.RectCorners\n        }\n        GlobalStates.regionSelectorOpen = true\n    }\n\n    function ocr() {\n        root.action = RegionSelection.SnipAction.CharRecognition\n        root.selectionMode = RegionSelection.SelectionMode.RectCorners\n        GlobalStates.regionSelectorOpen = true\n    }\n\n    function record() {\n        root.action = RegionSelection.SnipAction.Record\n        root.selectionMode = RegionSelection.SelectionMode.RectCorners\n        // If already open then re-trigger to stop recording\n        if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false\n        GlobalStates.regionSelectorOpen = true\n    }\n\n    function recordWithSound() {\n        root.action = RegionSelection.SnipAction.RecordWithSound\n        root.selectionMode = RegionSelection.SelectionMode.RectCorners\n        // If already open then re-trigger to stop recording\n        if (GlobalStates.regionSelectorOpen) GlobalStates.regionSelectorOpen = false\n        GlobalStates.regionSelectorOpen = true\n    }\n\n    IpcHandler {\n        target: \"region\"\n\n        function screenshot() {\n            root.screenshot()\n        }\n        function search() {\n            root.search()\n        }\n        function ocr() {\n            root.ocr()\n        }\n        function record() {\n            root.record()\n        }\n        function recordWithSound() {\n            root.recordWithSound()\n        }\n    }\n\n    GlobalShortcut {\n        name: \"regionScreenshot\"\n        description: \"Takes a screenshot of the selected region\"\n        onPressed: root.screenshot()\n    }\n    GlobalShortcut {\n        name: \"regionSearch\"\n        description: \"Searches the selected region\"\n        onPressed: root.search()\n    }\n    GlobalShortcut {\n        name: \"regionOcr\"\n        description: \"Recognizes text in the selected region\"\n        onPressed: root.ocr()\n    }\n    GlobalShortcut {\n        name: \"regionRecord\"\n        description: \"Records the selected region\"\n        onPressed: root.record()\n    }\n    GlobalShortcut {\n        name: \"regionRecordWithSound\"\n        description: \"Records the selected region with sound\"\n        onPressed: root.recordWithSound()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/regionSelector/TargetRegion.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Widgets\n\nRectangle {\n    id: root\n    required property var clientDimensions\n\n    property color colBackground: Qt.alpha(\"#88111111\", 0.9)\n    property color colForeground: \"#ddffffff\"\n    property bool showLabel: Config.options.regionSelector.targetRegions.showLabel\n    property bool showIcon: false\n    property bool targeted: false\n    property color borderColor\n    property color fillColor: \"transparent\"\n    property string text: \"\"\n    property real textPadding: 10\n    z: 2\n    color: fillColor\n    border.color: borderColor\n    border.width: targeted ? 4 : 2\n    radius: 4\n\n    Behavior on color {\n        animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n    }\n\n    visible: opacity > 0\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n    x: clientDimensions.at[0]\n    y: clientDimensions.at[1]\n    width: clientDimensions.size[0]\n    height: clientDimensions.size[1]\n\n    Loader {\n        anchors {\n            top: parent.top\n            left: parent.left\n            topMargin: root.textPadding\n            leftMargin: root.textPadding\n        }\n        \n        active: root.showLabel\n        sourceComponent: Rectangle {\n            property real verticalPadding: 5\n            property real horizontalPadding: 10\n            radius: 10\n            color: root.colBackground\n            border.width: 1\n            border.color: Appearance.m3colors.m3outlineVariant\n            implicitWidth: regionInfoRow.implicitWidth + horizontalPadding * 2\n            implicitHeight: regionInfoRow.implicitHeight + verticalPadding * 2\n\n            Row {\n                id: regionInfoRow\n                anchors.centerIn: parent\n                spacing: 4\n\n                Loader {\n                    id: regionIconLoader\n                    active: root.showIcon\n                    visible: active\n                    sourceComponent: IconImage {\n                        implicitSize: Appearance.font.pixelSize.larger\n                        source: Quickshell.iconPath(AppSearch.guessIcon(root.text), \"image-missing\")\n                    }\n                }\n\n                StyledText {\n                    id: regionText\n                    text: root.text\n                    color: root.colForeground\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/screenCorners/ScreenCorners.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: screenCorners\n    readonly property Toplevel activeWindow: ToplevelManager.activeToplevel\n    property var actionForCorner: ({\n        [RoundCorner.CornerEnum.TopLeft]: () => GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen,\n        [RoundCorner.CornerEnum.BottomLeft]: () => GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen,\n        [RoundCorner.CornerEnum.TopRight]: () => GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen,\n        [RoundCorner.CornerEnum.BottomRight]: () => GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen\n    })\n\n    component CornerPanelWindow: PanelWindow {\n        id: cornerPanelWindow\n        property var screen: QsWindow.window?.screen\n        property var brightnessMonitor: Brightness.getMonitorForScreen(screen)\n        property bool fullscreen\n        visible: (Config.options.appearance.fakeScreenRounding === 1 || (Config.options.appearance.fakeScreenRounding === 2 && !fullscreen))\n        property var corner\n\n        exclusionMode: ExclusionMode.Ignore\n        mask: Region {\n            item: sidebarCornerOpenInteractionLoader.active ? sidebarCornerOpenInteractionLoader : null\n        }\n        WlrLayershell.namespace: \"quickshell:screenCorners\"\n        WlrLayershell.layer: WlrLayer.Overlay\n        color: \"transparent\"\n\n        anchors {\n            top: cornerWidget.isTopLeft || cornerWidget.isTopRight\n            left: cornerWidget.isBottomLeft || cornerWidget.isTopLeft\n            bottom: cornerWidget.isBottomLeft || cornerWidget.isBottomRight\n            right: cornerWidget.isTopRight || cornerWidget.isBottomRight\n        }\n        margins {\n            right: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.right) * -1\n            bottom: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.bottom) * -1\n        }\n\n        implicitWidth: cornerWidget.implicitWidth\n        implicitHeight: cornerWidget.implicitHeight\n\n        RoundCorner {\n            id: cornerWidget\n            anchors.fill: parent\n            corner: cornerPanelWindow.corner\n            rightVisualMargin: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.right) * 1\n            bottomVisualMargin: (Config.options.interactions.deadPixelWorkaround.enable && cornerPanelWindow.anchors.bottom) * 1\n\n            implicitSize: Appearance.rounding.screenRounding\n            implicitHeight: Math.max(implicitSize, sidebarCornerOpenInteractionLoader.implicitHeight)\n            implicitWidth: Math.max(implicitSize, sidebarCornerOpenInteractionLoader.implicitWidth)\n\n            Loader {\n                id: sidebarCornerOpenInteractionLoader\n                active: {\n                    if (!Config.options.sidebar.cornerOpen.enable) return false;\n                    if (cornerPanelWindow.fullscreen) return false;\n                    return (Config.options.sidebar.cornerOpen.bottom == cornerWidget.isBottom);\n                }\n                anchors {\n                    top: (cornerWidget.isTopLeft || cornerWidget.isTopRight) ? parent.top : undefined\n                    bottom: (cornerWidget.isBottomLeft || cornerWidget.isBottomRight) ? parent.bottom : undefined\n                    left: (cornerWidget.isLeft) ? parent.left : undefined\n                    right: (cornerWidget.isTopRight || cornerWidget.isBottomRight) ? parent.right : undefined\n                }\n\n                sourceComponent: FocusedScrollMouseArea {\n                    id: mouseArea\n                    implicitWidth: Config.options.sidebar.cornerOpen.cornerRegionWidth\n                    implicitHeight: Config.options.sidebar.cornerOpen.cornerRegionHeight\n                    hoverEnabled: true\n                    onPositionChanged: {\n                        if (!Config.options.sidebar.cornerOpen.clicklessCornerEnd) return;\n                        const verticalOffset = Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset;\n                        const correctX = (cornerWidget.isRight && mouseArea.mouseX >= mouseArea.width - 2) || (cornerWidget.isLeft && mouseArea.mouseX <= 2);\n                        const correctY = (cornerWidget.isTop && mouseArea.mouseY > verticalOffset || cornerWidget.isBottom && mouseArea.mouseY < mouseArea.height - verticalOffset);\n                        if (correctX && correctY)\n                            screenCorners.actionForCorner[cornerPanelWindow.corner]();\n                    }\n                    onEntered: {\n                        if (Config.options.sidebar.cornerOpen.clickless)\n                            screenCorners.actionForCorner[cornerPanelWindow.corner]();\n                    }\n                    onPressed: {\n                        screenCorners.actionForCorner[cornerPanelWindow.corner]();\n                    }\n                    onScrollDown: {\n                        if (!Config.options.sidebar.cornerOpen.valueScroll)\n                            return;\n                        if (cornerWidget.isLeft)\n                            Brightness.decreaseBrightness()\n                        else {\n                            const currentVolume = Audio.value;\n                            const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;\n                            Audio.sink.audio.volume -= step;\n                        }\n                    }\n                    onScrollUp: {\n                        if (!Config.options.sidebar.cornerOpen.valueScroll)\n                            return;\n                        if (cornerWidget.isLeft)\n                            Brightness.increaseBrightness()\n                        else {\n                            const currentVolume = Audio.value;\n                            const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;\n                            Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step);\n                        }\n                    }\n                    onMovedAway: {\n                        if (!Config.options.sidebar.cornerOpen.valueScroll)\n                            return;\n                        if (cornerWidget.isLeft)\n                            GlobalStates.osdBrightnessOpen = false;\n                        else\n                            GlobalStates.osdVolumeOpen = false;\n                    }\n\n                    Loader {\n                        active: Config.options.sidebar.cornerOpen.visualize\n                        anchors.fill: parent\n                        sourceComponent: Rectangle {\n                            color: Appearance.colors.colPrimary\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Variants {\n        model: Quickshell.screens\n\n        Scope {\n            id: monitorScope\n            required property var modelData\n            property HyprlandMonitor monitor: Hyprland.monitorFor(modelData)\n\n            // Hide when fullscreen\n            property list<HyprlandWorkspace> workspacesForMonitor: Hyprland.workspaces.values.filter(workspace => workspace.monitor && workspace.monitor.name == monitor.name)\n            property var activeWorkspaceWithFullscreen: workspacesForMonitor.filter(workspace => ((workspace.toplevels.values.filter(window => window.wayland?.fullscreen)[0] != undefined) && workspace.active))[0]\n            property bool fullscreen: activeWorkspaceWithFullscreen != undefined\n\n            CornerPanelWindow {\n                screen: modelData\n                corner: RoundCorner.CornerEnum.TopLeft\n                fullscreen: monitorScope.fullscreen\n            }\n            CornerPanelWindow {\n                screen: modelData\n                corner: RoundCorner.CornerEnum.TopRight\n                fullscreen: monitorScope.fullscreen\n            }\n            CornerPanelWindow {\n                screen: modelData\n                corner: RoundCorner.CornerEnum.BottomLeft\n                fullscreen: monitorScope.fullscreen\n            }\n            CornerPanelWindow {\n                screen: modelData\n                corner: RoundCorner.CornerEnum.BottomRight\n                fullscreen: monitorScope.fullscreen\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTextOverlay.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport QtQuick\nimport QtQuick.Effects\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\n\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.models.gCloud\nimport qs.modules.common.utils\nimport qs.modules.common.widgets\nimport qs.services\n\nItem {\n    id: root\n\n    property double scaleFactor: 1\n    property color overlayColor: \"#BB000000\"\n    property color textColor: \"white\"\n    required property string screenshotPath\n\n    readonly property string wikiLink: \"https://ii.clsty.link/en/ii-qs/02usage/#setting-it-up\" // TODO: write a page for this\n    readonly property string textColorDetectionScriptPath: Quickshell.shellPath(\"scripts/images/text-color-venv.sh\")\n\n    property bool loading: true\n    property var visionParagraphs: []\n    property list<string> translationKeys: []\n    property var translation: ({})\n\n    function translate(s: string): string {\n        return translation[s] ?? s;\n    }\n\n    property bool error: false\n    property string errorMessage: \"\"\n    function showError() {\n        error = true;\n    }\n\n    Component.onCompleted: {\n        if (GoogleCloud.tokenReady && GoogleCloud.tokenError) {\n            root.showError();\n        }\n        cloudVision.annotateImage(screenshotPath);\n    }\n\n    function reattemptAsNeeded() {\n        if (root.visionParagraphs == [] && GoogleCloud.tokenReady && !GoogleCloud.tokenError) {\n            root.error = false;\n            cloudVision.annotateImage(root.screenshotPath);\n        }\n    }\n\n    Connections {\n        target: GoogleCloud\n        function onTokenReadyChanged() {\n            root.reattemptAsNeeded();\n        }\n    }\n\n    Rectangle {\n        id: loadingOverlay\n        anchors.fill: parent\n        opacity: root.loading ? 1 : 0\n        Behavior on opacity {\n            animation: Appearance.animation.elementMoveSmall.numberAnimation.createObject(this)\n        }\n        color: root.overlayColor\n\n        Column {\n            visible: !root.error\n            anchors.centerIn: parent\n            spacing: 10 * root.scaleFactor\n            MaterialLoadingIndicator {\n                anchors.horizontalCenter: parent.horizontalCenter\n                implicitSize: 100 * root.scaleFactor\n                scale: 1 + ((1 - loadingOverlay.opacity) * 0.5) * root.scaleFactor\n            }\n            StyledText {\n                anchors.horizontalCenter: parent.horizontalCenter\n                text: {\n                    if (cloudVision.state == GCloudApi.State.Preparing)\n                        return Translation.tr(\"Uploading image\");\n                    else if (cloudVision.state == GCloudApi.State.Processing)\n                        return Translation.tr(\"Reading image\");\n                    else if (cloudVision.state == GCloudApi.State.Error)\n                        return Translation.tr(\"Error\");\n                    else if (cloudTrans.state == GCloudApi.State.Preparing)\n                        return Translation.tr(\"Getting ready to translate\");\n                    else if (cloudTrans.state == GCloudApi.State.Processing)\n                        return Translation.tr(\"Translating\");\n                    else\n                        return \" \";\n                }\n                font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor\n                animateChange: true\n                color: root.textColor\n            }\n        }\n\n        Column {\n            visible: root.error\n            anchors.centerIn: parent\n            spacing: 10 * root.scaleFactor\n\n            MaterialShapeWrappedMaterialSymbol {\n                anchors.horizontalCenter: parent.horizontalCenter\n                text: \"exclamation\"\n                iconSize: 80 * root.scaleFactor\n                padding: 6 * root.scaleFactor\n                color: Appearance.colors.colError\n                colSymbol: Appearance.colors.colOnError\n                shape: MaterialShape.Shape.Sunny\n            }\n            StyledText {\n                anchors.horizontalCenter: parent.horizontalCenter\n                width: Math.min(root.windowWidth / 2, 800) * root.scaleFactor\n                horizontalAlignment: Text.AlignHCenter\n                textFormat: Text.MarkdownText\n                wrapMode: Text.Wrap\n                text: `**${Translation.tr(\"Screen Translator\")}**\\n\\n${root.errorMessage}\\n\\n__[${Translation.tr(\"See setup instructions on the wiki\")}](${root.wikiLink})__`\n                font.pixelSize: Appearance.font.pixelSize.small * root.scaleFactor\n                color: root.textColor\n                onLinkActivated: (link) => {\n                    Qt.openUrlExternally(link)\n                    GlobalStates.screenTranslatorOpen = false\n                }\n\n                PointingHandLinkHover {}\n            }\n        }\n    }\n\n    GCloudVisionResult {\n        id: gcr\n    }\n\n    function handleError(msg) {\n        if (msg?.length > 0) root.errorMessage = msg;\n        else root.errorMessage = Translation.tr(\"Set your Google Cloud service account key\");\n        root.showError();\n    }\n\n    GCloudVision {\n        id: cloudVision\n        onError: (msg) => {\n            root.handleError(msg);\n        }\n        onFinished: {\n            gcr.initializeWithData(outputData);\n            root.visionParagraphs = gcr.coherentParagraphs;\n            // print(gcr.coherentParagraphs)\n            root.translationKeys = gcr.coherentParagraphs.map(p => p.text);\n            // print(\"TRANSLATION KEYS:\", JSON.stringify(root.translationKeys));\n            cloudTrans.translateStrings(root.translationKeys);\n        }\n    }\n\n    GCloudTranslate {\n        id: cloudTrans\n        onError: (msg) => {\n            root.handleError(msg);\n        }\n        onFinished: {\n            var values = outputData.translations.map(translation => translation.translatedText);\n            const keys = root.translationKeys;\n            root.translation = ({});\n            for (var i = 0; i < keys.length; i++) {\n                Object.assign(root.translation, {\n                    [keys[i]]: values[i]\n                });\n            }\n            // print(\"TRANSLATION:\", JSON.stringify(root.translation));\n            root.loading = false;\n        }\n    }\n\n    property real windowWidth: QsWindow.window.screen.width\n    property real windowHeight: QsWindow.window.screen.height\n\n    StyledImage {\n        id: screenshotImage\n        z: 1\n        asynchronous: false\n        width: root.windowWidth\n        height: root.windowHeight\n        source: Qt.resolvedUrl(root.screenshotPath)\n        visible: false\n    }\n\n    Item {\n        id: blurMaskItem\n        z: 2\n        width: root.windowWidth\n        height: root.windowHeight\n        layer.enabled: true\n        visible: false\n        Repeater {\n            model: root.loading ? [] : root.visionParagraphs\n            delegate: VisionBoundingBoxRect {\n                readonly property string text: modelData.text\n                readonly property string translatedText: root.translate(text)\n                visible: translatedText != text\n                scaleFactor: 1\n            }\n        }\n    }\n\n    // I no longer need these but they were a fucking pain in the ass to figure out so they're staying\n    // GaussianBlur {\n    //     id: blurredImage\n    //     z: 3\n    //     width: root.windowWidth\n    //     height: root.windowHeight\n    //     transformOrigin: Item.TopLeft\n    //     scale: root.scaleFactor\n    //     source: screenshotImage\n    //     radius: 10\n    //     samples: radius * 2 + 1\n    //     visible: false\n    // }\n    // MultiEffect {\n    //     id: blurredImage\n    //     z: 3\n    //     source: screenshotImage\n    //     width: root.windowWidth\n    //     height: root.windowHeight\n    //     transformOrigin: Item.TopLeft\n    //     scale: root.scaleFactor\n\n    //     blurEnabled: true\n    //     blur: 1\n    //     blurMax: 64\n    //     visible: false\n    // }\n\n    MaskMultiEffect {\n        z: 4\n        implicitWidth: parent.width\n        implicitHeight: parent.height\n        width: parent.width\n        height: parent.height\n\n        // Mask\n        source: screenshotImage\n        maskSource: blurMaskItem\n\n        // Blur\n        blurEnabled: true\n        blur: 1\n        blurMax: 50\n        blurMultiplier: root.scaleFactor\n        autoPaddingEnabled: false\n    }\n\n    Item {\n        id: textItems\n        z: 999\n        Repeater {\n            model: root.loading ? [] : root.visionParagraphs\n            // An entry looks like this:\n            delegate: TextItem {}\n        }\n    }\n\n    component VisionBoundingBoxRect: Rectangle {\n        required property var modelData\n        property real scaleFactor: root.scaleFactor\n        property list<var> boundingVertices: modelData.boundingBox.vertices\n        property real unscaledX: boundingVertices[0].x\n        property real unscaledY: boundingVertices[0].y\n        property real unscaledWidth: boundingVertices[1].x - boundingVertices[0].x\n        property real unscaledHeight: boundingVertices[3].y - boundingVertices[0].y\n        \n        // Calculate rotation based on first two vertices (top-left to top-right)\n        property real dx: boundingVertices[1].x - boundingVertices[0].x\n        property real dy: boundingVertices[1].y - boundingVertices[0].y\n        transformOrigin: Item.TopLeft\n        rotation: {\n            // Note rotation in qml is degrees clockwise\n            var angle = Math.atan2(dy, dx) * 180 / Math.PI;\n            return angle;\n        }\n        \n        x: unscaledX * scaleFactor\n        y: unscaledY * scaleFactor\n        width: unscaledWidth * scaleFactor\n        height: unscaledHeight * scaleFactor\n        radius: 4\n    }\n\n    component TextItem: VisionBoundingBoxRect {\n        id: ti\n        // {\"boundingPoly\": {\"vertices\": [{\"x\": 536,\"y\": 236},{\"x\": 583,\"y\": 236},{\"x\": 583,\"y\": 262},{\"x\": 536,\"y\": 262}]},\"description\": \"宮坂\"}\n        readonly property string text: modelData.text\n        readonly property string translatedText: root.translate(text)\n        visible: translatedText != text\n\n        color: ColorUtils.transparentize(Appearance.colors.colSecondaryContainer, 0.4)\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        Loader {\n            active: ti.visible\n            sourceComponent: MultiTurnProcess {\n                Component.onCompleted: {\n                    runSequence([ //\n                        [ //\n                            \"bash\", \"-c\", //\n                            `magick ${StringUtils.shellSingleQuoteEscape(root.screenshotPath)} +repage -crop ${StringUtils.shellSingleQuoteEscape(ti.unscaledWidth)}x${StringUtils.shellSingleQuoteEscape(ti.unscaledHeight)}+${StringUtils.shellSingleQuoteEscape(ti.unscaledX)}+${StringUtils.shellSingleQuoteEscape(ti.unscaledY)} png:- | ${root.textColorDetectionScriptPath}`\n                        ],\n                        (out => {\n                            var colorData = JSON.parse(out);\n                            ti.color = ColorUtils.transparentize(colorData.background, 0.4);\n                            tiText.color = colorData.text;\n                        })\n                    ]);\n                }\n            }\n        }\n\n        SqueezedAnnotationStyledText {\n            id: tiText\n            width: parent.width\n            height: parent.height\n            text: ti.translatedText\n            scaleFactor: root.scaleFactor\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslator.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n\n    function dismiss() {\n        GlobalStates.screenTranslatorOpen = false\n    }\n\n    readonly property var currentScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null\n    \n    Loader {\n        id: translatorLoader\n        property var lockedScreen\n        active: false\n        Connections {\n            target: GlobalStates\n            function onScreenTranslatorOpenChanged() {\n                if (!GlobalStates.screenTranslatorOpen) {\n                    translatorLoader.active = false;\n                } else {\n                    translatorLoader.lockedScreen = root.currentScreen\n                    translatorLoader.active = true\n                }\n            }\n        }\n\n        sourceComponent: ScreenTranslatorPanel {\n            screen: translatorLoader.lockedScreen\n            onDismiss: root.dismiss()\n        }\n    }\n\n    function translate() {\n        GlobalStates.screenTranslatorOpen = true\n    }\n\n    IpcHandler {\n        target: \"screenTranslator\"\n\n        function translate() {\n            root.translate()\n        }\n    }\n\n    GlobalShortcut {\n        name: \"screenTranslate\"\n        description: \"Translates screen content\"\n        onPressed: root.translate()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/screenTranslator/ScreenTranslatorPanel.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Wayland\n\nimport qs.modules.common\nimport qs.modules.common.utils\nimport qs.modules.common.widgets\nimport qs.services\n\nPanelWindow {\n    id: root\n\n    // Interface\n    signal dismiss\n\n    // Window props\n    visible: false\n    // color: Appearance.colors.colLayer0\n    color: \"black\"\n    WlrLayershell.namespace: \"quickshell:regionSelector\"\n    WlrLayershell.layer: WlrLayer.Overlay\n    WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n    exclusionMode: ExclusionMode.Ignore\n    anchors {\n        left: true\n        right: true\n        top: true\n        bottom: true\n    }\n\n    // Config\n    readonly property string screenshotDir: Directories.screenshotTemp\n    readonly property string screenshotPath: `${root.screenshotDir}/image-${screen.name}`\n\n    // Preparation\n    property bool screenshotReady: false\n\n    function performTranslation() {\n        screenshotReady = true;\n    }\n\n    TempScreenshotProcess {\n        id: screenshotProc\n        running: true\n        screen: root.screen\n        screenshotDir: root.screenshotDir\n        screenshotPath: root.screenshotPath\n        onExited: (_, __) => {\n            root.visible = true;\n            root.performTranslation();\n        }\n    }\n\n    // Actual content\n    property real scale: 1.0\n    property real contentX: 0\n    property real contentY: 0\n\n    MouseArea {\n        anchors.fill: parent\n        clip: true\n\n        property real lastX: 0\n        property real lastY: 0\n\n        cursorShape: Qt.SizeAllCursor\n\n        onPressed: mouse => {\n            lastX = mouse.x;\n            lastY = mouse.y;\n        }\n\n        onPositionChanged: mouse => {\n            if (pressed) {\n                root.contentX += (mouse.x - lastX);\n                root.contentY += (mouse.y - lastY);\n                lastX = mouse.x;\n                lastY = mouse.y;\n            }\n        }\n\n        onWheel: event => {\n            const zoomFactor = event.angleDelta.y > 0 ? 1.1 : 0.9;\n            const oldScale = root.scale;\n            const newScale = Math.min(Math.max(0.1, oldScale * zoomFactor), 5);\n\n            if (newScale !== oldScale) {\n                // Determine mouse position relative to the content's unscaled origin\n                const localX = (event.x - root.contentX) / oldScale;\n                const localY = (event.y - root.contentY) / oldScale;\n\n                // Apply zoom\n                root.scale = newScale;\n\n                // Shift offsets to keep the same local point under the cursor\n                root.contentX = event.x - (localX * newScale);\n                root.contentY = event.y - (localY * newScale);\n            }\n        }\n\n        ScreencopyView { // Freeze screen\n            id: screencopy\n            width: parent.width\n            height: parent.height\n\n            x: root.contentX\n            y: root.contentY\n            scale: root.scale\n            transformOrigin: Item.TopLeft\n\n            live: false\n            captureSource: root.screen\n        }\n\n        Loader {\n            width: parent.width * root.scale\n            height: parent.height * root.scale\n\n            x: root.contentX\n            y: root.contentY\n\n            active: root.screenshotReady\n            sourceComponent: ScreenTextOverlay {\n                screenshotPath: root.screenshotPath\n                scaleFactor: root.scale\n            }\n        }\n    }\n\n    Row {\n        anchors {\n            horizontalCenter: parent.horizontalCenter\n            bottom: parent.bottom\n            bottomMargin: -height\n        }\n        Behavior on anchors.bottomMargin {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n        Component.onCompleted: {\n            anchors.bottomMargin = 8;\n        }\n\n        spacing: 6\n\n        Toolbar {\n            id: toolbar\n            focus: root.visible\n            Keys.onPressed: event => { // Esc to close\n                if (event.key === Qt.Key_Escape) {\n                    root.dismiss();\n                }\n            }\n            spacing: 0\n\n            IconToolbarButton {\n                id: sleepButton\n                onClicked: {\n                    toggled = !toggled\n                    if (toggled) keyInput.forceActiveFocus()\n                }\n                text: \"key\"\n\n                StyledToolTip {\n                    z: 9999\n                    text: Translation.tr(\"Key input\")\n                }\n            }\n\n            Revealer {\n                reveal: sleepButton.toggled\n                Layout.fillHeight: true\n\n                RowLayout {\n                    anchors.left: parent.left\n                    spacing: 6\n                    Item {} // extra padding\n                    ToolbarTextField {\n                        id: keyInput\n                        implicitWidth: 400\n                        placeholderText: Translation.tr(\"Paste service account key JSON here\")\n                        inputMethodHints: Qt.ImhSensitiveData\n                        onAccepted: submit()\n\n                        function submit() {\n                            const success = GoogleCloud.setKeyJson(text);\n                            if (!success) {\n                                invalidJsonAnimation.restart();\n                            } else {\n                                text = \"\";\n                                sleepButton.toggled = false;\n                            }\n                        }\n\n                        ErrorShakeAnimation {\n                            id: invalidJsonAnimation\n                            target: keyInput\n                        }\n                    }\n                    IconToolbarButton {\n                        id: submitButton\n                        onClicked: keyInput.submit()\n                        text: \"check\"\n                        toggled: keyInput.text.length > 0\n\n                        StyledToolTip {\n                            z: 9999\n                            text: Translation.tr(\"Confirm\")\n                        }\n                    }\n                }\n            }\n        }\n\n        ToolbarPairedFab {\n            iconText: \"close\"\n            onClicked: root.dismiss()\n            StyledToolTip {\n                text: Translation.tr(\"Close\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionActionButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton {\n    id: button\n\n    property string buttonIcon\n    property string buttonText\n    property bool keyboardDown: false\n    property real size: 120\n\n    buttonRadius: (button.focus || button.down) ? size / 2 : Appearance.rounding.verylarge\n    colBackground: button.keyboardDown ? Appearance.colors.colSecondaryContainerActive : \n        button.focus ? Appearance.colors.colPrimary : \n        Appearance.colors.colSecondaryContainer\n    colBackgroundHover: Appearance.colors.colPrimary\n    colRipple: Appearance.colors.colPrimaryActive\n    property color colText: (button.down || button.keyboardDown || button.focus || button.hovered) ?\n        Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer0\n\n    Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter\n    background.implicitHeight: size\n    background.implicitWidth: size\n\n    Behavior on buttonRadius {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    Keys.onPressed: (event) => {\n        if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            keyboardDown = true\n            button.clicked()\n            event.accepted = true;\n        }\n    }\n    Keys.onReleased: (event) => {\n        if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            keyboardDown = false\n            event.accepted = true;\n        }\n    }\n\n    contentItem: MaterialSymbol {\n        id: icon\n        anchors.fill: parent\n        color: button.colText\n        horizontalAlignment: Text.AlignHCenter\n        iconSize: 45\n        text: buttonIcon\n    }\n\n    StyledToolTip {\n        text: buttonText\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sessionScreen/SessionScreen.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n    property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)\n\n    Loader {\n        id: sessionLoader\n        active: GlobalStates.sessionOpen\n        onActiveChanged: {\n            if (sessionLoader.active)\n                SessionWarnings.refresh();\n        }\n\n        Connections {\n            target: GlobalStates\n            function onScreenLockedChanged() {\n                if (GlobalStates.screenLocked) {\n                    GlobalStates.sessionOpen = false;\n                }\n            }\n        }\n\n        sourceComponent: PanelWindow { // Session menu\n            id: sessionRoot\n            visible: sessionLoader.active\n            property string subtitle\n\n            function hide() {\n                GlobalStates.sessionOpen = false;\n            }\n\n            exclusionMode: ExclusionMode.Ignore\n            WlrLayershell.namespace: \"quickshell:session\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive\n            color: ColorUtils.transparentize(Appearance.m3colors.m3background, Appearance.m3colors.darkmode ? 0.05 : 0.12)\n\n            anchors {\n                top: true\n                left: true\n                right: true\n            }\n\n            implicitWidth: root.focusedScreen?.width ?? 0\n            implicitHeight: root.focusedScreen?.height ?? 0\n\n            MouseArea {\n                id: sessionMouseArea\n                anchors.fill: parent\n                onClicked: {\n                    sessionRoot.hide();\n                }\n            }\n\n            ColumnLayout { // Content column\n                id: contentColumn\n                anchors.centerIn: parent\n                spacing: 15\n\n                Keys.onPressed: event => {\n                    if (event.key === Qt.Key_Escape) {\n                        sessionRoot.hide();\n                    }\n                }\n\n                ColumnLayout {\n                    Layout.alignment: Qt.AlignHCenter\n                    spacing: 0\n                    StyledText {\n                        // Title\n                        Layout.alignment: Qt.AlignHCenter\n                        horizontalAlignment: Text.AlignHCenter\n                        font {\n                            family: Appearance.font.family.title\n                            pixelSize: Appearance.font.pixelSize.title\n                            variableAxes: Appearance.font.variableAxes.title\n                        }\n                        text: Translation.tr(\"Session\")\n                    }\n\n                    StyledText {\n                        // Small instruction\n                        Layout.alignment: Qt.AlignHCenter\n                        horizontalAlignment: Text.AlignHCenter\n                        font.pixelSize: Appearance.font.pixelSize.normal\n                        text: Translation.tr(\"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\")\n                    }\n                }\n\n                GridLayout {\n                    columns: 4\n                    columnSpacing: 15\n                    rowSpacing: 15\n\n                    SessionActionButton {\n                        id: sessionLock\n                        focus: sessionRoot.visible\n                        buttonIcon: \"lock\"\n                        buttonText: Translation.tr(\"Lock\")\n                        onClicked: {\n                            Session.lock();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.right: sessionSleep\n                        KeyNavigation.down: sessionHibernate\n                    }\n                    SessionActionButton {\n                        id: sessionSleep\n                        buttonIcon: \"dark_mode\"\n                        buttonText: Translation.tr(\"Sleep\")\n                        onClicked: {\n                            Session.suspend();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.left: sessionLock\n                        KeyNavigation.right: sessionLogout\n                        KeyNavigation.down: sessionShutdown\n                    }\n                    SessionActionButton {\n                        id: sessionLogout\n                        buttonIcon: \"logout\"\n                        buttonText: Translation.tr(\"Logout\")\n                        onClicked: {\n                            Session.logout();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.left: sessionSleep\n                        KeyNavigation.right: sessionTaskManager\n                        KeyNavigation.down: sessionReboot\n                    }\n                    SessionActionButton {\n                        id: sessionTaskManager\n                        buttonIcon: \"browse_activity\"\n                        buttonText: Translation.tr(\"Task Manager\")\n                        onClicked: {\n                            Session.launchTaskManager();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.left: sessionLogout\n                        KeyNavigation.down: sessionFirmwareReboot\n                    }\n\n                    SessionActionButton {\n                        id: sessionHibernate\n                        buttonIcon: \"downloading\"\n                        buttonText: Translation.tr(\"Hibernate\")\n                        onClicked: {\n                            Session.hibernate();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.up: sessionLock\n                        KeyNavigation.right: sessionShutdown\n                    }\n                    SessionActionButton {\n                        id: sessionShutdown\n                        buttonIcon: \"power_settings_new\"\n                        buttonText: Translation.tr(\"Shutdown\")\n                        onClicked: {\n                            Session.poweroff();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.left: sessionHibernate\n                        KeyNavigation.right: sessionReboot\n                        KeyNavigation.up: sessionSleep\n                    }\n                    SessionActionButton {\n                        id: sessionReboot\n                        buttonIcon: \"restart_alt\"\n                        buttonText: Translation.tr(\"Reboot\")\n                        onClicked: {\n                            Session.reboot();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.left: sessionShutdown\n                        KeyNavigation.right: sessionFirmwareReboot\n                        KeyNavigation.up: sessionLogout\n                    }\n                    SessionActionButton {\n                        id: sessionFirmwareReboot\n                        buttonIcon: \"settings_applications\"\n                        buttonText: Translation.tr(\"Reboot to firmware settings\")\n                        onClicked: {\n                            Session.rebootToFirmware();\n                            sessionRoot.hide();\n                        }\n                        onFocusChanged: {\n                            if (focus)\n                                sessionRoot.subtitle = buttonText;\n                        }\n                        KeyNavigation.up: sessionTaskManager\n                        KeyNavigation.left: sessionReboot\n                    }\n                }\n\n                DescriptionLabel {\n                    Layout.alignment: Qt.AlignHCenter\n                    text: sessionRoot.subtitle\n                }\n            }\n\n            ColumnLayout {\n                anchors {\n                    top: contentColumn.bottom\n                    topMargin: 10\n                    horizontalCenter: contentColumn.horizontalCenter\n                }\n                spacing: 10\n\n                Loader {\n                    Layout.alignment: Qt.AlignHCenter\n                    active: SessionWarnings.downloadRunning\n                    visible: active\n                    sourceComponent: DescriptionLabel {\n                        text: Translation.tr(\"There might be a download in progress. Check your Downloads folder.\")\n                        textColor: Appearance.m3colors.m3onErrorContainer\n                        color: Appearance.m3colors.m3errorContainer\n                    }\n                }\n\n                Loader {\n                    Layout.alignment: Qt.AlignHCenter\n                    active: SessionWarnings.packageManagerRunning\n                    visible: active\n                    sourceComponent: DescriptionLabel {\n                        text: Translation.tr(\"Your package manager is running\")\n                        textColor: Appearance.m3colors.m3onErrorContainer\n                        color: Appearance.m3colors.m3errorContainer\n                    }\n                }\n            }\n        }\n    }\n\n    component DescriptionLabel: Rectangle {\n        id: descriptionLabel\n        property string text\n        property color textColor: Appearance.colors.colOnTooltip\n        color: Appearance.colors.colTooltip\n        clip: true\n        radius: Appearance.rounding.normal\n        implicitHeight: descriptionLabelText.implicitHeight + 10 * 2\n        implicitWidth: descriptionLabelText.implicitWidth + 15 * 2\n\n        Behavior on implicitWidth {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n\n        StyledText {\n            id: descriptionLabelText\n            anchors.centerIn: parent\n            color: descriptionLabel.textColor\n            text: descriptionLabel.text\n        }\n    }\n\n    IpcHandler {\n        target: \"session\"\n\n        function toggle(): void {\n            GlobalStates.sessionOpen = !GlobalStates.sessionOpen;\n        }\n\n        function close(): void {\n            GlobalStates.sessionOpen = false;\n        }\n\n        function open(): void {\n            GlobalStates.sessionOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sessionToggle\"\n        description: \"Toggles session screen on press\"\n\n        onPressed: {\n            GlobalStates.sessionOpen = !GlobalStates.sessionOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sessionOpen\"\n        description: \"Opens session screen on press\"\n\n        onPressed: {\n            GlobalStates.sessionOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sessionClose\"\n        description: \"Closes session screen on press\"\n\n        onPressed: {\n            GlobalStates.sessionOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/AiChat.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.ii.sidebarLeft.aiChat\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\n\nItem {\n    id: root\n    property real padding: 4\n    property var inputField: messageInputField\n    property string commandPrefix: \"/\"\n\n    property var suggestionQuery: \"\"\n    property var suggestionList: []\n\n    onFocusChanged: focus => {\n        if (focus) {\n            root.inputField.forceActiveFocus();\n        }\n    }\n\n    Keys.onPressed: event => {\n        messageInputField.forceActiveFocus();\n        if (event.modifiers === Qt.NoModifier) {\n            if (event.key === Qt.Key_PageUp) {\n                messageListView.contentY = Math.max(0, messageListView.contentY - messageListView.height / 2);\n                event.accepted = true;\n            } else if (event.key === Qt.Key_PageDown) {\n                messageListView.contentY = Math.min(messageListView.contentHeight - messageListView.height / 2, messageListView.contentY + messageListView.height / 2);\n                event.accepted = true;\n            }\n        }\n        if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) {\n            Ai.clearMessages();\n        }\n    }\n\n    property var allCommands: [\n        {\n            name: \"attach\",\n            description: Translation.tr(\"Attach a file. Only works with Gemini.\"),\n            execute: args => {\n                Ai.attachFile(args.join(\" \").trim());\n            }\n        },\n        {\n            name: \"model\",\n            description: Translation.tr(\"Choose model\"),\n            execute: args => {\n                Ai.setModel(args[0]);\n            }\n        },\n        {\n            name: \"tool\",\n            description: Translation.tr(\"Set the tool to use for the model.\"),\n            execute: args => {\n                // console.log(args)\n                if (args.length == 0 || args[0] == \"get\") {\n                    Ai.addMessage(Translation.tr(\"Usage: %1tool TOOL_NAME\").arg(root.commandPrefix), Ai.interfaceRole);\n                } else {\n                    const tool = args[0];\n                    const switched = Ai.setTool(tool);\n                    if (switched) {\n                        Ai.addMessage(Translation.tr(\"Tool set to: %1\").arg(tool), Ai.interfaceRole);\n                    }\n                }\n            }\n        },\n        {\n            name: \"prompt\",\n            description: Translation.tr(\"Set the system prompt for the model.\"),\n            execute: args => {\n                if (args.length === 0 || args[0] === \"get\") {\n                    Ai.printPrompt();\n                    return;\n                }\n                Ai.loadPrompt(args.join(\" \").trim());\n            }\n        },\n        {\n            name: \"key\",\n            description: Translation.tr(\"Set API key\"),\n            execute: args => {\n                if (args[0] == \"get\") {\n                    Ai.printApiKey();\n                } else {\n                    Ai.setApiKey(args[0]);\n                }\n            }\n        },\n        {\n            name: \"save\",\n            description: Translation.tr(\"Save chat\"),\n            execute: args => {\n                const joinedArgs = args.join(\" \");\n                if (joinedArgs.trim().length == 0) {\n                    Ai.addMessage(Translation.tr(\"Usage: %1save CHAT_NAME\").arg(root.commandPrefix), Ai.interfaceRole);\n                    return;\n                }\n                Ai.saveChat(joinedArgs);\n            }\n        },\n        {\n            name: \"load\",\n            description: Translation.tr(\"Load chat\"),\n            execute: args => {\n                const joinedArgs = args.join(\" \");\n                if (joinedArgs.trim().length == 0) {\n                    Ai.addMessage(Translation.tr(\"Usage: %1load CHAT_NAME\").arg(root.commandPrefix), Ai.interfaceRole);\n                    return;\n                }\n                Ai.loadChat(joinedArgs);\n            }\n        },\n        {\n            name: \"clear\",\n            description: Translation.tr(\"Clear chat history\"),\n            execute: () => {\n                Ai.clearMessages();\n            }\n        },\n        {\n            name: \"temp\",\n            description: Translation.tr(\"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\"),\n            execute: args => {\n                // console.log(args)\n                if (args.length == 0 || args[0] == \"get\") {\n                    Ai.printTemperature();\n                } else {\n                    const temp = parseFloat(args[0]);\n                    Ai.setTemperature(temp);\n                }\n            }\n        },\n        {\n            name: \"test\",\n            description: Translation.tr(\"Markdown test\"),\n            execute: () => {\n                Ai.addMessage(`\n<think>\nA longer think block to test revealing animation\nOwO wem ipsum dowo sit amet, consekituwet awipiscing ewit, sed do eiuwsmod tempow inwididunt ut wabowe et dowo mawa. Ut enim ad minim weniam, quis nostwud exeucitation uwuwamcow bowowis nisi ut awiquip ex ea commowo consequat. Duuis aute iwuwe dowo in wepwependewit in wowuptate velit esse ciwwum dowo eu fugiat nuwa pawiatuw. Excepteuw sint occaecat cupidatat non pwowoident, sunt in cuwpa qui officia desewunt mowit anim id est wabowum. Meouw! >w<\nMowe uwu wem ipsum!\n</think>\n## ✏️ Markdown test\n### Formatting\n\n- *Italic*, \\`Monospace\\`, **Bold**, [Link](https://example.com)\n- Arch lincox icon <img src=\"${Quickshell.shellPath(\"assets/icons/arch-symbolic.svg\")}\" height=\"${Appearance.font.pixelSize.small}\"/>\n\n### Table\n\nQuickshell vs AGS/Astal\n\n|                          | Quickshell       | AGS/Astal         |\n|--------------------------|------------------|-------------------|\n| UI Toolkit               | Qt               | Gtk3/Gtk4         |\n| Language                 | QML              | Js/Ts/Lua         |\n| Reactivity               | Implied          | Needs declaration |\n| Widget placement         | Mildly difficult | More intuitive    |\n| Bluetooth & Wifi support | ❌               | ✅                |\n| No-delay keybinds        | ✅               | ❌                |\n| Development              | New APIs         | New syntax        |\n\n### Code block\n\nJust a hello world...\n\n\\`\\`\\`cpp\n#include <bits/stdc++.h>\n// This is intentionally very long to test scrolling\nconst std::string GREETING = \\\"UwU\\\";\nint main(int argc, char* argv[]) {\n    std::cout << GREETING;\n}\n\\`\\`\\`\n\n### LaTeX\n\n\nInline w/ dollar signs: $\\\\frac{1}{2} = \\\\frac{2}{4}$\n\nInline w/ double dollar signs: $$\\\\int_0^\\\\infty e^{-x^2} dx = \\\\frac{\\\\sqrt{\\\\pi}}{2}$$\n\nInline w/ backslash and square brackets \\\\[\\\\int_0^\\\\infty \\\\frac{1}{x^2} dx = \\\\infty\\\\]\n\nInline w/ backslash and round brackets \\\\(e^{i\\\\pi} + 1 = 0\\\\)\n`, Ai.interfaceRole);\n            }\n        },\n    ]\n\n    function handleInput(inputText) {\n        if (inputText.startsWith(root.commandPrefix)) {\n            // Handle special commands\n            const command = inputText.split(\" \")[0].substring(1);\n            const args = inputText.split(\" \").slice(1);\n            const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`);\n            if (commandObj) {\n                commandObj.execute(args);\n            } else {\n                Ai.addMessage(Translation.tr(\"Unknown command: \") + command, Ai.interfaceRole);\n            }\n        } else {\n            Ai.sendUserMessage(inputText);\n        }\n\n        // Always scroll to bottom when user sends a message\n        messageListView.positionViewAtEnd();\n    }\n\n    Process {\n        id: decodeImageAndAttachProc\n        property string imageDecodePath: Directories.cliphistDecode\n        property string imageDecodeFileName: \"image\"\n        property string imageDecodeFilePath: `${imageDecodePath}/${imageDecodeFileName}`\n        function handleEntry(entry: string) {\n            imageDecodeFileName = parseInt(entry.match(/^(\\d+)\\t/)[1]);\n            decodeImageAndAttachProc.exec([\"bash\", \"-c\", `[ -f ${imageDecodeFilePath} ] || echo '${StringUtils.shellSingleQuoteEscape(entry)}' | ${Cliphist.cliphistBinary} decode > '${imageDecodeFilePath}'`]);\n        }\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode === 0) {\n                Ai.attachFile(imageDecodeFilePath);\n            } else {\n                console.error(\"[AiChat] Failed to decode image in clipboard content\");\n            }\n        }\n    }\n\n    component StatusItem: MouseArea {\n        id: statusItem\n        property string icon\n        property string statusText\n        property string description\n        hoverEnabled: true\n        implicitHeight: statusItemRowLayout.implicitHeight\n        implicitWidth: statusItemRowLayout.implicitWidth\n\n        RowLayout {\n            id: statusItemRowLayout\n            spacing: 0\n            MaterialSymbol {\n                text: statusItem.icon\n                iconSize: Appearance.font.pixelSize.huge\n                color: Appearance.colors.colSubtext\n            }\n            StyledText {\n                font.pixelSize: Appearance.font.pixelSize.small\n                text: statusItem.statusText\n                color: Appearance.colors.colSubtext\n                animateChange: true\n            }\n        }\n\n        StyledToolTip {\n            text: statusItem.description\n            extraVisibleCondition: false\n            alternativeVisibleCondition: statusItem.containsMouse\n        }\n    }\n\n    component StatusSeparator: Rectangle {\n        implicitWidth: 4\n        implicitHeight: 4\n        radius: implicitWidth / 2\n        color: Appearance.colors.colOutlineVariant\n    }\n\n    ColumnLayout {\n        id: columnLayout\n        anchors {\n            fill: parent\n            margins: root.padding\n        }\n        spacing: root.padding\n\n        Item {\n            // Messages\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: swipeView.width\n                    height: swipeView.height\n                    radius: Appearance.rounding.small\n                }\n            }\n\n            StyledRectangularShadow {\n                z: 1\n                target: statusBg\n                opacity: messageListView.atYBeginning ? 0 : 1\n                visible: opacity > 0\n                Behavior on opacity {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n            }\n            Rectangle {\n                id: statusBg\n                z: 2\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    top: parent.top\n                    topMargin: 4\n                }\n                implicitWidth: statusRowLayout.implicitWidth + 10 * 2\n                implicitHeight: Math.max(statusRowLayout.implicitHeight, 38)\n                radius: Appearance.rounding.normal - root.padding\n                color: messageListView.atYBeginning ? Appearance.colors.colLayer2 : Appearance.colors.colLayer2Base\n                Behavior on color {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n                RowLayout {\n                    id: statusRowLayout\n                    anchors.centerIn: parent\n                    spacing: 10\n\n                    StatusItem {\n                        icon: Ai.currentModelHasApiKey ? \"key\" : \"key_off\"\n                        statusText: \"\"\n                        description: Ai.currentModelHasApiKey ? Translation.tr(\"API key is set\\nChange with /key YOUR_API_KEY\") : Translation.tr(\"No API key\\nSet it with /key YOUR_API_KEY\")\n                    }\n                    StatusSeparator {}\n                    StatusItem {\n                        icon: \"device_thermostat\"\n                        statusText: Ai.temperature.toFixed(1)\n                        description: Translation.tr(\"Temperature\\nChange with /temp VALUE\")\n                    }\n                    StatusSeparator {\n                        visible: Ai.tokenCount.total > 0\n                    }\n                    StatusItem {\n                        visible: Ai.tokenCount.total > 0\n                        icon: \"token\"\n                        statusText: Ai.tokenCount.total\n                        description: Translation.tr(\"Total token count\\nInput: %1\\nOutput: %2\").arg(Ai.tokenCount.input).arg(Ai.tokenCount.output)\n                    }\n                }\n            }\n\n            ScrollEdgeFade {\n                z: 1\n                target: messageListView\n                vertical: true\n            }\n\n            StyledListView { // Message list\n                id: messageListView\n                z: 0\n                anchors.fill: parent\n                spacing: 10\n                popin: false\n                topMargin: statusBg.implicitHeight + statusBg.anchors.topMargin * 2\n\n                touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4\n                mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4\n\n                property int lastResponseLength: 0\n                // onContentHeightChanged: {\n                //     if (atYEnd)\n                //         Qt.callLater(positionViewAtEnd);\n                // }\n                // onCountChanged: {\n                //     // Auto-scroll when new messages are added\n                //     if (atYEnd)\n                //         Qt.callLater(positionViewAtEnd);\n                // }\n\n                add: null // Prevent function calls from being janky\n\n                model: ScriptModel {\n                    values: Ai.messageIDs.filter(id => {\n                        const message = Ai.messageByID[id];\n                        return message?.visibleToUser ?? true;\n                    })\n                }\n                delegate: AiMessage {\n                    required property var modelData\n                    required property int index\n                    messageIndex: index\n                    messageData: {\n                        Ai.messageByID[modelData];\n                    }\n                    messageInputField: root.inputField\n                }\n            }\n\n            PagePlaceholder {\n                z: 2\n                shown: Ai.messageIDs.length === 0\n                icon: \"neurology\"\n                title: Translation.tr(\"Large language models\")\n                description: Translation.tr(\"Type /key to get started with online models\\nCtrl+O to expand sidebar\\nCtrl+P to pin sidebar\\nCtrl+D to detach sidebar\")\n                shape: MaterialShape.Shape.PixelCircle\n            }\n\n            ScrollToBottomButton {\n                z: 3\n                target: messageListView\n            }\n        }\n\n        DescriptionBox {\n            text: root.suggestionList[suggestions.selectedIndex]?.description ?? \"\"\n            showArrows: root.suggestionList.length > 1\n        }\n\n        FlowButtonGroup { // Suggestions\n            id: suggestions\n            visible: root.suggestionList.length > 0 && messageInputField.text.length > 0\n            property int selectedIndex: 0\n            Layout.fillWidth: true\n            spacing: 5\n\n            Repeater {\n                id: suggestionRepeater\n                model: {\n                    suggestions.selectedIndex = 0;\n                    return root.suggestionList.slice(0, 10);\n                }\n                delegate: ApiCommandButton {\n                    id: commandButton\n                    colBackground: suggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer\n                    bounce: false\n                    contentItem: StyledText {\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        color: Appearance.m3colors.m3onSurface\n                        horizontalAlignment: Text.AlignHCenter\n                        text: modelData.displayName ?? modelData.name\n                    }\n\n                    onHoveredChanged: {\n                        if (commandButton.hovered) {\n                            suggestions.selectedIndex = index;\n                        }\n                    }\n                    onClicked: {\n                        suggestions.acceptSuggestion(modelData.name);\n                    }\n                }\n            }\n\n            function acceptSuggestion(word) {\n                const words = messageInputField.text.trim().split(/\\s+/);\n                if (words.length > 0) {\n                    words[words.length - 1] = word;\n                } else {\n                    words.push(word);\n                }\n                const updatedText = words.join(\" \") + \" \";\n                messageInputField.text = updatedText;\n                messageInputField.cursorPosition = messageInputField.text.length;\n                messageInputField.forceActiveFocus();\n            }\n\n            function acceptSelectedWord() {\n                if (suggestions.selectedIndex >= 0 && suggestions.selectedIndex < suggestionRepeater.count) {\n                    const word = root.suggestionList[suggestions.selectedIndex].name;\n                    suggestions.acceptSuggestion(word);\n                }\n            }\n        }\n\n        Rectangle { // Input area\n            id: inputWrapper\n            property real spacing: 5\n            Layout.fillWidth: true\n            radius: Appearance.rounding.normal - root.padding\n            color: Appearance.colors.colLayer2\n            implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + spacing, 45) + (attachedFileIndicator.implicitHeight + spacing + attachedFileIndicator.anchors.topMargin)\n            clip: true\n\n            Behavior on implicitHeight {\n                animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n            }\n\n            AttachedFileIndicator {\n                id: attachedFileIndicator\n                anchors {\n                    top: parent.top\n                    left: parent.left\n                    right: parent.right\n                    margins: visible ? 5 : 0\n                }\n                filePath: Ai.pendingFilePath\n                onRemove: Ai.attachFile(\"\")\n            }\n\n            RowLayout { // Input field and send button\n                id: inputFieldRowLayout\n                anchors {\n                    bottom: commandButtonsRow.top\n                    left: parent.left\n                    right: parent.right\n                    bottomMargin: 5\n                }\n                spacing: 0\n\n                ScrollView {\n                    id: inputScrollView\n                    Layout.fillWidth: true\n                    Layout.preferredHeight: Math.min(root.height * 3/5, messageInputField.height)\n                    clip: true\n                    ScrollBar.vertical.policy: ScrollBar.AsNeeded\n\n                    StyledTextArea { // The actual TextArea (inside ScrollView to enable scrolling)\n                        id: messageInputField\n                        anchors.fill: parent\n                        wrapMode: TextArea.Wrap\n                        padding: 10\n                        color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant\n                        placeholderText: Translation.tr('Message the model... \"%1\" for commands').arg(root.commandPrefix)\n\n                        background: null\n\n                        onTextChanged: {\n                            // Handle suggestions\n                            if (messageInputField.text.length === 0) {\n                                root.suggestionQuery = \"\";\n                                root.suggestionList = [];\n                                return;\n                            } else if (messageInputField.text.startsWith(`${root.commandPrefix}model`)) {\n                                root.suggestionQuery = messageInputField.text.split(\" \")[1] ?? \"\";\n                                const modelResults = Fuzzy.go(root.suggestionQuery, Ai.modelList.map(model => {\n                                    return {\n                                        name: Fuzzy.prepare(model),\n                                        obj: model\n                                    };\n                                }), {\n                                    all: true,\n                                    key: \"name\"\n                                });\n                                root.suggestionList = modelResults.map(model => {\n                                    return {\n                                        name: `${messageInputField.text.trim().split(\" \").length == 1 ? (root.commandPrefix + \"model \") : \"\"}${model.target}`,\n                                        displayName: `${Ai.models[model.target].name}`,\n                                        description: `${Ai.models[model.target].description}`\n                                    };\n                                });\n                            } else if (messageInputField.text.startsWith(`${root.commandPrefix}prompt`)) {\n                                root.suggestionQuery = messageInputField.text.split(\" \")[1] ?? \"\";\n                                const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.promptFiles.map(file => {\n                                    return {\n                                        name: Fuzzy.prepare(file),\n                                        obj: file\n                                    };\n                                }), {\n                                    all: true,\n                                    key: \"name\"\n                                });\n                                root.suggestionList = promptFileResults.map(file => {\n                                    return {\n                                        name: `${messageInputField.text.trim().split(\" \").length == 1 ? (root.commandPrefix + \"prompt \") : \"\"}${file.target}`,\n                                        displayName: `${FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target))}`,\n                                        description: Translation.tr(\"Load prompt from %1\").arg(file.target)\n                                    };\n                                });\n                            } else if (messageInputField.text.startsWith(`${root.commandPrefix}save`)) {\n                                root.suggestionQuery = messageInputField.text.split(\" \")[1] ?? \"\";\n                                const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {\n                                    return {\n                                        name: Fuzzy.prepare(file),\n                                        obj: file\n                                    };\n                                }), {\n                                    all: true,\n                                    key: \"name\"\n                                });\n                                root.suggestionList = promptFileResults.map(file => {\n                                    const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();\n                                    return {\n                                        name: `${messageInputField.text.trim().split(\" \").length == 1 ? (root.commandPrefix + \"save \") : \"\"}${chatName}`,\n                                        displayName: `${chatName}`,\n                                        description: Translation.tr(\"Save chat to %1\").arg(chatName)\n                                    };\n                                });\n                            } else if (messageInputField.text.startsWith(`${root.commandPrefix}load`)) {\n                                root.suggestionQuery = messageInputField.text.split(\" \")[1] ?? \"\";\n                                const promptFileResults = Fuzzy.go(root.suggestionQuery, Ai.savedChats.map(file => {\n                                    return {\n                                        name: Fuzzy.prepare(file),\n                                        obj: file\n                                    };\n                                }), {\n                                    all: true,\n                                    key: \"name\"\n                                });\n                                root.suggestionList = promptFileResults.map(file => {\n                                    const chatName = FileUtils.trimFileExt(FileUtils.fileNameForPath(file.target)).trim();\n                                    return {\n                                        name: `${messageInputField.text.trim().split(\" \").length == 1 ? (root.commandPrefix + \"load \") : \"\"}${chatName}`,\n                                        displayName: `${chatName}`,\n                                        description: Translation.tr(`Load chat from %1`).arg(file.target)\n                                    };\n                                });\n                            } else if (messageInputField.text.startsWith(`${root.commandPrefix}tool`)) {\n                                root.suggestionQuery = messageInputField.text.split(\" \")[1] ?? \"\";\n                                const toolResults = Fuzzy.go(root.suggestionQuery, Ai.availableTools.map(tool => {\n                                    return {\n                                        name: Fuzzy.prepare(tool),\n                                        obj: tool\n                                    };\n                                }), {\n                                    all: true,\n                                    key: \"name\"\n                                });\n                                root.suggestionList = toolResults.map(tool => {\n                                    const toolName = tool.target;\n                                    return {\n                                        name: `${messageInputField.text.trim().split(\" \").length == 1 ? (root.commandPrefix + \"tool \") : \"\"}${tool.target}`,\n                                        displayName: toolName,\n                                        description: Ai.toolDescriptions[toolName]\n                                    };\n                                });\n                            } else if (messageInputField.text.startsWith(root.commandPrefix)) {\n                                root.suggestionQuery = messageInputField.text;\n                                root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(messageInputField.text.substring(1))).map(cmd => {\n                                    return {\n                                        name: `${root.commandPrefix}${cmd.name}`,\n                                        description: `${cmd.description}`\n                                    };\n                                });\n                            }\n                        }\n\n                        function accept() {\n                            root.handleInput(text);\n                            text = \"\";\n                        }\n\n                        Keys.onPressed: event => {\n                            if (event.key === Qt.Key_Tab) {\n                                suggestions.acceptSelectedWord();\n                                event.accepted = true;\n                            } else if (event.key === Qt.Key_Up && suggestions.visible) {\n                                suggestions.selectedIndex = Math.max(0, suggestions.selectedIndex - 1);\n                                event.accepted = true;\n                            } else if (event.key === Qt.Key_Down && suggestions.visible) {\n                                suggestions.selectedIndex = Math.min(root.suggestionList.length - 1, suggestions.selectedIndex + 1);\n                                event.accepted = true;\n                            } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {\n                                if (event.modifiers & Qt.ShiftModifier) {\n                                    // Insert newline\n                                    messageInputField.insert(messageInputField.cursorPosition, \"\\n\");\n                                    event.accepted = true;\n                                } else {\n                                    // Accept text\n                                    const inputText = messageInputField.text;\n                                    messageInputField.clear();\n                                    root.handleInput(inputText);\n                                    event.accepted = true;\n                                }\n                            } else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) {\n                                // Intercept Ctrl+V to handle image/file pasting\n                                if (event.modifiers & Qt.ShiftModifier) {\n                                    // Let Shift+Ctrl+V = plain paste\n                                    messageInputField.text += Quickshell.clipboardText;\n                                    event.accepted = true;\n                                    return;\n                                }\n                                // Try image paste first\n                                const currentClipboardEntry = Cliphist.entries[0];\n                                const cleanCliphistEntry = StringUtils.cleanCliphistEntry(currentClipboardEntry);\n                                if (/^\\d+\\t\\[\\[.*binary data.*\\d+x\\d+.*\\]\\]$/.test(currentClipboardEntry)) {\n                                    // First entry = currently copied entry = image?\n                                    decodeImageAndAttachProc.handleEntry(currentClipboardEntry);\n                                    event.accepted = true;\n                                    return;\n                                } else if (cleanCliphistEntry.startsWith(\"file://\")) {\n                                    // First entry = currently copied entry = image?\n                                    const fileName = decodeURIComponent(cleanCliphistEntry);\n                                    Ai.attachFile(fileName);\n                                    event.accepted = true;\n                                    return;\n                                }\n                                event.accepted = false; // No image, let text pasting proceed\n                            } else if (event.key === Qt.Key_Escape) {\n                                // Esc to detach file\n                                if (Ai.pendingFilePath.length > 0) {\n                                    Ai.attachFile(\"\");\n                                    event.accepted = true;\n                                } else {\n                                    event.accepted = false;\n                                }\n                            }\n                        }\n                    }\n                }\n                RippleButton { // Send button\n                    id: sendButton\n                    Layout.alignment: Qt.AlignBottom\n                    Layout.rightMargin: 5\n                    implicitWidth: 40\n                    implicitHeight: 40\n                    buttonRadius: Appearance.rounding.small\n                    enabled: messageInputField.text.length > 0\n                    toggled: enabled\n\n                    MouseArea {\n                        anchors.fill: parent\n                        cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor\n                        onClicked: {\n                            const inputText = messageInputField.text;\n                            root.handleInput(inputText);\n                            messageInputField.clear();\n                        }\n                    }\n\n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        iconSize: 22\n                        color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled\n                        text: \"arrow_upward\"\n                    }\n                }\n            }\n\n            RowLayout { // Controls\n                id: commandButtonsRow\n                anchors.left: parent.left\n                anchors.right: parent.right\n                anchors.bottom: parent.bottom\n                anchors.bottomMargin: 5\n                anchors.leftMargin: 10\n                anchors.rightMargin: 5\n                spacing: 4\n\n                property var commandsShown: [\n                    {\n                        name: \"\",\n                        sendDirectly: false,\n                        dontAddSpace: true\n                    },\n                    {\n                        name: \"clear\",\n                        sendDirectly: true\n                    },\n                ]\n\n                ApiInputBoxIndicator {\n                    // Model indicator\n                    icon: \"api\"\n                    text: Ai.getModel().name\n                    tooltipText: Translation.tr(\"Current model: %1\\nSet it with %2model MODEL\").arg(Ai.getModel().name).arg(root.commandPrefix)\n                }\n\n                ApiInputBoxIndicator {\n                    // Tool indicator\n                    icon: \"service_toolbox\"\n                    text: Ai.currentTool.charAt(0).toUpperCase() + Ai.currentTool.slice(1)\n                    tooltipText: Translation.tr(\"Current tool: %1\\nSet it with %2tool TOOL\").arg(Ai.currentTool).arg(root.commandPrefix)\n                }\n\n                Item {\n                    Layout.fillWidth: true\n                }\n\n                ButtonGroup {\n                    // Command buttons\n                    padding: 0\n\n                    Repeater {\n                        // Command buttons\n                        model: commandButtonsRow.commandsShown\n                        delegate: ApiCommandButton {\n                            property string commandRepresentation: `${root.commandPrefix}${modelData.name}`\n                            buttonText: commandRepresentation\n                            downAction: () => {\n                                if (modelData.sendDirectly) {\n                                    root.handleInput(commandRepresentation);\n                                } else {\n                                    messageInputField.text = commandRepresentation + (modelData.dontAddSpace ? \"\" : \" \");\n                                    messageInputField.cursorPosition = messageInputField.text.length;\n                                    messageInputField.forceActiveFocus();\n                                }\n                                if (modelData.name === \"clear\") {\n                                    messageInputField.text = \"\";\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/Anime.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.ii.sidebarLeft.anime\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\n\nItem {\n    id: root\n    property real padding: 4\n\n    property var inputField: tagInputField\n    readonly property var responses: Booru.responses\n    property string previewDownloadPath: Directories.booruPreviews\n    property string downloadPath: Directories.booruDownloads\n    property string nsfwPath: Directories.booruDownloadsNsfw\n    property string commandPrefix: \"/\"\n    property real scrollOnNewResponse: 100\n    property int tagSuggestionDelay: 210\n    property var suggestionQuery: \"\"\n    property var suggestionList: []\n\n    property bool pullLoading: false\n    property int pullLoadingGap: 80\n    property real normalizedPullDistance: Math.max(0, (1 - Math.exp(-booruResponseListView.verticalOvershoot / 50)) * booruResponseListView.dragging)\n\n    Connections {\n        target: Booru\n        function onTagSuggestion(query, suggestions) {\n            root.suggestionQuery = query;\n            root.suggestionList = suggestions;\n        }\n        function onRunningRequestsChanged() {\n            if (Booru.runningRequests === 0) {\n                root.pullLoading = false;\n            }\n        }\n    }\n\n    property var allCommands: [\n        {\n            name: \"mode\",\n            description: Translation.tr(\"Set the current API provider\"),\n            execute: (args) => {\n                Booru.setProvider(args[0]);\n            }\n        },\n        {\n            name: \"clear\",\n            description: Translation.tr(\"Clear the current list of images\"),\n            execute: () => {\n                Booru.clearResponses();\n            }\n        },\n        {\n            name: \"next\",\n            description: Translation.tr(\"Get the next page of results\"),\n            execute: () => {\n                if (root.responses.length > 0) {\n                    const lastResponse = root.responses[root.responses.length - 1];\n                    root.handleInput(`${lastResponse.tags.join(\" \")} ${parseInt(lastResponse.page) + 1}`);\n                } else {\n                    root.handleInput(\"\");\n                }\n            }\n        },\n        {\n            name: \"safe\",\n            description: Translation.tr(\"Disable NSFW content\"),\n            execute: () => {\n                Persistent.states.booru.allowNsfw = false;\n            }\n        },\n        {\n            name: \"lewd\",\n            description: Translation.tr(\"Allow NSFW content\"),\n            execute: () => {\n                Persistent.states.booru.allowNsfw = true;\n            }\n        },\n    ]\n\n    function handleInput(inputText) {\n        if (inputText.startsWith(root.commandPrefix)) {\n            // Handle special commands\n            const command = inputText.split(\" \")[0].substring(1);\n            const args = inputText.split(\" \").slice(1);\n            const commandObj = root.allCommands.find(cmd => cmd.name === `${command}`);\n            if (commandObj) {\n                commandObj.execute(args);\n            } else {\n                Booru.addSystemMessage(Translation.tr(\"Unknown command: \") + command);\n            }\n        }\n        else if (inputText.trim() == \"+\") {\n            root.handleInput(`${root.commandPrefix}next`);\n        }\n        else {\n            // Create tag list\n            const tagList = inputText.split(/\\s+/).filter(tag => tag.length > 0);\n            let pageIndex = 1;\n            for (let i = 0; i < tagList.length; ++i) { // Detect page number\n                if (/^\\d+$/.test(tagList[i])) {\n                    pageIndex = parseInt(tagList[i], 10);\n                    tagList.splice(i, 1);\n                    break;\n                }\n            }\n            Booru.makeRequest(tagList, Persistent.states.booru.allowNsfw, Config.options.sidebar.booru.limit, pageIndex);\n        }\n    }\n\n    onFocusChanged: (focus) => {\n        if (focus) {\n            tagInputField.forceActiveFocus()\n        }\n    }\n\n    property real pageKeyScrollAmount: booruResponseListView.height / 2\n    Keys.onPressed: (event) => {\n        tagInputField.forceActiveFocus()\n        if (event.modifiers === Qt.NoModifier) {\n            if (event.key === Qt.Key_PageUp) {\n                if (booruResponseListView.atYBeginning) return;\n                booruResponseListView.contentY = Math.max(0, booruResponseListView.contentY - root.pageKeyScrollAmount)\n                event.accepted = true\n            } else if (event.key === Qt.Key_PageDown) {\n                if (booruResponseListView.atYEnd) return;\n                booruResponseListView.contentY = Math.min(booruResponseListView.contentHeight, booruResponseListView.contentY + root.pageKeyScrollAmount)\n                event.accepted = true\n            }\n        }\n        if ((event.modifiers & Qt.ControlModifier) && (event.modifiers & Qt.ShiftModifier) && event.key === Qt.Key_O) {\n            Booru.clearResponses()\n        }\n    }\n\n\n    ColumnLayout {\n        id: columnLayout\n        anchors {\n            fill: parent\n            margins: root.padding\n        }\n        spacing: root.padding\n\n        Item {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: swipeView.width\n                    height: swipeView.height\n                    radius: Appearance.rounding.small\n                }\n            }\n\n            ScrollEdgeFade {\n                z: 1\n                target: booruResponseListView\n                vertical: true\n            }\n\n            StyledListView { // Booru responses\n                id: booruResponseListView\n                z: 0\n                anchors.fill: parent\n                spacing: 10\n                \n                touchpadScrollFactor: Config.options.interactions.scrolling.touchpadScrollFactor * 1.4\n                mouseScrollFactor: Config.options.interactions.scrolling.mouseScrollFactor * 1.4\n\n                property int lastResponseLength: 0\n                Connections {\n                    target: root\n                    function onResponsesChanged() {\n                        if (root.responses.length > booruResponseListView.lastResponseLength) {\n                            if (booruResponseListView.lastResponseLength > 0 && root.responses[booruResponseListView.lastResponseLength].provider != \"system\")\n                                booruResponseListView.contentY = booruResponseListView.contentY + root.scrollOnNewResponse\n                            booruResponseListView.lastResponseLength = root.responses.length\n                        }\n                    }\n                }\n\n                model: ScriptModel {\n                    values: root.responses\n                }\n                delegate: BooruResponse {\n                    responseData: modelData\n                    tagInputField: root.inputField\n                    previewDownloadPath: root.previewDownloadPath\n                    downloadPath: root.downloadPath\n                    nsfwPath: root.nsfwPath\n                }\n\n                onDragEnded: { // Pull to load more\n                    const gap = booruResponseListView.verticalOvershoot\n                    if (gap > root.pullLoadingGap) {\n                        root.pullLoading = true\n                        root.handleInput(`${root.commandPrefix}next`)\n                    }\n                }\n            }\n\n            PagePlaceholder {\n                id: placeholderItem\n                z: 2\n                shown: root.responses.length === 0\n                icon: \"bookmark_heart\"\n                title: Translation.tr(\"Anime boorus\")\n                description: \"\"\n                shape: MaterialShape.Shape.Bun\n            }\n\n            ScrollToBottomButton {\n                z: 3\n                target: booruResponseListView\n            }\n\n            MaterialLoadingIndicator {\n                id: loadingIndicator\n                z: 4\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    bottom: parent.bottom\n                    bottomMargin: 20 + (root.pullLoading ? 0 : Math.max(0, (root.normalizedPullDistance - 0.5) * 50))\n                    Behavior on bottomMargin {\n                        NumberAnimation {\n                            duration: 200\n                            easing.type: Easing.BezierSpline\n                            easing.bezierCurve: Appearance.animationCurves.expressiveFastSpatial\n                        }\n                    }\n                }\n                loading: root.pullLoading || Booru.runningRequests > 0\n                pullProgress: Math.min(1, booruResponseListView.verticalOvershoot / root.pullLoadingGap * booruResponseListView.dragging)\n                scale: root.pullLoading ? 1 : Math.min(1, root.normalizedPullDistance * 2)\n            }\n        }\n\n        DescriptionBox { // Tag suggestion description\n            text: root.suggestionList[tagSuggestions.selectedIndex]?.description ?? \"\"\n            showArrows: root.suggestionList.length > 1\n        }\n\n        FlowButtonGroup { // Tag suggestions\n            id: tagSuggestions\n            visible: root.suggestionList.length > 0 && tagInputField.text.length > 0\n            property int selectedIndex: 0\n            Layout.fillWidth: true\n            spacing: 5\n\n            Repeater {\n                id: tagSuggestionRepeater\n                model: {\n                    tagSuggestions.selectedIndex = 0\n                    return root.suggestionList.slice(0, 10)\n                }\n                delegate: ApiCommandButton {\n                    id: tagButton\n                    colBackground: tagSuggestions.selectedIndex === index ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colSecondaryContainer\n                    bounce: false\n                    contentItem: RowLayout {\n                        anchors.centerIn: parent\n                        spacing: 5\n                        StyledText {\n                            Layout.fillWidth: false\n                            font.pixelSize: Appearance.font.pixelSize.small\n                            color: Appearance.colors.colOnSecondaryContainer\n                            horizontalAlignment: Text.AlignRight\n                            text: modelData.displayName ?? modelData.name\n                        }\n                        StyledText {\n                            Layout.fillWidth: false\n                            visible: modelData.count !== undefined\n                            font.pixelSize: Appearance.font.pixelSize.smaller\n                            color: Appearance.colors.colOnSecondaryContainer\n                            horizontalAlignment: Text.AlignLeft\n                            text: modelData.count ?? \"\"\n                        }\n                    }\n\n                    onHoveredChanged: {\n                        if (tagButton.hovered) {\n                            tagSuggestions.selectedIndex = index;\n                        }\n                    }\n                    onClicked: {\n                        tagSuggestions.acceptTag(modelData.name)\n                    }\n                }\n            }\n\n            function acceptTag(tag) {\n                const words = tagInputField.text.trim().split(/\\s+/);\n                if (words.length > 0) {\n                    words[words.length - 1] = tag;\n                } else {\n                    words.push(tag);\n                }\n                const updatedText = words.join(\" \") + \" \";\n                tagInputField.text = updatedText;\n                tagInputField.cursorPosition = tagInputField.text.length;\n                tagInputField.forceActiveFocus();\n            }\n\n            function acceptSelectedTag() {\n                if (tagSuggestions.selectedIndex >= 0 && tagSuggestions.selectedIndex < tagSuggestionRepeater.count) {\n                    const tag = root.suggestionList[tagSuggestions.selectedIndex].name;\n                    tagSuggestions.acceptTag(tag);\n                }\n            }\n        }\n\n        Rectangle { // Tag input area\n            id: tagInputContainer\n            property real columnSpacing: 5\n            Layout.fillWidth: true\n            radius: Appearance.rounding.normal - root.padding\n            color: Appearance.colors.colLayer2\n            implicitWidth: tagInputField.implicitWidth\n            implicitHeight: Math.max(inputFieldRowLayout.implicitHeight + inputFieldRowLayout.anchors.topMargin \n                + commandButtonsRow.implicitHeight + commandButtonsRow.anchors.bottomMargin + columnSpacing, 45)\n            clip: true\n\n            Behavior on implicitHeight {\n                animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n            }\n\n            RowLayout { // Input field and send button\n                id: inputFieldRowLayout\n                anchors.top: parent.top\n                anchors.left: parent.left\n                anchors.right: parent.right\n                anchors.topMargin: 5\n                spacing: 0\n\n                StyledTextArea { // The actual TextArea\n                    id: tagInputField\n                    wrapMode: TextArea.Wrap\n                    Layout.fillWidth: true\n                    padding: 10\n                    color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant\n                    renderType: Text.NativeRendering\n                    placeholderText: Translation.tr('Enter tags, or \"%1\" for commands').arg(root.commandPrefix)\n\n                    background: null\n\n                    property Timer searchTimer: Timer { // Timer for tag suggestions\n                        interval: root.tagSuggestionDelay\n                        repeat: false\n                        onTriggered: {\n                            const inputText = tagInputField.text\n                            const words = inputText.trim().split(/\\s+/);\n                            if (words.length > 0) {\n                                Booru.triggerTagSearch(words[words.length - 1]);\n                            }\n                        }\n                    }\n\n                    onTextChanged: { // Handle tag suggestions\n                        if(tagInputField.text.length === 0) {\n                            root.suggestionQuery = \"\"\n                            root.suggestionList = []\n                            searchTimer.stop();\n                            return\n                        }\n                        if(tagInputField.text.startsWith(`${root.commandPrefix}mode`)) {\n                            root.suggestionQuery = tagInputField.text.split(\" \")[1] ?? \"\"\n                            const providerResults = Fuzzy.go(root.suggestionQuery, Booru.providerList.map(provider => {\n                                return {\n                                    name: Fuzzy.prepare(provider),\n                                    obj: provider,\n                                }\n                            }), {\n                                all: true,\n                                key: \"name\"\n                            })\n                            root.suggestionList = providerResults.map(provider => {\n                                return {\n                                    name: `${tagInputField.text.trim().split(\" \").length == 1 ? (root.commandPrefix + \"mode \") : \"\"}${provider.target}`,\n                                    displayName: `${Booru.providers[provider.target].name}`,\n                                    description: `${Booru.providers[provider.target].description}`,\n                                }\n                            })\n                            searchTimer.stop();\n                            return\n                        }\n                        if(tagInputField.text.startsWith(root.commandPrefix)) {\n                            root.suggestionQuery = tagInputField.text\n                            root.suggestionList = root.allCommands.filter(cmd => cmd.name.startsWith(tagInputField.text.substring(1))).map(cmd => {\n                                return {\n                                    name: `${root.commandPrefix}${cmd.name}`,\n                                    description: `${cmd.description}`,\n                                }\n                            })\n                            searchTimer.stop();\n                            return\n                        }\n                        searchTimer.restart();\n                    }\n\n                    function accept() {\n                        root.handleInput(text)\n                        text = \"\"\n                    }\n\n                    Keys.onPressed: (event) => {\n                        if (event.key === Qt.Key_Tab) {\n                            tagSuggestions.acceptSelectedTag();\n                            event.accepted = true;\n                        } else if (event.key === Qt.Key_Up) {\n                            tagSuggestions.selectedIndex = Math.max(0, tagSuggestions.selectedIndex - 1);\n                            event.accepted = true;\n                        } else if (event.key === Qt.Key_Down) {\n                            tagSuggestions.selectedIndex = Math.min(root.suggestionList.length - 1, tagSuggestions.selectedIndex + 1);\n                            event.accepted = true;\n                        } else if ((event.key === Qt.Key_Enter || event.key === Qt.Key_Return)) {\n                            if (event.modifiers & Qt.ShiftModifier) {\n                                // Insert newline\n                                tagInputField.insert(tagInputField.cursorPosition, \"\\n\")\n                                event.accepted = true\n                            } else { // Accept text\n                                const inputText = tagInputField.text\n                                root.handleInput(inputText)\n                                tagInputField.clear()\n                                event.accepted = true\n                            }\n                        }\n                    }\n                }\n\n                RippleButton { // Send button\n                    id: sendButton\n                    Layout.alignment: Qt.AlignTop\n                    Layout.rightMargin: 5\n                    implicitWidth: 40\n                    implicitHeight: 40\n                    buttonRadius: Appearance.rounding.small\n                    enabled: tagInputField.text.length > 0\n                    toggled: enabled\n\n                    MouseArea {\n                        anchors.fill: parent\n                        cursorShape: sendButton.enabled ? Qt.PointingHandCursor : Qt.ArrowCursor\n                        onClicked: {\n                            const inputText = tagInputField.text\n                            root.handleInput(inputText)\n                            tagInputField.clear()\n                        }\n                    }\n\n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        iconSize: 22\n                        color: sendButton.enabled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer2Disabled\n                        text: \"arrow_upward\"\n                    }\n                }\n            }\n\n            RowLayout { // Controls\n                id: commandButtonsRow\n                anchors.left: parent.left\n                anchors.right: parent.right\n                anchors.bottom: parent.bottom\n                anchors.bottomMargin: 5\n                anchors.leftMargin: 5\n                anchors.rightMargin: 5\n                spacing: 5\n\n                property var commandsShown: [\n                    {\n                        name: \"mode\",\n                        sendDirectly: false,\n                    },\n                    {\n                        name: \"clear\",\n                        sendDirectly: true,\n                    }, \n                ]\n\n                ApiInputBoxIndicator { // Tool indicator\n                    icon: \"api\"\n                    text: Booru.providers[Booru.currentProvider].name\n                    tooltipText: Translation.tr(\"Current API endpoint: %1\\nSet it with %2mode PROVIDER\")\n                        .arg(Booru.providers[Booru.currentProvider].url)\n                        .arg(root.commandPrefix)\n                }\n\n                StyledText {\n                    font.pixelSize: Appearance.font.pixelSize.large\n                    color: Appearance.colors.colOnLayer1\n                    text: \"•\"\n                }\n\n                MouseArea { // NSFW toggle\n                    visible: width > 0\n                    implicitWidth: switchesRow.implicitWidth\n                    Layout.fillHeight: true\n\n                    hoverEnabled: true\n                    PointingHandInteraction {}\n                    onPressed: {\n                        nsfwSwitch.checked = !nsfwSwitch.checked\n                    }\n\n                    RowLayout {\n                        id: switchesRow\n                        spacing: 5\n                        anchors.centerIn: parent\n\n                        StyledText {\n                            Layout.fillHeight: true\n                            Layout.leftMargin: 10\n                            Layout.alignment: Qt.AlignVCenter\n                            font.pixelSize: Appearance.font.pixelSize.smaller\n                            color: nsfwSwitch.enabled ? Appearance.colors.colOnLayer1 : Appearance.m3colors.m3outline\n                            text: Translation.tr(\"Allow NSFW\")\n                        }\n                        StyledSwitch {\n                            id: nsfwSwitch\n                            enabled: Booru.currentProvider !== \"zerochan\"\n                            scale: 0.6\n                            Layout.alignment: Qt.AlignVCenter\n                            checked: (Persistent.states.booru.allowNsfw && Booru.currentProvider !== \"zerochan\")\n                            onCheckedChanged: {\n                                if (!nsfwSwitch.enabled) return;\n                                Persistent.states.booru.allowNsfw = checked;\n                            }\n                        }\n                    }\n\n                }\n\n                Item { Layout.fillWidth: true }\n\n                ButtonGroup {\n                    padding: 0\n                    Repeater { // Command buttons\n                        id: commandRepeater\n                        model: commandButtonsRow.commandsShown\n                        delegate: ApiCommandButton {\n                            property string commandRepresentation: `${root.commandPrefix}${modelData.name}`\n                            buttonText: commandRepresentation\n                            colBackground: Appearance.colors.colLayer2\n\n                            downAction: () => {\n                                if (modelData.sendDirectly) {\n                                    root.handleInput(commandRepresentation)\n                                } else {\n                                    tagInputField.text = commandRepresentation + \" \"\n                                    tagInputField.cursorPosition = tagInputField.text.length\n                                    tagInputField.forceActiveFocus()\n                                }\n                                if (modelData.name === \"clear\") {\n                                    tagInputField.text = \"\"\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/ApiCommandButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nGroupButton {\n    id: button\n    property string buttonText\n\n    horizontalPadding: 8\n    verticalPadding: 6\n\n    baseWidth: contentItem.implicitWidth + horizontalPadding * 2\n    clickedWidth: baseWidth + 14\n    baseHeight: contentItem.implicitHeight + verticalPadding * 2\n    buttonRadius: down ? Appearance.rounding.verysmall : Appearance.rounding.small\n\n    colBackground: Appearance.colors.colLayer2\n    colBackgroundHover: Appearance.colors.colLayer2Hover\n    colBackgroundActive: Appearance.colors.colLayer2Active\n\n    contentItem: StyledText {\n        horizontalAlignment: Text.AlignHCenter\n        text: buttonText\n        color: Appearance.m3colors.m3onSurface\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/ApiInputBoxIndicator.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nItem { // Model indicator\n    id: root\n    property string icon: \"api\"\n    property string text: \"\"\n    property string tooltipText: \"\"\n    implicitHeight: rowLayout.implicitHeight + 4 * 2\n    implicitWidth: rowLayout.implicitWidth + 4 * 2\n\n    RowLayout {\n        id: rowLayout\n        anchors.centerIn: parent\n\n        MaterialSymbol {\n            text: root.icon\n            iconSize: Appearance.font.pixelSize.normal\n        }\n        StyledText {\n            id: providerName\n            font.pixelSize: Appearance.font.pixelSize.smaller\n            color: Appearance.m3colors.m3onSurface\n            elide: Text.ElideRight\n            text: root.text\n            animateChange: true\n        }\n    }\n\n    Loader {\n        active: root.tooltipText?.length > 0\n        anchors.fill: parent\n        sourceComponent: MouseArea {\n            id: mouseArea\n            hoverEnabled: true\n\n            StyledToolTip {\n                id: toolTip\n                extraVisibleCondition: false\n                alternativeVisibleCondition: mouseArea.containsMouse // Show tooltip when hovered\n                text: root.tooltipText\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/DescriptionBox.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nItem { // Tag suggestion description\n    id: root\n    property alias text: tagDescriptionText.text\n    property bool showArrows: true\n    property bool showTab: true\n\n    visible: tagDescriptionText.text.length > 0\n    Layout.fillWidth: true\n    implicitHeight: tagDescriptionBackground.implicitHeight\n\n    Rectangle {\n        id: tagDescriptionBackground\n        color: Appearance.colors.colLayer2\n        anchors.fill: parent\n        radius: Appearance.rounding.verysmall\n        implicitHeight: descriptionRow.implicitHeight + 5 * 2\n\n        RowLayout {\n            id: descriptionRow\n            spacing: 4\n            anchors {\n                fill: parent\n                leftMargin: 10\n                rightMargin: 10\n            }\n\n            StyledText {\n                id: tagDescriptionText\n                Layout.fillWidth: true\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                color: Appearance.colors.colOnLayer2\n                wrapMode: Text.Wrap\n            }\n            KeyboardKey {\n                visible: root.showArrows\n                key: \"↑\"\n            }\n            KeyboardKey {\n                visible: root.showArrows\n                key: \"↓\"\n            }\n            StyledText {\n                visible: root.showArrows && root.showTab\n                text: Translation.tr(\"or\")\n                font.pixelSize: Appearance.font.pixelSize.smaller\n            }\n            KeyboardKey {\n                id: tagDescriptionKey\n                visible: root.showTab\n                key: \"Tab\"\n                Layout.alignment: Qt.AlignVCenter\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/ScrollToBottomButton.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton {\n    id: root\n    required property ListView target\n\n    anchors {\n        bottom: parent.bottom\n        horizontalCenter: parent.horizontalCenter\n        bottomMargin: 10\n    }\n\n    opacity: !target.atYEnd ? 1 : 0\n    scale: !target.atYEnd ? 1 : 0.7\n    visible: opacity > 0\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n    Behavior on scale {\n        animation: Appearance.animation.elementResize.numberAnimation.createObject(this)\n    }\n\n    implicitWidth: contentItem.implicitWidth + 8 * 2\n    implicitHeight: contentItem.implicitHeight + 4 * 2\n\n    colBackground: Appearance.colors.colSecondary\n    colBackgroundHover: Appearance.colors.colSecondaryHover\n    colRipple: Appearance.colors.colSecondaryActive\n    buttonRadius: Appearance.rounding.verysmall\n\n    downAction: () => {\n        target.positionViewAtEnd()\n    }\n\n    contentItem: Row {\n        id: contentItem\n        spacing: 4\n        MaterialSymbol {\n            anchors.verticalCenter: parent.verticalCenter\n            text: \"arrow_downward\"\n            font.pixelSize: Appearance.font.pixelSize.larger\n            color: Appearance.colors.colOnSecondary\n            verticalAlignment: Text.AlignVCenter\n        }\n        StyledText {\n            anchors.verticalCenter: parent.verticalCenter\n            text: Translation.tr(\"Scroll to Bottom\")\n            font.pixelSize: Appearance.font.pixelSize.smallie\n            color: Appearance.colors.colOnSecondary\n            verticalAlignment: Text.AlignVCenter\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeft.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope { // Scope\n    id: root\n    property bool detach: false\n    property bool pin: false\n    property Component contentComponent: SidebarLeftContent {}\n    property Item sidebarContent\n\n    function toggleDetach() {\n        root.detach = !root.detach;\n    }\n\n    Process { // Dodge cursor away, pin, move cursor back\n        id: pinWithFunnyHyprlandWorkaroundProc\n        property var hook: null\n        property int cursorX;\n        property int cursorY;\n        function doIt() {\n            command = [\"hyprctl\", \"cursorpos\"]\n            hook = (output) => {\n                cursorX = parseInt(output.split(\",\")[0]);\n                cursorY = parseInt(output.split(\",\")[1]);\n                doIt2();\n            }\n            running = true;\n        }\n        function doIt2(output) {\n            command = [\"bash\", \"-c\", \"hyprctl dispatch 'hl.dsp.cursor.move({x=9999,y=9999})'\"];\n            hook = () => {\n                doIt3();\n            }\n            running = true;\n        }\n        function doIt3(output) {\n            root.pin = !root.pin;\n            command = [\"bash\", \"-c\", `sleep 0.01; hyprctl dispatch 'hl.dsp.cursor.move({x=${cursorX},y=${cursorY}})'`];\n            hook = null\n            running = true;\n        }\n        stdout: StdioCollector {\n            onStreamFinished: {\n                pinWithFunnyHyprlandWorkaroundProc.hook(text);\n            }\n        }\n    }\n\n    function togglePin() {\n        if (!root.pin) pinWithFunnyHyprlandWorkaroundProc.doIt()\n        else root.pin = !root.pin;\n    }\n\n    Component.onCompleted: {\n        root.sidebarContent = contentComponent.createObject(null, {\n            \"scopeRoot\": root,\n        });\n        sidebarLoader.item.contentParent.children = [root.sidebarContent];\n    }\n\n    onDetachChanged: {\n        if (root.detach) {\n            GlobalFocusGrab.removeDismissable(sidebarLoader.item) // Remove sidebar from the focus grab system\n            sidebarContent.parent = null; // Detach content from sidebar\n            sidebarLoader.active = false; // Unload sidebar\n            detachedSidebarLoader.active = true; // Load detached window\n            detachedSidebarLoader.item.contentParent.children = [sidebarContent];\n        } else {\n            sidebarContent.parent = null; // Detach content from window\n            detachedSidebarLoader.active = false; // Unload detached window\n            sidebarLoader.active = true; // Load sidebar\n            sidebarLoader.item.contentParent.children = [sidebarContent];\n        }\n    }\n\n    Loader {\n        id: sidebarLoader\n        active: true\n        \n        sourceComponent: PanelWindow { // Window\n            id: panelWindow\n            visible: GlobalStates.sidebarLeftOpen\n            \n            property bool extend: false\n            property real sidebarWidth: panelWindow.extend ? Appearance.sizes.sidebarWidthExtended : Appearance.sizes.sidebarWidth\n            property var contentParent: sidebarLeftBackground\n\n            function hide() {\n                GlobalStates.sidebarLeftOpen = false\n            }\n\n            exclusionMode: ExclusionMode.Normal\n            exclusiveZone: root.pin ? sidebarWidth : 0\n            implicitWidth: Appearance.sizes.sidebarWidthExtended + Appearance.sizes.elevationMargin\n            WlrLayershell.namespace: \"quickshell:sidebarLeft\"\n            // Hyprland 0.49: OnDemand is Exclusive, Exclusive just breaks click-outside-to-close\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n            color: \"transparent\"\n\n            anchors {\n                top: true\n                left: true\n                bottom: true\n            }\n\n            mask: Region {\n                item: sidebarLeftBackground\n            }\n\n            onVisibleChanged: {\n                if (visible) {\n                    GlobalFocusGrab.addDismissable(panelWindow);\n                } else {\n                    GlobalFocusGrab.removeDismissable(panelWindow);\n                }\n            }\n            Connections {\n                target: GlobalFocusGrab\n                function onDismissed() {\n                    panelWindow.hide();\n                }\n            }\n\n            // Content\n            StyledRectangularShadow {\n                target: sidebarLeftBackground\n                radius: sidebarLeftBackground.radius\n            }\n            Rectangle {\n                id: sidebarLeftBackground\n                anchors.top: parent.top\n                anchors.left: parent.left\n                anchors.topMargin: Appearance.sizes.hyprlandGapsOut\n                anchors.leftMargin: Appearance.sizes.hyprlandGapsOut\n                width: panelWindow.sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin\n                height: parent.height - Appearance.sizes.hyprlandGapsOut * 2\n                color: Appearance.colors.colLayer0\n                border.width: 1\n                border.color: Appearance.colors.colLayer0Border\n                radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1\n\n                Behavior on width {\n                    animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n                }\n\n                Keys.onPressed: (event) => {\n                    if (event.key === Qt.Key_Escape) {\n                        panelWindow.hide();\n                    }\n                    if (event.modifiers === Qt.ControlModifier) {\n                        if (event.key === Qt.Key_O) {\n                            panelWindow.extend = !panelWindow.extend;\n                        } else if (event.key === Qt.Key_D) {\n                            root.toggleDetach();\n                        } else if (event.key === Qt.Key_P) {\n                            root.togglePin();\n                        }\n                        event.accepted = true;\n                    }\n                }\n            }\n        }\n    }\n\n    Loader {\n        id: detachedSidebarLoader\n        active: false\n\n        sourceComponent: FloatingWindow {\n            id: detachedSidebarRoot\n            property var contentParent: detachedSidebarBackground\n            color: \"transparent\"\n\n            visible: GlobalStates.sidebarLeftOpen\n            onVisibleChanged: {\n                if (!visible) GlobalStates.sidebarLeftOpen = false;\n            }\n            \n            Rectangle {\n                id: detachedSidebarBackground\n                anchors.fill: parent\n                color: Appearance.colors.colLayer0\n\n                Keys.onPressed: (event) => {\n                    if (event.modifiers === Qt.ControlModifier) {\n                        if (event.key === Qt.Key_D) {\n                            root.toggleDetach();\n                        }\n                        event.accepted = true;\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"sidebarLeft\"\n\n        function toggle(): void {\n            GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen\n        }\n\n        function close(): void {\n            GlobalStates.sidebarLeftOpen = false\n        }\n\n        function open(): void {\n            GlobalStates.sidebarLeftOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarLeftToggle\"\n        description: \"Toggles left sidebar on press\"\n\n        onPressed: {\n            GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarLeftOpen\"\n        description: \"Opens left sidebar on press\"\n\n        onPressed: {\n            GlobalStates.sidebarLeftOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarLeftClose\"\n        description: \"Closes left sidebar on press\"\n\n        onPressed: {\n            GlobalStates.sidebarLeftOpen = false;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarLeftToggleDetach\"\n        description: \"Detach left sidebar into a window/Attach it back\"\n\n        onPressed: {\n            root.detach = !root.detach;\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/SidebarLeftContent.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Qt.labs.synchronizer\n\nItem {\n    id: root\n    required property var scopeRoot\n    property int sidebarPadding: 10\n    anchors.fill: parent\n    property bool aiChatEnabled: Config.options.policies.ai !== 0\n    property bool translatorEnabled: Config.options.sidebar.translator.enable\n    property bool animeEnabled: Config.options.policies.weeb !== 0\n    property bool animeCloset: Config.options.policies.weeb === 2\n    property var tabButtonList: [\n        ...(root.aiChatEnabled ? [{\"icon\": \"neurology\", \"name\": Translation.tr(\"Intelligence\")}] : []),\n        ...(root.translatorEnabled ? [{\"icon\": \"translate\", \"name\": Translation.tr(\"Translator\")}] : []),\n        ...((root.animeEnabled && !root.animeCloset) ? [{\"icon\": \"bookmark_heart\", \"name\": Translation.tr(\"Anime\")}] : [])\n    ]\n    property int tabCount: swipeView.count\n\n    function focusActiveItem() {\n        swipeView.currentItem.forceActiveFocus()\n    }\n\n    Keys.onPressed: (event) => {\n        if (event.modifiers === Qt.ControlModifier) {\n            if (event.key === Qt.Key_PageDown) {\n                swipeView.incrementCurrentIndex()\n                event.accepted = true;\n            }\n            else if (event.key === Qt.Key_PageUp) {\n                swipeView.decrementCurrentIndex()\n                event.accepted = true;\n            }\n        }\n    }\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            margins: sidebarPadding\n        }\n        spacing: sidebarPadding\n\n        Toolbar {\n            visible: tabButtonList.length > 0\n            Layout.alignment: Qt.AlignHCenter\n            enableShadow: false\n            ToolbarTabBar {\n                id: tabBar\n                Layout.alignment: Qt.AlignHCenter\n                tabButtonList: root.tabButtonList\n                currentIndex: swipeView.currentIndex\n            }\n        }\n\n        Rectangle {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            implicitWidth: swipeView.implicitWidth\n            implicitHeight: swipeView.implicitHeight\n            radius: Appearance.rounding.normal\n            color: Appearance.colors.colLayer1\n\n            SwipeView { // Content pages\n                id: swipeView\n                anchors.fill: parent\n                spacing: 10\n                currentIndex: tabBar.currentIndex\n\n                clip: true\n                layer.enabled: true\n                layer.effect: OpacityMask {\n                    maskSource: Rectangle {\n                        width: swipeView.width\n                        height: swipeView.height\n                        radius: Appearance.rounding.small\n                    }\n                }\n\n                contentChildren: [\n                    ...(root.aiChatEnabled ? [aiChat.createObject()] : []),\n                    ...(root.translatorEnabled ? [translator.createObject()] : []),\n                    ...((root.tabButtonList.length === 0 || (!root.aiChatEnabled && !root.translatorEnabled && root.animeCloset)) ? [placeholder.createObject()] : []),\n                    ...(root.animeEnabled ? [anime.createObject()] : []),\n                ]\n            }\n        }\n\n        Component {\n            id: aiChat\n            AiChat {}\n        }\n        Component {\n            id: translator\n            Translator {}\n        }\n        Component {\n            id: anime\n            Anime {}\n        }\n        Component {\n            id: placeholder\n            Item {\n                StyledText {\n                    anchors.centerIn: parent\n                    text: root.animeCloset ? Translation.tr(\"Nothing\") : Translation.tr(\"Enjoy your empty sidebar...\")\n                    color: Appearance.colors.colSubtext\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/Translator.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.ii.sidebarLeft.translator\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\n\n/**\n * Translator widget with the `trans` commandline tool.\n */\nItem {\n    id: root\n\n    // Sizes\n    property real padding: 4\n\n    // Widgets\n    property var inputField: inputCanvas.inputTextArea\n\n    // Widget variables\n    property bool translationFor: false // Indicates if the translation is for an autocorrected text\n    property string translatedText: \"\"\n    property list<string> languages: []\n\n    // Options\n    property string targetLanguage: Config.options.language.translator.targetLanguage\n    property string sourceLanguage: Config.options.language.translator.sourceLanguage\n    property string hostLanguage: targetLanguage\n\n    // States\n    property bool showLanguageSelector: false\n    property bool languageSelectorTarget: false // true for target language, false for source language\n\n    function showLanguageSelectorDialog(isTargetLang: bool) {\n        root.languageSelectorTarget = isTargetLang;\n        root.showLanguageSelector = true\n    }\n\n    onFocusChanged: (focus) => {\n        if (focus) {\n            root.inputField.forceActiveFocus()\n        }\n    }\n\n    Timer {\n        id: translateTimer\n        interval: Config.options.sidebar.translator.delay\n        repeat: false\n        onTriggered: () => {\n            if (root.inputField.text.trim().length > 0) {\n                // console.log(\"Translating with command:\", translateProc.command);\n                translateProc.running = false;\n                translateProc.buffer = \"\"; // Clear the buffer\n                translateProc.running = true; // Restart the process\n            } else {\n                root.translatedText = \"\";\n            }\n        }\n    }\n\n    Process {\n        id: translateProc\n        command: [\"bash\", \"-c\", `trans -brief -no-bidi`\n            + ` -source '${StringUtils.shellSingleQuoteEscape(root.sourceLanguage)}'`\n            + ` -target '${StringUtils.shellSingleQuoteEscape(root.targetLanguage)}'`\n            + ` '${StringUtils.shellSingleQuoteEscape(root.inputField.text.trim())}'`]\n        property string buffer: \"\"\n        stdout: SplitParser {\n            onRead: data => {\n                translateProc.buffer += data + \"\\n\";\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            // With -brief mode, we get output with no metadata\n            root.translatedText = translateProc.buffer.trim();\n        }\n    }\n\n    Process {\n        id: getLanguagesProc\n        command: [\"trans\", \"-list-languages\", \"-no-bidi\"]\n        property list<string> bufferList: [\"auto\"]\n        running: true\n        stdout: SplitParser {\n            onRead: data => {\n                getLanguagesProc.bufferList.push(data.trim());\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            // Ensure \"auto\" is always the first language\n            let langs = getLanguagesProc.bufferList\n                .filter(lang => lang.trim().length > 0 && lang !== \"auto\")\n                .sort((a, b) => a.localeCompare(b));\n            langs.unshift(\"auto\");\n            root.languages = langs;\n            getLanguagesProc.bufferList = []; // Clear the buffer\n        }\n    }\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            margins: root.padding\n        }\n\n        StyledFlickable {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            contentHeight: contentColumn.implicitHeight\n\n            ColumnLayout {\n                id: contentColumn\n                anchors.fill: parent\n\n                LanguageSelectorButton { // Target language button\n                    id: targetLanguageButton\n                    displayText: root.targetLanguage\n                    onClicked: {\n                        root.showLanguageSelectorDialog(true);\n                    }\n                }\n\n                TextCanvas { // Content translation\n                    id: outputCanvas\n                    isInput: false\n                    placeholderText: Translation.tr(\"Translation goes here...\")\n                    property bool hasTranslation: (root.translatedText.trim().length > 0)\n                    text: hasTranslation ? root.translatedText : \"\"\n                    GroupButton {\n                        id: copyButton\n                        baseWidth: height\n                        buttonRadius: Appearance.rounding.small\n                        enabled: outputCanvas.displayedText.trim().length > 0\n                        contentItem: MaterialSymbol {\n                            anchors.centerIn: parent\n                            horizontalAlignment: Text.AlignHCenter\n                            iconSize: Appearance.font.pixelSize.larger\n                            text: \"content_copy\"\n                            color: copyButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext\n                        }\n                        onClicked: {\n                            Quickshell.clipboardText = outputCanvas.displayedText\n                        }\n                    }\n                    GroupButton {\n                        id: searchButton\n                        baseWidth: height\n                        buttonRadius: Appearance.rounding.small\n                        enabled: outputCanvas.displayedText.trim().length > 0\n                        contentItem: MaterialSymbol {\n                            anchors.centerIn: parent\n                            horizontalAlignment: Text.AlignHCenter\n                            iconSize: Appearance.font.pixelSize.larger\n                            text: \"travel_explore\"\n                            color: searchButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext\n                        }\n                        onClicked: {\n                            let url = Config.options.search.engineBaseUrl + outputCanvas.displayedText;\n                            for (let site of Config.options.search.excludedSites) {\n                                url += ` -site:${site}`;\n                            }\n                            Qt.openUrlExternally(url);\n                        }\n                    }\n                }\n\n            }    \n        }\n\n        LanguageSelectorButton { // Source language button\n            id: sourceLanguageButton\n            displayText: root.sourceLanguage\n            onClicked: {\n                root.showLanguageSelectorDialog(false);\n            }\n        }\n\n        TextCanvas { // Content input\n            id: inputCanvas\n            isInput: true\n            placeholderText: Translation.tr(\"Enter text to translate...\")\n            onInputTextChanged: {\n                translateTimer.restart();\n            }\n            GroupButton {\n                id: pasteButton\n                baseWidth: height\n                buttonRadius: Appearance.rounding.small\n                contentItem: MaterialSymbol {\n                    anchors.centerIn: parent\n                    horizontalAlignment: Text.AlignHCenter\n                    iconSize: Appearance.font.pixelSize.larger\n                    text: \"content_paste\"\n                    color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext\n                }\n                onClicked: {\n                    root.inputField.text = Quickshell.clipboardText\n                }\n            }\n            GroupButton {\n                id: deleteButton\n                baseWidth: height\n                buttonRadius: Appearance.rounding.small\n                enabled: inputCanvas.inputTextArea.text.length > 0\n                contentItem: MaterialSymbol {\n                    anchors.centerIn: parent\n                    horizontalAlignment: Text.AlignHCenter\n                    iconSize: Appearance.font.pixelSize.larger\n                    text: \"close\"\n                    color: deleteButton.enabled ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext\n                }\n                onClicked: {\n                    root.inputField.text = \"\"\n                }\n            }\n        }\n    }\n\n    Loader {\n        anchors.fill: parent\n        active: root.showLanguageSelector\n        visible: root.showLanguageSelector\n        z: 9999\n        sourceComponent: SelectionDialog {\n            id: languageSelectorDialog\n            titleText: Translation.tr(\"Select Language\")\n            items: root.languages\n            defaultChoice: root.languageSelectorTarget ? root.targetLanguage : root.sourceLanguage\n            onCanceled: () => {\n                root.showLanguageSelector = false;\n            }\n            onSelected: (result) => {\n                root.showLanguageSelector = false;\n                if (!result || result.length === 0) return; // No selection made\n\n                if (root.languageSelectorTarget) {\n                    root.targetLanguage = result;\n                    Config.options.language.translator.targetLanguage = result; // Save to config\n                } else {\n                    root.sourceLanguage = result;\n                    Config.options.language.translator.sourceLanguage = result; // Save to config\n                }\n\n                translateTimer.restart(); // Restart translation after language change\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AiMessage.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nRectangle {\n    id: root\n    property int messageIndex\n    property var messageData\n    property var messageInputField\n\n    property real messagePadding: 7\n    property real contentSpacing: 3\n\n    property bool enableMouseSelection: false\n    property bool renderMarkdown: true\n    property bool editing: false\n\n    property list<var> messageBlocks: StringUtils.splitMarkdownBlocks(root.messageData?.content)\n\n    anchors.left: parent?.left\n    anchors.right: parent?.right\n    implicitHeight: columnLayout.implicitHeight + root.messagePadding * 2\n\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colLayer1\n\n    function saveMessage() {\n        if (!root.editing) return;\n        // Get all Loader children (each represents a segment)\n        const segments = messageContentColumnLayout.children\n            .map(child => child.segment)\n            .filter(segment => (segment));\n\n        // Reconstruct markdown\n        const newContent = segments.map(segment => {\n            if (segment.type === \"code\") {\n                const lang = segment.lang ? segment.lang : \"\";\n                // Remove trailing newlines\n                const code = segment.content.replace(/\\n+$/, \"\");\n                return \"```\" + lang + \"\\n\" + code + \"\\n```\";\n            } else {\n                return segment.content;\n            }\n        }).join(\"\");\n\n        root.editing = false\n        root.messageData.content = newContent;\n    }\n\n    Keys.onPressed: (event) => {\n        if ( // Prevent de-select\n            event.key === Qt.Key_Control || \n            event.key == Qt.Key_Shift || \n            event.key == Qt.Key_Alt || \n            event.key == Qt.Key_Meta\n        ) {\n            event.accepted = true\n        }\n        // Ctrl + S to save\n        if ((event.key === Qt.Key_S) && event.modifiers == Qt.ControlModifier) {\n            root.saveMessage();\n            event.accepted = true;\n        }\n    }\n\n    ColumnLayout { // Main layout of the whole thing\n        id: columnLayout\n\n        anchors.left: parent.left\n        anchors.right: parent.right\n        anchors.top: parent.top\n        anchors.margins: messagePadding\n        spacing: root.contentSpacing\n\n        Rectangle {\n            Layout.fillWidth: true\n            implicitWidth: headerRowLayout.implicitWidth + 4 * 2\n            implicitHeight: headerRowLayout.implicitHeight + 4 * 2\n            color: Appearance.colors.colSecondaryContainer\n            radius: Appearance.rounding.small\n        \n            RowLayout { // Header\n                id: headerRowLayout\n                anchors {\n                    fill: parent\n                    margins: 4\n                }\n                spacing: 18\n\n                Item { // Name\n                    id: nameWrapper\n                    implicitHeight: Math.max(nameRowLayout.implicitHeight + 5 * 2, 30)\n                    Layout.fillWidth: true\n                    Layout.alignment: Qt.AlignVCenter\n\n                    RowLayout {\n                        id: nameRowLayout\n                        anchors.verticalCenter: parent.verticalCenter\n                        anchors.left: parent.left\n                        anchors.right: parent.right\n                        anchors.leftMargin: 10\n                        anchors.rightMargin: 10\n                        spacing: 12\n\n                        Item {\n                            Layout.alignment: Qt.AlignVCenter\n                            Layout.fillHeight: true\n                            implicitWidth: messageData?.role == 'assistant' ? modelIcon.width : roleIcon.implicitWidth\n                            implicitHeight: messageData?.role == 'assistant' ? modelIcon.height : roleIcon.implicitHeight\n\n                            CustomIcon {\n                                id: modelIcon\n                                anchors.centerIn: parent\n                                visible: messageData?.role == 'assistant' && Ai.models[messageData?.model].icon\n                                width: Appearance.font.pixelSize.large\n                                height: Appearance.font.pixelSize.large\n                                source: messageData?.role == 'assistant' ? Ai.models[messageData?.model].icon :\n                                    messageData?.role == 'user' ? 'linux-symbolic' : 'desktop-symbolic'\n\n                                colorize: true\n                                color: Appearance.m3colors.m3onSecondaryContainer\n                            }\n\n                            MaterialSymbol {\n                                id: roleIcon\n                                anchors.centerIn: parent\n                                visible: !modelIcon.visible\n                                iconSize: Appearance.font.pixelSize.larger\n                                color: Appearance.m3colors.m3onSecondaryContainer\n                                text: messageData?.role == 'user' ? 'person' : \n                                    messageData?.role == 'interface' ? 'settings' : \n                                    messageData?.role == 'assistant' ? 'neurology' : \n                                    'computer'\n                            }\n                        }\n\n                        StyledText {\n                            id: providerName\n                            Layout.alignment: Qt.AlignVCenter\n                            Layout.fillWidth: true\n                            elide: Text.ElideRight\n                            font.pixelSize: Appearance.font.pixelSize.normal\n                            color: Appearance.m3colors.m3onSecondaryContainer\n                            text: messageData?.role == 'assistant' ? Ai.models[messageData?.model].name :\n                                (messageData?.role == 'user' && SystemInfo.username) ? SystemInfo.username :\n                                Translation.tr(\"Interface\")\n                        }\n                    }\n                }\n\n                Button { // Not visible to model\n                    id: modelVisibilityIndicator\n                    visible: messageData?.role == 'interface'\n                    implicitWidth: 16\n                    implicitHeight: 30\n                    Layout.alignment: Qt.AlignVCenter\n\n                    background: Item\n\n                    MaterialSymbol {\n                        id: notVisibleToModelText\n                        anchors.centerIn: parent\n                        iconSize: Appearance.font.pixelSize.small\n                        color: Appearance.colors.colSubtext\n                        text: \"visibility_off\"\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Not visible to model\")\n                    }\n                }\n\n                ButtonGroup {\n                    spacing: 5\n\n                    AiMessageControlButton {\n                        id: regenButton\n                        buttonIcon: \"refresh\"\n                        visible: messageData?.role === 'assistant'\n\n                        onClicked: {\n                            Ai.regenerate(root.messageIndex)\n                        }\n                        \n                        StyledToolTip {\n                            text: Translation.tr(\"Regenerate\")\n                        }\n                    }\n\n                    AiMessageControlButton {\n                        id: copyButton\n                        buttonIcon: activated ? \"inventory\" : \"content_copy\"\n\n                        onClicked: {\n                            Quickshell.clipboardText = root.messageData?.content\n                            copyButton.activated = true\n                            copyIconTimer.restart()\n                        }\n\n                        Timer {\n                            id: copyIconTimer\n                            interval: 1500\n                            repeat: false\n                            onTriggered: {\n                                copyButton.activated = false\n                            }\n                        }\n                        \n                        StyledToolTip {\n                            text: Translation.tr(\"Copy\")\n                        }\n                    }\n                    AiMessageControlButton {\n                        id: editButton\n                        activated: root.editing\n                        enabled: root.messageData?.done ?? false\n                        buttonIcon: \"edit\"\n                        onClicked: {\n                            root.editing = !root.editing\n                            if (!root.editing) { // Save changes\n                                root.saveMessage()\n                            }\n                        }\n                        StyledToolTip {\n                            text: root.editing ? Translation.tr(\"Save\") : Translation.tr(\"Edit\")\n                        }\n                    }\n                    AiMessageControlButton {\n                        id: toggleMarkdownButton\n                        activated: !root.renderMarkdown\n                        buttonIcon: \"code\"\n                        onClicked: {\n                            root.renderMarkdown = !root.renderMarkdown\n                        }\n                        StyledToolTip {\n                            text: Translation.tr(\"View Markdown source\")\n                        }\n                    }\n                    AiMessageControlButton {\n                        id: deleteButton\n                        buttonIcon: \"close\"\n                        onClicked: {\n                            Ai.removeMessage(root.messageIndex)\n                        }\n                        StyledToolTip {\n                            text: Translation.tr(\"Delete\")\n                        }\n                    }\n                }\n            }\n        }\n\n        Loader {\n            Layout.fillWidth: true\n            active: root.messageData?.localFilePath && root.messageData?.localFilePath.length > 0\n            sourceComponent: AttachedFileIndicator {\n                filePath: root.messageData?.localFilePath\n                canRemove: false\n            }\n        }\n\n        ColumnLayout { // Message content\n            id: messageContentColumnLayout\n            spacing: 0\n\n            Item {\n                Layout.fillWidth: true\n                implicitHeight: loadingIndicatorLoader.shown ? loadingIndicatorLoader.implicitHeight : 0\n                implicitWidth: loadingIndicatorLoader.implicitWidth\n                visible: implicitHeight > 0\n\n                Behavior on implicitHeight {\n                    animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n                }\n                FadeLoader {\n                    id: loadingIndicatorLoader\n                    anchors.centerIn: parent\n                    shown: (root.messageBlocks.length < 1) && (!root.messageData.done)\n                    sourceComponent: MaterialLoadingIndicator {\n                        loading: true\n                    }\n                }\n            }\n            Repeater {\n                model: ScriptModel {\n                    values: root.messageBlocks\n                }\n                delegate: DelegateChooser {\n                    id: messageDelegate\n                    role: \"type\"\n\n                    DelegateChoice { roleValue: \"code\"; MessageCodeBlock {\n                        editing: root.editing\n                        renderMarkdown: root.renderMarkdown\n                        enableMouseSelection: root.enableMouseSelection\n                        segmentContent: modelData.content\n                        segmentLang: modelData.lang\n                        messageData: root.messageData\n                    } }\n                    DelegateChoice { roleValue: \"think\"; MessageThinkBlock {\n                        editing: root.editing\n                        renderMarkdown: root.renderMarkdown\n                        enableMouseSelection: root.enableMouseSelection\n                        segmentContent: modelData.content\n                        messageData: root.messageData\n                        done: root.messageData?.done ?? false\n                        completed: modelData.completed ?? false\n                    } }\n                    DelegateChoice { roleValue: \"text\"; MessageTextBlock {\n                        editing: root.editing\n                        renderMarkdown: root.renderMarkdown\n                        enableMouseSelection: root.enableMouseSelection\n                        segmentContent: modelData.content\n                        messageData: root.messageData\n                        done: root.messageData?.done ?? false\n                        forceDisableChunkSplitting: root.messageData?.content.includes(\"```\") ?? true\n                    } }\n                }\n            }\n        }\n\n        Flow { // Annotations\n            visible: root.messageData?.annotationSources?.length > 0\n            spacing: 5\n            Layout.fillWidth: true\n            Layout.alignment: Qt.AlignLeft\n\n            Repeater {\n                model: ScriptModel {\n                    values: root.messageData?.annotationSources || []\n                }\n                delegate: AnnotationSourceButton {\n                    required property var modelData\n                    displayText: modelData.text\n                    url: modelData.url\n                }\n            }\n        }\n\n        Flow { // Search queries\n            visible: root.messageData?.searchQueries?.length > 0\n            spacing: 5\n            Layout.fillWidth: true\n            Layout.alignment: Qt.AlignLeft\n\n            Repeater {\n                model: ScriptModel {\n                    values: root.messageData?.searchQueries || []\n                }\n                delegate: SearchQueryButton {\n                    required property var modelData\n                    query: modelData\n                }\n            }\n        }\n\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AiMessageControlButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\n\nGroupButton {\n    id: button\n    property string buttonIcon\n    property bool activated: false\n    toggled: activated\n    baseWidth: height\n    colBackgroundHover: Appearance.colors.colSecondaryContainerHover\n    colBackgroundActive: Appearance.colors.colSecondaryContainerActive\n\n    contentItem: MaterialSymbol {\n        horizontalAlignment: Text.AlignHCenter\n        iconSize: Appearance.font.pixelSize.larger\n        text: buttonIcon\n        color: button.activated ? Appearance.m3colors.m3onPrimary :\n            button.enabled ? Appearance.m3colors.m3onSurface :\n            Appearance.colors.colOnLayer1Inactive\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AnnotationSourceButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\n\nimport qs\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nRippleButton {\n    id: root\n    property string displayText\n    property string url\n\n    property real faviconSize: 20\n    implicitHeight: 30\n    leftPadding: (implicitHeight - faviconSize) / 2\n    rightPadding: 10\n    buttonRadius: Appearance.rounding.full\n    colBackground: Appearance.colors.colSurfaceContainerHighest\n    colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover\n    colRipple: Appearance.colors.colSurfaceContainerHighestActive\n\n    PointingHandInteraction {}\n    onClicked: {\n        if (url) {\n            Qt.openUrlExternally(url)\n            GlobalStates.sidebarLeftOpen = false\n        }\n    }\n\n    contentItem: Item {\n        anchors.centerIn: parent\n        implicitWidth: rowLayout.implicitWidth\n        implicitHeight: rowLayout.implicitHeight\n        RowLayout {\n            id: rowLayout\n            anchors.fill: parent\n            spacing: 5\n            Favicon {\n                url: root.url\n                size: root.faviconSize\n                displayText: root.displayText\n            }\n            StyledText {\n                id: text\n                horizontalAlignment: Text.AlignHCenter\n                text: displayText\n                color: Appearance.m3colors.m3onSurface\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/AttachedFileIndicator.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell.Io\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\n\nRectangle {\n    id: root\n\n    signal remove()\n    property bool canRemove: true\n    property string filePath: \"\"\n    property string mimeType: \"\"\n    property real maxHeight: 200\n    property real imageWidth: -1\n    property real imageHeight: -1\n    property real scale: Math.min(root.maxHeight / imageHeight, root.width / imageWidth)\n    onFilePathChanged: refresh()\n    visible: filePath !== \"\"\n\n    function refresh() {\n        root.mimeType = \"\";\n        root.imageWidth = -1;\n        root.imageHeight = -1;\n        fileTypeProc.exec([\"file\", \"-b\", \"--mime-type\", filePath]);\n    }\n\n    Process {\n        id: fileTypeProc\n        command: [\"file\", \"-b\", \"--mime-type\", filePath]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                root.mimeType = this.text;\n                if (root.mimeType.startsWith(\"image/\"))\n                    imageSizeProc.exec([\"identify\", \"-format\", \"%wx%h\", filePath]);\n            }\n        }\n    }\n\n    Process {\n        id: imageSizeProc\n        command: [\"identify\", \"-format\", \"%wx%h\", filePath]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                const dimensions = this.text.split(\"x\");\n                root.imageWidth = parseInt(dimensions[0]);\n                root.imageHeight = parseInt(dimensions[1]);\n            }\n        }\n    }\n\n    // Styles/widgets\n    property real horizontalPadding: 10\n    property real verticalPadding: 10\n    radius: Appearance.rounding.small - anchors.margins\n    color: Appearance.colors.colLayer2\n    implicitHeight: visible ? (contentItem.implicitHeight + verticalPadding * 2) : 0\n\n    ColumnLayout {\n        id: contentItem\n        anchors {\n            fill: parent\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n            topMargin: root.verticalPadding\n            bottomMargin: root.verticalPadding\n        }\n\n        RowLayout {\n            MaterialSymbol {\n                Layout.alignment: Qt.AlignTop\n                text: {\n                    if (root.mimeType.startsWith(\"image/\"))\n                        return \"image\";\n                    if (root.mimeType.startsWith(\"audio/\"))\n                        return \"music_note\";\n                    if (root.mimeType.startsWith(\"video/\"))\n                        return \"movie\";\n                    if (root.mimeType === \"application/pdf\")\n                        return \"picture_as_pdf\";\n                    if (root.mimeType.startsWith(\"text/\"))\n                        return \"description\";\n                    return \"file_present\";\n                }\n                iconSize: Appearance.font.pixelSize.hugeass\n            }\n\n            StyledText {\n                Layout.fillWidth: true\n                Layout.topMargin: 4\n                text: root.filePath\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                font.family: Appearance.font.family.monospace\n                wrapMode: Text.Wrap\n            }\n\n            RippleButton {\n                visible: root.canRemove\n                Layout.alignment: Qt.AlignTop\n                buttonRadius: Appearance.rounding.full\n                colBackground: Appearance.colors.colLayer2\n                implicitHeight: 28\n                implicitWidth: 28\n                contentItem: MaterialSymbol {\n                    anchors.centerIn: parent\n                    text: \"close\"\n                    horizontalAlignment: Text.AlignHCenter\n                    iconSize: Appearance.font.pixelSize.larger\n                    color: Appearance.colors.colOnSurfaceVariant\n                }\n\n                onClicked: root.remove()\n            }\n        }\n\n        Loader {\n            id: imagePreviewLoader\n            visible: (root.imageWidth != -1) && (root.imageHeight != -1)\n            Layout.alignment: Qt.AlignHCenter\n            sourceComponent: Item {\n                implicitHeight: root.imageHeight * root.scale\n                implicitWidth: imagePreview.implicitWidth\n                StyledImage {\n                    id: imagePreview\n                    anchors.fill: parent\n                    source: Qt.resolvedUrl(root.filePath)\n                    fillMode: Image.PreserveAspectFit\n                    antialiasing: true\n                    width: root.imageWidth * root.scale\n                    height: root.imageHeight * root.scale\n                    sourceSize.width: root.imageWidth * root.scale\n                    sourceSize.height: root.imageHeight * root.scale\n\n                    layer.enabled: true\n                    layer.effect: OpacityMask {\n                        maskSource: Rectangle {\n                            width: imagePreview.width\n                            height: imagePreview.height\n                            radius: Appearance.rounding.normal\n                        }\n                    }\n\n                    Rectangle {\n                        anchors.fill: parent\n                        color: \"transparent\"\n                        border.width: 1\n                        border.color: Appearance.colors.colOutlineVariant\n                        radius: Appearance.rounding.normal\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/MessageCodeBlock.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport org.kde.syntaxhighlighting\n\nColumnLayout {\n    id: root\n    // These are needed on the parent loader\n    property bool editing: false\n    property bool renderMarkdown: true\n    property bool enableMouseSelection: false\n    property var segmentContent: ({})\n    property var segmentLang: \"txt\"\n    property var messageData: {}\n    property bool isCommandRequest: segmentLang === \"command\"\n    property var displayLang: (isCommandRequest ? \"bash\" : segmentLang)\n\n    property real codeBlockBackgroundRounding: Appearance.rounding.small\n    property real codeBlockHeaderPadding: 3\n    property real codeBlockComponentSpacing: 2\n\n    spacing: codeBlockComponentSpacing\n\n    Rectangle { // Code background\n        Layout.fillWidth: true\n        topLeftRadius: codeBlockBackgroundRounding\n        topRightRadius: codeBlockBackgroundRounding\n        bottomLeftRadius: Appearance.rounding.unsharpen\n        bottomRightRadius: Appearance.rounding.unsharpen\n        color: Appearance.colors.colSurfaceContainerHighest\n        implicitHeight: codeBlockTitleBarRowLayout.implicitHeight + codeBlockHeaderPadding * 2\n\n        RowLayout { // Language and buttons\n            id: codeBlockTitleBarRowLayout\n            anchors.verticalCenter: parent.verticalCenter\n            anchors.left: parent.left\n            anchors.right: parent.right\n            anchors.leftMargin: codeBlockHeaderPadding\n            anchors.rightMargin: codeBlockHeaderPadding\n            spacing: 5\n\n            StyledText {\n                id: codeBlockLanguage\n                Layout.alignment: Qt.AlignLeft\n                Layout.fillWidth: false\n                Layout.topMargin: 7\n                Layout.bottomMargin: 7\n                Layout.leftMargin: 10\n                font.pixelSize: Appearance.font.pixelSize.small\n                font.weight: Font.DemiBold\n                color: Appearance.colors.colOnLayer2\n                text: root.displayLang ? Repository.definitionForName(root.displayLang).name : \"plain\"\n            }\n\n            Item { Layout.fillWidth: true }\n\n            ButtonGroup {\n                AiMessageControlButton {\n                    id: copyCodeButton\n                    buttonIcon: activated ? \"inventory\" : \"content_copy\"\n\n                    onClicked: {\n                        Quickshell.clipboardText = segmentContent\n                        copyCodeButton.activated = true\n                        copyIconTimer.restart()\n                    }\n\n                    Timer {\n                        id: copyIconTimer\n                        interval: 1500\n                        repeat: false\n                        onTriggered: {\n                            copyCodeButton.activated = false\n                        }\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Copy code\")\n                    }\n                }\n                AiMessageControlButton {\n                    id: saveCodeButton\n                    buttonIcon: activated ? \"check\" : \"save\"\n\n                    onClicked: {\n                        const downloadPath = FileUtils.trimFileProtocol(Directories.downloads)\n                        Quickshell.execDetached([\"bash\", \"-c\", \n                            `echo '${StringUtils.shellSingleQuoteEscape(segmentContent)}' > '${downloadPath}/code.${segmentLang || \"txt\"}'`\n                        ])\n                        Quickshell.execDetached([\"notify-send\", \n                            Translation.tr(\"Code saved to file\"), \n                            Translation.tr(\"Saved to %1\").arg(`${downloadPath}/code.${segmentLang || \"txt\"}`),\n                            \"-a\", \"Shell\"\n                        ])\n                        saveCodeButton.activated = true\n                        saveIconTimer.restart()\n                    }\n\n                    Timer {\n                        id: saveIconTimer\n                        interval: 1500\n                        repeat: false\n                        onTriggered: {\n                            saveCodeButton.activated = false\n                        }\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Save to Downloads\")\n                    }\n                }\n            }\n        }\n    }\n\n    RowLayout { // Line numbers and code\n        spacing: codeBlockComponentSpacing\n\n        Rectangle { // Line numbers\n            implicitWidth: 40\n            implicitHeight: lineNumberColumnLayout.implicitHeight\n            Layout.fillHeight: true\n            Layout.fillWidth: false\n            topLeftRadius: Appearance.rounding.unsharpen\n            bottomLeftRadius: codeBlockBackgroundRounding\n            topRightRadius: Appearance.rounding.unsharpen\n            bottomRightRadius: Appearance.rounding.unsharpen\n            color: Appearance.colors.colLayer2\n\n            ColumnLayout {\n                id: lineNumberColumnLayout\n                anchors {\n                    left: parent.left\n                    right: parent.right\n                    rightMargin: 5\n                    top: parent.top\n                    topMargin: 6\n                }\n                spacing: 0\n                \n                Repeater {\n                    model: codeTextArea.text.split(\"\\n\").length\n                    Text {\n                        required property int index\n                        Layout.fillWidth: true\n                        Layout.alignment: Qt.AlignRight\n                        font.family: Appearance.font.family.monospace\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        color: Appearance.colors.colSubtext\n                        horizontalAlignment: Text.AlignRight\n                        text: index + 1\n                    }\n                }\n            }\n        }\n\n        Rectangle { // Code background\n            Layout.fillWidth: true\n            topLeftRadius: Appearance.rounding.unsharpen\n            bottomLeftRadius: Appearance.rounding.unsharpen\n            topRightRadius: Appearance.rounding.unsharpen\n            bottomRightRadius: codeBlockBackgroundRounding\n            color: Appearance.colors.colLayer2\n            implicitHeight: codeColumnLayout.implicitHeight\n\n            ColumnLayout {\n                id: codeColumnLayout\n                anchors.fill: parent\n                spacing: 0\n                ScrollView {\n                    id: codeScrollView\n                    Layout.fillWidth: true\n                    // Layout.fillHeight: true\n                    implicitWidth: parent.width\n                    implicitHeight: codeTextArea.implicitHeight + 1\n                    contentWidth: codeTextArea.width - 1\n                    // contentHeight: codeTextArea.contentHeight\n                    clip: true\n                    ScrollBar.vertical.policy: ScrollBar.AlwaysOff\n                    \n                    ScrollBar.horizontal: ScrollBar {\n                        anchors.bottom: parent.bottom\n                        anchors.left: parent.left\n                        anchors.right: parent.right\n                        padding: 5\n                        policy: ScrollBar.AsNeeded\n                        opacity: visualSize == 1 ? 0 : 1\n                        visible: opacity > 0\n\n                        Behavior on opacity {\n                            NumberAnimation {\n                                duration: Appearance.animation.elementMoveFast.duration\n                                easing.type: Appearance.animation.elementMoveFast.type\n                                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                            }\n                        }\n                        \n                        contentItem: Rectangle {\n                            implicitHeight: 6\n                            radius: Appearance.rounding.small\n                            color: Appearance.colors.colLayer2Active\n                        }\n                    }\n\n                    TextArea { // Code\n                        id: codeTextArea\n                        Layout.fillWidth: true\n                        readOnly: !editing\n                        selectByMouse: enableMouseSelection || editing\n                        renderType: Text.NativeRendering\n                        font.family: Appearance.font.family.monospace\n                        font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n                        selectionColor: Appearance.colors.colSecondaryContainer\n                        // wrapMode: TextEdit.Wrap\n                        color: messageData.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1\n\n                        text: segmentContent\n                        onTextChanged: {\n                            segmentContent = text\n                        }\n\n                        Keys.onPressed: (event) => {\n                            if (event.key === Qt.Key_Tab) {\n                                // Insert 4 spaces at cursor\n                                const cursor = codeTextArea.cursorPosition;\n                                codeTextArea.insert(cursor, \"    \");\n                                codeTextArea.cursorPosition = cursor + 4;\n                                event.accepted = true;\n                            } else if ((event.key === Qt.Key_C) && event.modifiers == Qt.ControlModifier) {\n                                codeTextArea.copy();\n                                event.accepted = true;\n                            }\n                        }\n\n                        SyntaxHighlighter {\n                            id: highlighter\n                            textEdit: codeTextArea\n                            repository: Repository\n                            definition: Repository.definitionForName(root.displayLang || \"plaintext\")\n                            theme: Appearance.syntaxHighlightingTheme\n                        }\n                    }\n                }\n                Loader {\n                    active: root.isCommandRequest && root.messageData.functionPending\n                    visible: active\n                    Layout.fillWidth: true\n                    Layout.margins: 6\n                    Layout.topMargin: 0\n                    sourceComponent: RowLayout {\n                        Item { Layout.fillWidth: true }\n                        ButtonGroup {\n                            GroupButton {\n                                contentItem: StyledText {\n                                    text: Translation.tr(\"Reject\")\n                                    font.pixelSize: Appearance.font.pixelSize.small\n                                    color: Appearance.colors.colOnLayer2\n                                }\n                                onClicked: Ai.rejectCommand(root.messageData)\n                            }\n                            GroupButton {\n                                toggled: true\n                                contentItem: StyledText {\n                                    text: Translation.tr(\"Approve\")\n                                    font.pixelSize: Appearance.font.pixelSize.small\n                                    color: Appearance.colors.colOnPrimary\n                                }\n                                onClicked: Ai.approveCommand(root.messageData)\n                            }\n                        }\n                    }\n                }\n            }\n\n            // MouseArea to block scrolling\n            // MouseArea {\n            //     id: codeBlockMouseArea\n            //     anchors.fill: parent\n            //     acceptedButtons: editing ? Qt.NoButton : Qt.LeftButton\n            //     cursorShape: (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor\n            //     onWheel: (event) => {\n            //         event.accepted = false\n            //     }\n            // }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/MessageTextBlock.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\n\nColumnLayout {\n    id: root\n    // These are needed on the parent loader\n    property bool editing: false\n    property bool renderMarkdown: true\n    property bool enableMouseSelection: false\n    property var segmentContent: ({})\n    property var messageData: {}\n    property bool done: true\n    property bool forceDisableChunkSplitting: false\n\n    property list<string> renderedLatexHashes: []\n    property string renderedSegmentContent: \"\"\n    property string shownText: \"\"\n    property bool fadeChunkSplitting: !forceDisableChunkSplitting && !editing && !/\\n\\|/.test(shownText) && Config.options.sidebar.ai.textFadeIn\n\n    Layout.fillWidth: true\n\n    Timer {\n        id: renderTimer\n        interval: 1000\n        repeat: false\n        onTriggered: {\n            renderLatex()\n            for (const hash of renderedLatexHashes) {\n                handleRenderedLatex(hash, true);\n            }\n        }\n    }\n\n    function renderLatex() {\n        // Regex for $...$, $$...$$, \\[...\\]\n        // Note: This is a simple approach and may need refinement for edge cases\n        let regex = /(\\$\\$([\\s\\S]+?)\\$\\$)|(\\$([^\\$]+?)\\$)|(\\\\\\[((?:.|\\n)+?)\\\\\\])|(\\\\\\(([\\s\\S]+?)\\\\\\))/g;\n        let match;\n        while ((match = regex.exec(segmentContent)) !== null) {\n            let expression = match[1] || match[2] || match[3] || match[4] || match[5] || match[6] || match[7] || match[8];\n            if (expression) {\n                Qt.callLater(() => {\n                    const [renderHash, isNew] = LatexRenderer.requestRender(expression.trim());\n                    if (!renderedLatexHashes.includes(renderHash)) {\n                        renderedLatexHashes.push(renderHash);\n                    }\n                });\n            }\n        }\n    }\n\n    function handleRenderedLatex(hash, force = false) {\n        if (renderedLatexHashes.includes(hash) || force) {\n            const imagePath = LatexRenderer.renderedImagePaths[hash];\n            const markdownImage = `![latex](${imagePath})`;\n\n            const expression = LatexRenderer.processedExpressions[hash];\n            renderedSegmentContent = renderedSegmentContent.replace(expression, markdownImage);\n        }\n    }\n\n    onDoneChanged: {\n        renderTimer.restart();\n    }\n    onEditingChanged: {\n        if (!editing) {\n            renderLatex()\n        } else {\n            // console.log(\"Editing mode enabled\", segmentContent)\n            root.shownText = segmentContent\n        }\n    }\n\n    onSegmentContentChanged: {\n        // console.log(\"Segment content changed: \" + segmentContent);\n        renderedSegmentContent = segmentContent;\n        if (!root.editing && segmentContent) {\n            root.renderLatex();\n        }\n    }\n\n    onRenderedSegmentContentChanged: {\n        // console.log(\"Rendered segment content changed: \" + renderedSegmentContent);\n        if (renderedSegmentContent) {\n            root.shownText = renderedSegmentContent;\n        }\n    }\n\n    // When something finishes rendering\n    // 1. Check if the hash is in the list\n    // 2. If it is, replace the expression with the image path\n    Connections {\n        target: LatexRenderer\n        function onRenderFinished(hash, imagePath) {\n            const expression = LatexRenderer.processedExpressions[hash];\n            // console.log(\"Render finished: \" + hash + \" \" + expression);\n            handleRenderedLatex(hash);\n        }\n    }\n\n    spacing: 0\n    Repeater {\n        id: textLinesRepeater\n        property list<real> textLineOpacities: []\n        model: ScriptModel {\n            // Split by either double newlines or single newlines in a list\n            values: root.fadeChunkSplitting ? root.shownText.split(/\\n\\n(?= {0,2})|\\n(?= {0,2}[-\\*])/g).filter(line => line.trim() !== \"\") : [root.shownText]\n            onValuesChanged: {\n                while (textLinesRepeater.textLineOpacities.length < values.length) {\n                    textLinesRepeater.textLineOpacities.push(root.messageData.done ? 1 : 0);\n                }\n            }\n        }\n        delegate: TextArea {\n            id: textArea\n            required property int index\n            required property string modelData\n\n            // Fade in animation\n            visible: opacity > 0\n            opacity: fadeChunkSplitting ? (textLinesRepeater.textLineOpacities[index] ?? (root.messageData.done ? 1 : 0)) : 1\n            Connections {\n                target: root.messageData\n                function onDoneChanged() {\n                    if (root.messageData.done) {\n                        textLinesRepeater.textLineOpacities[textArea.index] = 1\n                    }\n                }\n            }\n            Connections {\n                target: textLinesRepeater.model\n                function onValuesChanged() {\n                    if (textLinesRepeater.model.values.length > textArea.index + 1) {\n                        textLinesRepeater.textLineOpacities[textArea.index] = 1\n                    }\n                }\n            }\n            Behavior on opacity {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n\n            Layout.fillWidth: true\n            readOnly: !editing\n            selectByMouse: enableMouseSelection || editing\n            renderType: Text.NativeRendering\n            font.family: Appearance.font.family.reading\n            font.hintingPreference: Font.PreferNoHinting // Prevent weird bold text\n            font.pixelSize: Appearance.font.pixelSize.small\n            selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n            selectionColor: Appearance.colors.colSecondaryContainer\n            wrapMode: TextEdit.Wrap\n            color: root.messageData?.thinking ? Appearance.colors.colSubtext : Appearance.colors.colOnLayer1\n            textFormat: renderMarkdown ? TextEdit.MarkdownText : TextEdit.PlainText\n            text: modelData\n\n            onTextChanged: {\n                if (!root.editing) return\n                segmentContent = text\n            }\n\n            onLinkActivated: (link) => {\n                Qt.openUrlExternally(link)\n                GlobalStates.sidebarLeftOpen = false\n            }\n\n            MouseArea { // Pointing hand for links\n                anchors.fill: parent\n                acceptedButtons: Qt.NoButton // Only for hover\n                hoverEnabled: true\n                cursorShape: parent.hoveredLink !== \"\" ? Qt.PointingHandCursor : \n                    (enableMouseSelection || editing) ? Qt.IBeamCursor : Qt.ArrowCursor\n            }\n\n            // Rectangle {\n            //     anchors.fill: parent\n            //     color: \"#22786378\"\n            //     border.width: 1\n            //     border.color: \"#7E7E7E\"\n            // }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/MessageThinkBlock.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\n\nItem {\n    id: root\n    // These are needed on the parent loader\n    property bool editing: false\n    property bool renderMarkdown: true\n    property bool enableMouseSelection: false\n    property var segmentContent: ({})\n    property var messageData: {}\n    property bool done: true\n    property bool completed: false\n\n    property real thinkBlockBackgroundRounding: Appearance.rounding.small\n    property real thinkBlockHeaderPaddingVertical: 3\n    property real thinkBlockHeaderPaddingHorizontal: 10\n    property real thinkBlockComponentSpacing: 2\n\n    property var collapseAnimation: messageTextBlock.implicitHeight > 40 ? Appearance.animation.elementMoveEnter : Appearance.animation.elementMoveFast\n    property bool collapsed: true /* should be root.completed but its kinda buggy rn so nope */\n\n    Layout.fillWidth: true\n    implicitHeight: collapsed ? header.implicitHeight : columnLayout.implicitHeight\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Rectangle {\n            width: root.width\n            height: root.height\n            radius: thinkBlockBackgroundRounding\n        }\n    }\n\n    Behavior on implicitHeight {\n        enabled: root.completed ?? false\n        NumberAnimation {\n            duration: collapseAnimation.duration\n            easing.type: collapseAnimation.type\n            easing.bezierCurve: collapseAnimation.bezierCurve\n        }\n    }\n\n    ColumnLayout {\n        id: columnLayout\n        anchors.left: parent.left\n        anchors.right: parent.right\n        anchors.top: parent.top\n        spacing: 0\n\n        Rectangle { // Header background\n            id: header\n            color: Appearance.colors.colSurfaceContainerHighest\n            Layout.fillWidth: true\n            implicitHeight: thinkBlockTitleBarRowLayout.implicitHeight + thinkBlockHeaderPaddingVertical * 2\n\n            MouseArea { // Click to reveal\n                id: headerMouseArea\n                enabled: root.completed\n                anchors.fill: parent\n                cursorShape: Qt.PointingHandCursor\n                hoverEnabled: true\n                onClicked: {\n                    root.collapsed = !root.collapsed\n                }\n            }\n\n            RowLayout { // Header content\n                id: thinkBlockTitleBarRowLayout\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.left: parent.left\n                anchors.right: parent.right\n                anchors.leftMargin: thinkBlockHeaderPaddingHorizontal\n                anchors.rightMargin: thinkBlockHeaderPaddingHorizontal\n                spacing: 10\n\n                MaterialSymbol {\n                    Layout.fillWidth: false\n                    Layout.topMargin: 7\n                    Layout.bottomMargin: 7\n                    Layout.leftMargin: 3\n                    text: \"linked_services\"\n                }\n                StyledText {\n                    id: thinkBlockLanguage\n                    Layout.fillWidth: false\n                    Layout.alignment: Qt.AlignLeft\n                    text: root.completed ? Translation.tr(\"Thought\") : (Translation.tr(\"Thinking\") + \".\".repeat(Math.random() * 4))\n                }\n                Item { Layout.fillWidth: true }\n                RippleButton { // Expand button\n                    id: expandButton\n                    visible: root.completed\n                    implicitWidth: 22\n                    implicitHeight: 22\n                    colBackground: headerMouseArea.containsMouse ? Appearance.colors.colLayer2Hover\n                        : ColorUtils.transparentize(Appearance.colors.colLayer2, 1)\n                    colBackgroundHover: Appearance.colors.colLayer2Hover\n                    colRipple: Appearance.colors.colLayer2Active\n\n                    onClicked: { root.collapsed = !root.collapsed }\n                    \n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        text: \"keyboard_arrow_down\"\n                        horizontalAlignment: Text.AlignHCenter\n                        verticalAlignment: Text.AlignVCenter\n                        iconSize: Appearance.font.pixelSize.normal\n                        color: Appearance.colors.colOnLayer2\n                        rotation: root.collapsed ? 0 : 180\n                        Behavior on rotation {\n                            NumberAnimation {\n                                duration: Appearance.animation.elementMoveFast.duration\n                                easing.type: Appearance.animation.elementMoveFast.type\n                                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                            }\n                        }\n                    }\n\n                }\n                \n            }\n\n        }\n\n        Item {\n            id: content\n            Layout.fillWidth: true\n            implicitHeight: collapsed ? 0 : contentBackground.implicitHeight + thinkBlockComponentSpacing\n            clip: true\n\n            Behavior on implicitHeight {\n                enabled: root.completed ?? false\n                NumberAnimation {\n                    duration: collapseAnimation.duration\n                    easing.type: collapseAnimation.type\n                    easing.bezierCurve: collapseAnimation.bezierCurve\n                }\n            }\n\n            Rectangle {\n                id: contentBackground\n                anchors.left: parent.left\n                anchors.right: parent.right\n                anchors.bottom: parent.bottom\n                implicitHeight: messageTextBlock.implicitHeight\n                color: Appearance.colors.colLayer2\n\n                // Load data for the message at the correct scope\n                property bool editing: root.editing\n                property bool renderMarkdown: root.renderMarkdown\n                property bool enableMouseSelection: root.enableMouseSelection\n                property var messageData: root.messageData\n                property bool done: root.done\n\n                MessageTextBlock {\n                    id: messageTextBlock\n                    anchors.left: parent.left\n                    anchors.right: parent.right\n                    anchors.bottom: parent.bottom\n                    segmentContent: root.segmentContent\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/aiChat/SearchQueryButton.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell.Hyprland\n\nRippleButton {\n    id: root\n    property string query\n\n    implicitHeight: 30\n    leftPadding: 6\n    rightPadding: 10\n    buttonRadius: Appearance.rounding.verysmall\n    colBackground: Appearance.colors.colSurfaceContainerHighest\n    colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover\n    colRipple: Appearance.colors.colSurfaceContainerHighestActive\n\n    PointingHandInteraction {}\n    onClicked: {\n        let url = Config.options.search.engineBaseUrl + root.query;\n        for (let site of (Config?.options?.search.excludedSites ?? [])) {\n            url += ` -site:${site}`;\n        }\n        Qt.openUrlExternally(url);\n        GlobalStates.sidebarLeftOpen = false;\n    }\n\n    contentItem: Item {\n        anchors.centerIn: parent\n        implicitWidth: rowLayout.implicitWidth\n        implicitHeight: rowLayout.implicitHeight\n        RowLayout {\n            id: rowLayout\n            anchors.centerIn: parent\n            spacing: 5\n            MaterialSymbol {\n                text: \"search\"\n                iconSize: 20\n                color: Appearance.m3colors.m3onSurface\n            }\n            StyledText {\n                id: text\n                horizontalAlignment: Text.AlignHCenter\n                text: root.query\n                color: Appearance.m3colors.m3onSurface\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/anime/BooruImage.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.utils\nimport qs.modules.common.widgets\nimport QtQml\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nButton {\n    id: root\n    property var imageData\n    property var rowHeight\n    property bool manualDownload: false\n    property string previewDownloadPath\n    property string downloadPath\n    property string nsfwPath\n    property string fileName: decodeURIComponent((imageData.file_url).substring((imageData.file_url).lastIndexOf('/') + 1))\n    property string filePath: `${root.previewDownloadPath}/${root.fileName}`\n    property int maxTagStringLineLength: 50\n    property real imageRadius: Appearance.rounding.small\n\n    property bool showActions: false\n    ImageDownloaderProcess {\n        id: imageDownloader\n        running: root.manualDownload\n        filePath: root.filePath\n        sourceUrl: root.imageData.preview_url ?? root.imageData.sample_url\n        onDone: (path, width, height) => {\n            imageObject.source = \"\"\n            imageObject.source = path\n            if (!modelData.width || !modelData.height) {\n                modelData.width = width\n                modelData.height = height\n                modelData.aspect_ratio = width / height\n            }\n        }\n    }\n\n    StyledToolTip {\n        text: `${StringUtils.wordWrap(root.imageData.tags, root.maxTagStringLineLength)}`\n    }\n\n    padding: 0\n    implicitWidth: root.rowHeight * modelData.aspect_ratio\n    implicitHeight: root.rowHeight\n\n    background: Rectangle {\n        implicitWidth: root.rowHeight * modelData.aspect_ratio\n        implicitHeight: root.rowHeight\n        radius: imageRadius\n        color: Appearance.colors.colLayer2\n    }\n\n    contentItem: Item {\n        anchors.fill: parent\n\n        StyledImage {\n            id: imageObject\n            anchors.fill: parent\n            width: root.rowHeight * modelData.aspect_ratio\n            height: root.rowHeight\n            fillMode: Image.PreserveAspectFit\n            source: modelData.preview_url\n\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: root.rowHeight * modelData.aspect_ratio\n                    height: root.rowHeight\n                    radius: imageRadius\n                }\n            }\n        }\n\n        RippleButton {\n            id: menuButton\n            anchors.top: parent.top\n            anchors.right: parent.right\n            property real buttonSize: 30\n            anchors.margins: Math.max(root.imageRadius - buttonSize / 2, 8)\n            implicitHeight: buttonSize\n            implicitWidth: buttonSize\n\n            buttonRadius: Appearance.rounding.full\n            colBackground: ColorUtils.transparentize(Appearance.m3colors.m3surface, 0.3)\n            colBackgroundHover: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.8), 0.2)\n            colRipple: ColorUtils.transparentize(ColorUtils.mix(Appearance.m3colors.m3surface, Appearance.m3colors.m3onSurface, 0.6), 0.1)\n\n            contentItem: MaterialSymbol {\n                horizontalAlignment: Text.AlignHCenter\n                iconSize: Appearance.font.pixelSize.large\n                color: Appearance.m3colors.m3onSurface\n                text: \"more_vert\"\n            }\n\n            onClicked: {\n                root.showActions = !root.showActions\n            }\n        }\n\n        Loader {\n            id: contextMenuLoader\n            active: root.showActions\n            anchors.top: menuButton.bottom\n            anchors.right: parent.right\n            anchors.margins: 8\n\n            sourceComponent: Item {\n                width: contextMenu.width\n                height: contextMenu.height\n\n                StyledRectangularShadow {\n                    target: contextMenu\n                }\n                Rectangle {\n                    id: contextMenu\n                    anchors.centerIn: parent\n                    opacity: root.showActions ? 1 : 0\n                    visible: opacity > 0\n                    radius: Appearance.rounding.small\n                    color: Appearance.m3colors.m3surfaceContainer\n                    implicitHeight: contextMenuColumnLayout.implicitHeight + radius * 2\n                    implicitWidth: contextMenuColumnLayout.implicitWidth\n\n                    Behavior on opacity {\n                        NumberAnimation {\n                            duration: Appearance.animation.elementMoveFast.duration\n                            easing.type: Appearance.animation.elementMoveFast.type\n                            easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                        }\n                    }\n\n                    ColumnLayout {\n                        id: contextMenuColumnLayout\n                        anchors.centerIn: parent\n                        spacing: 0\n\n                        MenuButton {\n                            id: openFileLinkButton\n                            Layout.fillWidth: true\n                            buttonText: Translation.tr(\"Open file link\")\n                            onClicked: {\n                                root.showActions = false\n                                Hyprland.dispatch(\"hl.config({cursor = {no_warps = true}})\")\n                                Qt.openUrlExternally(root.imageData.file_url)\n                                Hyprland.dispatch(\"hl.config({cursor = {no_warps = false}})\")\n                            }\n                        }\n                        MenuButton {\n                            id: sourceButton\n                            visible: root.imageData.source && root.imageData.source.length > 0\n                            Layout.fillWidth: true\n                            buttonText: Translation.tr(\"Go to source (%1)\").arg(StringUtils.getDomain(root.imageData.source))\n                            enabled: root.imageData.source && root.imageData.source.length > 0\n                            onClicked: {\n                                root.showActions = false\n                                Hyprland.dispatch(\"hl.config({cursor = {no_warps = true}})\")\n                                Qt.openUrlExternally(root.imageData.source)\n                                Hyprland.dispatch(\"hl.config({cursor = {no_warps = false}})\")\n                            }\n                        }\n                        MenuButton {\n                            id: downloadButton\n                            Layout.fillWidth: true\n                            buttonText: Translation.tr(\"Download\")\n                            onClicked: {\n                                root.showActions = false;\n                                const targetPath = root.imageData.is_nsfw ? root.nsfwPath : root.downloadPath;\n                                Quickshell.execDetached([\"bash\", \"-c\", \n                                    `mkdir -p '${targetPath}' && curl '${root.imageData.file_url}' -o '${targetPath}/${root.fileName}' && notify-send '${Translation.tr(\"Download complete\")}' '${root.downloadPath}/${root.fileName}' -a 'Shell'`\n                                ])\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/anime/BooruResponse.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.ii.sidebarLeft\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport Qt5Compat.GraphicalEffects\n\nRectangle {\n    id: root\n    property var responseData\n    property var tagInputField\n\n    property string previewDownloadPath\n    property string downloadPath\n    property string nsfwPath\n\n    property real availableWidth: parent.width\n    property real rowTooShortThreshold: 190\n    property real imageSpacing: 5\n    property real responsePadding: 5\n\n    anchors.left: parent?.left\n    anchors.right: parent?.right\n    implicitHeight: columnLayout.implicitHeight + root.responsePadding * 2\n\n    Component.onCompleted: {\n        // Break property bind to prevent aggressive updates\n        availableWidth = parent.width\n    }\n\n    Connections {\n        target: parent\n        function onWidthChanged() {\n            updateWidthTimer.restart()\n        }\n    }\n\n    Timer {\n        id: updateWidthTimer\n        interval: 100\n        onTriggered: {\n            availableWidth = parent.width\n        }\n    }\n\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colLayer1\n\n    ColumnLayout {\n        id: columnLayout\n        \n        anchors.left: parent.left\n        anchors.right: parent.right\n        anchors.top: parent.top\n        anchors.margins: responsePadding\n        spacing: root.imageSpacing\n\n        RowLayout { // Header\n            Rectangle { // Provider name\n                id: providerNameWrapper\n                color: Appearance.colors.colSecondaryContainer\n                radius: Appearance.rounding.small\n                implicitWidth: providerName.implicitWidth + 10 * 2\n                implicitHeight: Math.max(providerName.implicitHeight + 5 * 2, 30)\n                Layout.alignment: Qt.AlignVCenter\n\n                StyledText {\n                    id: providerName\n                    anchors.centerIn: parent\n                    font.pixelSize: Appearance.font.pixelSize.large\n                    color: Appearance.m3colors.m3onSecondaryContainer\n                    text: Booru.providers[root.responseData.provider].name\n                }\n            }\n            Item { Layout.fillWidth: true }\n            Item { // Page number\n                visible: root.responseData.page != \"\" && root.responseData.page > 0\n                implicitWidth: Math.max(pageNumber.implicitWidth + 10 * 2, 30)\n                implicitHeight: pageNumber.implicitHeight + 5 * 2\n                Layout.alignment: Qt.AlignVCenter\n\n                StyledText {\n                    id: pageNumber\n                    anchors.centerIn: parent\n                    font.pixelSize: Appearance.font.pixelSize.smaller\n                    color: Appearance.colors.colOnLayer2\n                    // text: `Page ${root.responseData.page}`\n                    text: Translation.tr(\"Page %1\").arg(root.responseData.page)\n                }\n            }\n        }\n\n        StyledFlickable { // Tag strip\n            id: tagsFlickable\n            visible: root.responseData.tags.length > 0\n            Layout.alignment: Qt.AlignLeft\n            Layout.fillWidth: true\n            implicitHeight: tagRowLayout.implicitHeight\n            contentWidth: tagRowLayout.implicitWidth\n\n            clip: true\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: tagsFlickable.width\n                    height: tagsFlickable.height\n                    radius: Appearance.rounding.small\n                }\n            }\n\n            Behavior on implicitHeight {\n                animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n            }\n\n            RowLayout {\n                id: tagRowLayout\n                Layout.alignment: Qt.AlignBottom\n\n                Repeater {\n                    id: tagRepeater\n                    model: root.responseData.tags\n\n                    ApiCommandButton {\n                        Layout.fillWidth: false\n                        buttonText: modelData\n                        onClicked: {\n                            if(root.tagInputField.text.length !== 0) root.tagInputField.text += \" \"\n                            root.tagInputField.text += modelData\n                        }\n                    }\n                }\n                \n            }\n        }\n\n        StyledText { // Message\n            id: messageText\n            Layout.fillWidth: true\n            visible: root.responseData.message.length > 0\n            font.pixelSize: Appearance.font.pixelSize.small\n            color: Appearance.colors.colOnLayer1\n            text: root.responseData.message\n            wrapMode: Text.WordWrap\n            Layout.margins: responsePadding\n            textFormat: Text.MarkdownText\n            onLinkActivated: (link) => {\n                Qt.openUrlExternally(link)\n                GlobalStates.sidebarLeftOpen = false\n            }\n            PointingHandLinkHover {}\n        }\n\n        Repeater {\n            model: ScriptModel {\n                values: {\n                    // Greedily add images to a row as long as rowHeight >= rowTooShortThreshold\n                    let i = 0;\n                    let rows = [];\n                    const responseList = root.responseData.images;\n                    const minRowHeight = rowTooShortThreshold;\n                    const availableImageWidth = availableWidth - root.imageSpacing - (responsePadding * 2);\n\n                    while (i < responseList.length) {\n                        let row = {\n                            height: 0,\n                            images: [],\n                        };\n                        let j = i;\n                        let combinedAspect = 0;\n                        let rowHeight = 0;\n\n                        // Try to add as many images as possible without going below minRowHeight\n                        while (j < responseList.length) {\n                            combinedAspect += responseList[j].aspect_ratio;\n                            // Subtract imageSpacing for each gap between images in the row\n                            let imagesInRow = j - i + 1;\n                            let totalSpacing = root.imageSpacing * (imagesInRow - 1);\n                            let rowAvailableWidth = availableImageWidth - totalSpacing;\n                            rowHeight = rowAvailableWidth / combinedAspect;\n                            if (rowHeight < minRowHeight) {\n                                combinedAspect -= responseList[j].aspect_ratio;\n                                imagesInRow -= 1;\n                                totalSpacing = root.imageSpacing * (imagesInRow - 1);\n                                rowAvailableWidth = availableImageWidth - totalSpacing;\n                                rowHeight = rowAvailableWidth / combinedAspect;\n                                break;\n                            }\n                            j++;\n                        }\n\n                        // If we couldn't add any image (shouldn't happen), add at least one\n                        if (j === i) {\n                            row.images.push(responseList[i]);\n                            row.height = availableImageWidth / responseList[i].aspect_ratio;\n                            rows.push(row);\n                            i++;\n                        } else {\n                            for (let k = i; k < j; k++) {\n                                row.images.push(responseList[k]);\n                            }\n                            // Recalculate spacing for the final row\n                            let imagesInRow = j - i;\n                            let totalSpacing = root.imageSpacing * (imagesInRow - 1);\n                            let rowAvailableWidth = availableImageWidth - totalSpacing;\n                            row.height = rowAvailableWidth / combinedAspect;\n                            rows.push(row);\n                            i = j;\n                        }\n                    }\n                    return rows;\n                }\n            }\n            delegate: RowLayout {\n                id: imageRow\n                required property var modelData\n                property var rowHeight: modelData.height\n                spacing: root.imageSpacing\n\n                Repeater {\n                    model: modelData.images\n                    delegate: BooruImage {\n                        required property var modelData\n                        imageData: modelData\n                        rowHeight: imageRow.rowHeight\n                        imageRadius: imageRow.modelData.images.length == 1 ? 50 : Appearance.rounding.normal\n                        // Download manually to reduce redundant requests or make sure downloading works\n                        manualDownload: [\"danbooru\", \"waifu.im\", \"t.alcy.cc\"].includes(root.responseData.provider)\n                        previewDownloadPath: root.previewDownloadPath\n                        downloadPath: root.downloadPath\n                        nsfwPath: root.nsfwPath\n                    }\n                }\n            }\n        }\n\n        RippleButton { // Next page button\n            id: button\n            property string buttonText\n            visible: root.responseData.page != \"\" && root.responseData.page > 0\n\n            Layout.alignment: Qt.AlignRight\n            implicitHeight: 30\n            leftPadding: 10\n            rightPadding: 5\n\n            onClicked: {\n                tagInputField.text = `${responseData.tags.join(\" \")} ${parseInt(root.responseData.page) + 1}`\n                tagInputField.accept()\n            }\n\n            buttonRadius: Appearance.rounding.small\n            colBackground: Appearance.colors.colSurfaceContainerHighest\n            colBackgroundHover: Appearance.colors.colSurfaceContainerHighestHover\n            colRipple: Appearance.colors.colSurfaceContainerHighestActive            \n\n            contentItem: Item {\n                anchors.fill: parent\n                implicitHeight: nextPageRow.implicitHeight\n                implicitWidth: nextPageRow.implicitWidth\n\n                RowLayout {\n                    id: nextPageRow\n                    anchors.centerIn: parent\n                    spacing: 0\n                    StyledText {\n                        Layout.alignment: Qt.AlignVCenter\n                        verticalAlignment: Text.AlignVCenter\n                        text: \"Next page\"\n                        color: Appearance.m3colors.m3onSurface\n                    }\n                    MaterialSymbol {\n                        Layout.alignment: Qt.AlignVCenter\n                        iconSize: Appearance.font.pixelSize.larger\n                        color: Appearance.m3colors.m3onSurface\n                        text: \"chevron_right\"\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/translator/LanguageSelectorButton.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton {\n    id: root\n    property string displayText: \"\"\n    colBackground: Appearance.colors.colLayer2\n\n    implicitWidth: contentItem.implicitWidth + horizontalPadding * 2\n    implicitHeight: contentItem.implicitHeight + verticalPadding * 2\n\n    contentItem: Item {\n        anchors.centerIn: parent\n        implicitWidth: languageRow.implicitWidth\n        implicitHeight: languageText.implicitHeight\n        RowLayout {\n            id: languageRow\n            anchors.centerIn: parent\n            spacing: 0\n            StyledText {\n                id: languageText\n                Layout.alignment: Qt.AlignVCenter\n                Layout.leftMargin: 5\n                text: root.displayText\n                color: Appearance.colors.colOnLayer2\n                font.pixelSize: Appearance.font.pixelSize.small\n            }\n            MaterialSymbol {\n                Layout.alignment: Qt.AlignVCenter\n                iconSize: Appearance.font.pixelSize.hugeass\n                text: \"arrow_drop_down\"\n                color: Appearance.colors.colOnLayer2\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarLeft/translator/TextCanvas.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nRectangle {\n    id: root\n    property bool isInput: true // true for input, false for output\n    property string placeholderText\n    property string text: \"\"\n    property var inputTextArea: isInput ? inputLoader.item : undefined\n    readonly property string displayedText: isInput ? inputLoader.item.text : \n        root.text.length > 0 ? outputLoader.item.text : \"\"\n    default property alias actionButtons: actions.data\n    Layout.fillWidth: true\n    implicitHeight: Math.max(150, inputColumn.implicitHeight)\n    color: Appearance.colors.colLayer2\n    radius: Appearance.rounding.normal\n\n    signal inputTextChanged(); // Signal emitted when text changes\n\n    ColumnLayout {\n        id: inputColumn\n        anchors.fill: parent\n        spacing: 0\n\n        Loader {\n            id: inputLoader\n            active: root.isInput\n            visible: root.isInput\n            Layout.fillWidth: true\n            sourceComponent: StyledTextArea { // Input area\n                id: inputTextArea\n                placeholderText: root.placeholderText\n                wrapMode: TextEdit.Wrap\n                textFormat: TextEdit.PlainText\n                font.pixelSize: Appearance.font.pixelSize.small\n                color: Appearance.colors.colOnLayer1\n                padding: 15\n                background: null\n                onTextChanged: root.inputTextChanged()\n            }\n        }\n\n        Loader {\n            id: outputLoader\n            active: !root.isInput\n            visible: !root.isInput\n            Layout.fillWidth: true\n            sourceComponent: StyledText { // Output area\n                id: outputTextArea\n                padding: 15\n                wrapMode: Text.Wrap\n                font.pixelSize: Appearance.font.pixelSize.small\n                color: root.text.length > 0 ? Appearance.colors.colOnLayer1 : Appearance.colors.colSubtext\n                text: root.text.length > 0 ? root.text : root.placeholderText\n            }\n        }\n\n        Item { Layout.fillHeight: true } \n\n        RowLayout { // Status row\n            Layout.fillWidth: true\n            Layout.margins: 10\n            spacing: 10\n\n            Loader {\n                active: root.isInput\n                visible: root.isInput\n                Layout.leftMargin: 10\n                sourceComponent: Text {\n                    text: Translation.tr(\"%1 characters\").arg(inputLoader.item.text.length)\n                    color: Appearance.colors.colOnLayer1\n                    font.pixelSize: Appearance.font.pixelSize.smaller\n                }\n            }\n            Item { Layout.fillWidth: true }\n            ButtonGroup {\n                id: actions\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/BottomWidgetGroup.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs.modules.ii.sidebarRight.calendar\nimport qs.modules.ii.sidebarRight.todo\nimport qs.modules.ii.sidebarRight.pomodoro\nimport QtQuick\nimport QtQuick.Layouts\n\nRectangle {\n    id: root\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colLayer1\n    clip: true\n    implicitHeight: collapsed ? collapsedBottomWidgetGroupRow.implicitHeight : 350\n    property int selectedTab: Persistent.states.sidebar.bottomGroup.tab\n    property int previousIndex: -1\n    property bool collapsed: Persistent.states.sidebar.bottomGroup.collapsed\n    property var tabs: [\n        {\n            \"type\": \"calendar\",\n            \"name\": Translation.tr(\"Calendar\"),\n            \"icon\": \"calendar_month\",\n            \"widget\": \"calendar/CalendarWidget.qml\"\n        },\n        {\n            \"type\": \"todo\",\n            \"name\": Translation.tr(\"To Do\"),\n            \"icon\": \"done_outline\",\n            \"widget\": \"todo/TodoWidget.qml\"\n        },\n        {\n            \"type\": \"timer\",\n            \"name\": Translation.tr(\"Timer\"),\n            \"icon\": \"schedule\",\n            \"widget\": \"pomodoro/PomodoroWidget.qml\"\n        },\n    ]\n\n    Behavior on implicitHeight {\n        NumberAnimation {\n            duration: Appearance.animation.elementMove.duration\n            easing.type: Appearance.animation.elementMove.type\n            easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n        }\n    }\n\n    function setCollapsed(state) {\n        Persistent.states.sidebar.bottomGroup.collapsed = state;\n        if (collapsed) {\n            bottomWidgetGroupRow.opacity = 0;\n        } else {\n            collapsedBottomWidgetGroupRow.opacity = 0;\n        }\n        collapseCleanFadeTimer.start();\n    }\n\n    Timer {\n        id: collapseCleanFadeTimer\n        interval: Appearance.animation.elementMove.duration / 2\n        repeat: false\n        onTriggered: {\n            if (collapsed)\n                collapsedBottomWidgetGroupRow.opacity = 1;\n            else\n                bottomWidgetGroupRow.opacity = 1;\n        }\n    }\n\n    Keys.onPressed: event => {\n        if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.ControlModifier) {\n            if (event.key === Qt.Key_PageDown) {\n                root.selectedTab = Math.min(root.selectedTab + 1, root.tabs.length - 1);\n            } else if (event.key === Qt.Key_PageUp) {\n                root.selectedTab = Math.max(root.selectedTab - 1, 0);\n            }\n            event.accepted = true;\n        }\n    }\n\n    // The thing when collapsed\n    RowLayout {\n        id: collapsedBottomWidgetGroupRow\n        opacity: collapsed ? 1 : 0\n        visible: opacity > 0\n        Behavior on opacity {\n            NumberAnimation {\n                id: collapsedBottomWidgetGroupRowFade\n                duration: Appearance.animation.elementMove.duration / 2\n                easing.type: Appearance.animation.elementMove.type\n                easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n            }\n        }\n\n        spacing: 15\n\n        CalendarHeaderButton {\n            Layout.margins: 10\n            Layout.rightMargin: 0\n            forceCircle: true\n            downAction: () => {\n                root.setCollapsed(false);\n            }\n            contentItem: MaterialSymbol {\n                text: \"keyboard_arrow_up\"\n                iconSize: Appearance.font.pixelSize.larger\n                horizontalAlignment: Text.AlignHCenter\n                color: Appearance.colors.colOnLayer1\n            }\n        }\n\n        StyledText {\n            property int remainingTasks: Todo.list.filter(task => !task.done).length\n            Layout.margins: 10\n            Layout.leftMargin: 0\n            // text: `${DateTime.collapsedCalendarFormat}   •   ${remainingTasks} task${remainingTasks > 1 ? \"s\" : \"\"}`\n            text: Translation.tr(\"%1   •   %2 tasks\").arg(DateTime.collapsedCalendarFormat).arg(remainingTasks)\n            font.pixelSize: Appearance.font.pixelSize.large\n            color: Appearance.colors.colOnLayer1\n        }\n    }\n\n    // The thing when expanded\n    RowLayout {\n        id: bottomWidgetGroupRow\n\n        opacity: collapsed ? 0 : 1\n        visible: opacity > 0\n        Behavior on opacity {\n            NumberAnimation {\n                id: bottomWidgetGroupRowFade\n                duration: Appearance.animation.elementMove.duration / 2\n                easing.type: Appearance.animation.elementMove.type\n                easing.bezierCurve: Appearance.animation.elementMove.bezierCurve\n            }\n        }\n\n        anchors.fill: parent\n        // implicitHeight: tabStack.implicitHeight\n        spacing: 20\n\n        // Navigation rail\n        Item {\n            Layout.fillHeight: true\n            Layout.fillWidth: false\n            Layout.leftMargin: 10\n            Layout.topMargin: 10\n            implicitWidth: tabBar.implicitWidth\n            // Navigation rail buttons\n            NavigationRailTabArray {\n                id: tabBar\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.left: parent.left\n                anchors.leftMargin: 5\n                currentIndex: root.selectedTab\n                expanded: false\n                Repeater {\n                    model: root.tabs\n                    NavigationRailButton {\n                        required property int index\n                        required property var modelData\n                        showToggledHighlight: false\n                        toggled: root.selectedTab == index\n                        buttonText: modelData.name\n                        buttonIcon: modelData.icon\n                        onPressed: {\n                            root.selectedTab = index;\n                            Persistent.states.sidebar.bottomGroup.tab = index;\n                        }\n                    }\n                }\n            }\n            // Collapse button\n            CalendarHeaderButton {\n                anchors.left: parent.left\n                anchors.top: parent.top\n                forceCircle: true\n                downAction: () => {\n                    root.setCollapsed(true);\n                }\n                contentItem: MaterialSymbol {\n                    text: \"keyboard_arrow_down\"\n                    iconSize: Appearance.font.pixelSize.larger\n                    horizontalAlignment: Text.AlignHCenter\n                    color: Appearance.colors.colOnLayer1\n                }\n            }\n        }\n\n        // Content area\n        Item {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            // implicitHeight: tabStack.implicitHeight\n\n            Loader {\n                id: tabStack\n                anchors.fill: parent\n                anchors.bottomMargin: -anchors.topMargin\n\n                Component.onCompleted: {\n                    tabStack.source = root.tabs[root.selectedTab].widget;\n                }\n\n                Connections {\n                    target: root\n                    function onSelectedTabChanged() {\n                        if (root.currentTab > root.previousIndex)\n                            tabSwitchBehavior.animation.down = true;\n                        else if (root.currentTab < root.previousIndex)\n                            tabSwitchBehavior.animation.down = false;\n                        tabStack.source = root.tabs[root.selectedTab].widget;\n                    }\n                }\n\n                Behavior on source {\n                    id: tabSwitchBehavior\n                    animation: TabSwitchAnim {\n                        id: upAnim\n                        down: true\n                    }\n                }\n            }\n        }\n    }\n\n    component TabSwitchAnim: SequentialAnimation {\n        id: switchAnim\n        property bool down: false\n        ParallelAnimation {\n            PropertyAnimation {\n                target: tabStack\n                properties: \"opacity\"\n                to: 0\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n            }\n            PropertyAnimation {\n                target: tabStack.anchors\n                properties: \"topMargin\"\n                to: 10 * (switchAnim.down ? -1 : 1)\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n            }\n        }\n        PropertyAction {\n            target: tabStack\n            property: \"source\"\n            value: root.tabs[root.selectedTab].widget\n        } // The source change happens here\n        ParallelAnimation {\n            PropertyAnimation {\n                target: tabStack.anchors\n                properties: \"topMargin\"\n                from: 10 * -(switchAnim.down ? -1 : 1)\n                to: 0\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve\n            }\n            PropertyAnimation {\n                target: tabStack\n                properties: \"opacity\"\n                to: 1\n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animation.elementMoveEnter.bezierCurve\n            }\n        }\n        ScriptAction {\n            script: {\n                root.previousIndex = root.selectedTab;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/CenterWidgetGroup.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs.modules.ii.sidebarRight.notifications\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nRectangle {\n    id: root\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colLayer1\n\n    NotificationList {\n        anchors.fill: parent\n        anchors.margins: 5\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/QuickSliders.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport Quickshell.Services.UPower\n\nRectangle {\n    id: root\n\n    property var screen: root.QsWindow.window?.screen\n    property var brightnessMonitor: Brightness.getMonitorForScreen(screen)\n\n    implicitWidth: contentItem.implicitWidth + root.horizontalPadding * 2\n    implicitHeight: contentItem.implicitHeight + root.verticalPadding * 2\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colLayer1\n    property real verticalPadding: 4\n    property real horizontalPadding: 12\n\n    Column {\n        id: contentItem\n        anchors {\n            fill: parent\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n            topMargin: root.verticalPadding\n            bottomMargin: root.verticalPadding\n        }\n\n        Loader {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            visible: active\n            active: Config.options.sidebar.quickSliders.showBrightness\n            sourceComponent: QuickSlider {\n                materialSymbol: \"light_mode\"\n                secondaryMaterialSymbol: \"wb_twilight\"\n                stopIndicatorValues: Hyprsunset.gamma !== 100 && root.brightnessMonitor?.brightness !== 0 ? [0.3 + root.brightnessMonitor?.brightness * 0.7] : []\n                value: Hyprsunset.gamma === 100? 0.3 + root.brightnessMonitor?.brightness * 0.7 : (Hyprsunset.gamma - Hyprsunset.gammaLowerLimit) / (100 - Hyprsunset.gammaLowerLimit) * 0.3\n                tooltipContent: Hyprsunset.gamma === 100 ? `${Math.round(root.brightnessMonitor?.brightness * 100)}%` : `${Translation.tr(\"Gamma\")} ${Hyprsunset.gamma}%`\n                onMoved: {\n                    if (value >= 0.3) {\n                        // 0.3 - 1.0 brightness\n                        root.brightnessMonitor.setBrightness((value - 0.3) / 0.7);\n                        if (Hyprsunset.gamma !== 100) {\n                            Hyprsunset.setGamma(100);\n                        }\n                    } else {\n                        // 0 - 0.3 gamma\n                        if (root.brightnessMonitor.brightness !== 0) {\n                            root.brightnessMonitor.setBrightness(0);\n                        }\n                        Hyprsunset.setGamma((value / 0.3 * (100 - Hyprsunset.gammaLowerLimit) + Hyprsunset.gammaLowerLimit));\n                    }\n                }\n            }\n        }\n\n        Loader {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            visible: active\n            active: Config.options.sidebar.quickSliders.showVolume\n            sourceComponent: QuickSlider {\n                materialSymbol: \"volume_up\"\n                value: Audio.sink.audio.volume\n                onMoved: {\n                    Audio.sink.audio.volume = value\n                }\n            }\n        }\n\n        Loader {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            visible: active\n            active: Config.options.sidebar.quickSliders.showMic\n            sourceComponent: QuickSlider {\n                materialSymbol: \"mic\"\n                value: Audio.source.audio.volume\n                onMoved: {\n                    Audio.source.audio.volume = value\n                }\n            }\n        }\n    }\n\n    component QuickSlider: StyledSlider { \n        id: quickSlider\n        required property string materialSymbol\n        property string secondaryMaterialSymbol\n        configuration: StyledSlider.Configuration.M\n        stopIndicatorValues: []\n        dividerValues: secondaryMaterialSymbol.length > 0 ? [secondaryIcon.iconLocation] : []\n        \n        MaterialSymbol {\n            id: icon\n            property bool nearFull: quickSlider.value >= 0.9\n            anchors {\n                verticalCenter: quickSlider.verticalCenter\n                right: nearFull ? quickSlider.handle.right : quickSlider.right\n                rightMargin: nearFull ? 14 : 8\n            }\n            iconSize: 20\n            color: nearFull ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer\n            text: quickSlider.materialSymbol\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n            Behavior on anchors.rightMargin {\n                animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n            }\n        }\n\n        MaterialSymbol {\n            id: secondaryIcon\n            visible: secondaryMaterialSymbol.length > 0\n            property real iconLocation: 0.3\n            property bool nearIcon: iconLocation - quickSlider.value <= 0.1 && iconLocation - quickSlider.value > (quickSlider.handleWidth + 8 - 14) / quickSlider.effectiveDraggingWidth\n            anchors {\n                verticalCenter: quickSlider.verticalCenter\n                right: nearIcon ? quickSlider.handle.right : quickSlider.right\n                rightMargin: nearIcon ? 14 : (1 - iconLocation) * quickSlider.effectiveDraggingWidth + quickSlider.rightPadding + 8\n            }\n            iconSize: 20\n            color: quickSlider.value >= iconLocation - 0.1 ? Appearance.colors.colOnPrimary : Appearance.colors.colOnSecondaryContainer\n            text: secondaryMaterialSymbol\n\n            Behavior on color {\n                animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRight.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport QtQuick\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n    property int sidebarWidth: Appearance.sizes.sidebarWidth\n\n    PanelWindow {\n        id: panelWindow\n        visible: GlobalStates.sidebarRightOpen\n\n        function hide() {\n            GlobalStates.sidebarRightOpen = false;\n        }\n\n        exclusiveZone: 0\n        implicitWidth: sidebarWidth\n        WlrLayershell.namespace: \"quickshell:sidebarRight\"\n        WlrLayershell.keyboardFocus: GlobalStates.sidebarRightOpen ? WlrKeyboardFocus.OnDemand : WlrKeyboardFocus.None\n        color: \"transparent\"\n\n        anchors {\n            top: true\n            right: true\n            bottom: true\n        }\n\n        onVisibleChanged: {\n            if (visible) {\n                GlobalFocusGrab.addDismissable(panelWindow);\n            } else {\n                GlobalFocusGrab.removeDismissable(panelWindow);\n            }\n        }\n        Connections {\n            target: GlobalFocusGrab\n            function onDismissed() {\n                panelWindow.hide();\n            }\n        }\n\n        Loader {\n            id: sidebarContentLoader\n            active: GlobalStates.sidebarRightOpen || Config?.options.sidebar.keepRightSidebarLoaded\n            anchors {\n                fill: parent\n                margins: Appearance.sizes.hyprlandGapsOut\n                leftMargin: Appearance.sizes.elevationMargin\n            }\n            width: sidebarWidth - Appearance.sizes.hyprlandGapsOut - Appearance.sizes.elevationMargin\n            height: parent.height - Appearance.sizes.hyprlandGapsOut * 2\n\n            focus: GlobalStates.sidebarRightOpen\n            Keys.onPressed: event => {\n                if (event.key === Qt.Key_Escape) {\n                    panelWindow.hide();\n                }\n            }\n\n            sourceComponent: SidebarRightContent {}\n        }\n    }\n\n    IpcHandler {\n        target: \"sidebarRight\"\n\n        function toggle(): void {\n            GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n        }\n\n        function close(): void {\n            GlobalStates.sidebarRightOpen = false;\n        }\n\n        function open(): void {\n            GlobalStates.sidebarRightOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarRightToggle\"\n        description: \"Toggles right sidebar on press\"\n\n        onPressed: {\n            GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"sidebarRightOpen\"\n        description: \"Opens right sidebar on press\"\n\n        onPressed: {\n            GlobalStates.sidebarRightOpen = true;\n        }\n    }\n    GlobalShortcut {\n        name: \"sidebarRightClose\"\n        description: \"Closes right sidebar on press\"\n\n        onPressed: {\n            GlobalStates.sidebarRightOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/SidebarRightContent.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Bluetooth\nimport Quickshell.Hyprland\n\nimport qs.modules.ii.sidebarRight.quickToggles\nimport qs.modules.ii.sidebarRight.quickToggles.classicStyle\n\nimport qs.modules.ii.sidebarRight.bluetoothDevices\nimport qs.modules.ii.sidebarRight.nightLight\nimport qs.modules.ii.sidebarRight.volumeMixer\nimport qs.modules.ii.sidebarRight.wifiNetworks\n\nItem {\n    id: root\n    property int sidebarWidth: Appearance.sizes.sidebarWidth\n    property int sidebarPadding: 10\n    property string settingsQmlPath: Quickshell.shellPath(\"settings.qml\")\n    property bool showAudioOutputDialog: false\n    property bool showAudioInputDialog: false\n    property bool showBluetoothDialog: false\n    property bool showNightLightDialog: false\n    property bool showWifiDialog: false\n    property bool editMode: false\n\n    Connections {\n        target: GlobalStates\n        function onSidebarRightOpenChanged() {\n            if (!GlobalStates.sidebarRightOpen) {\n                root.showWifiDialog = false;\n                root.showBluetoothDialog = false;\n                root.showAudioOutputDialog = false;\n                root.showAudioInputDialog = false;\n            }\n        }\n    }\n\n    implicitHeight: sidebarRightBackground.implicitHeight\n    implicitWidth: sidebarRightBackground.implicitWidth\n\n    StyledRectangularShadow {\n        target: sidebarRightBackground\n    }\n    Rectangle {\n        id: sidebarRightBackground\n\n        anchors.fill: parent\n        implicitHeight: parent.height - Appearance.sizes.hyprlandGapsOut * 2\n        implicitWidth: sidebarWidth - Appearance.sizes.hyprlandGapsOut * 2\n        color: Appearance.colors.colLayer0\n        border.width: 1\n        border.color: Appearance.colors.colLayer0Border\n        radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1\n\n        ColumnLayout {\n            anchors.fill: parent\n            anchors.margins: sidebarPadding\n            spacing: sidebarPadding\n\n            SystemButtonRow {\n                Layout.fillHeight: false\n                Layout.fillWidth: true\n                // Layout.margins: 10\n                Layout.topMargin: 5\n                Layout.bottomMargin: 0\n            }\n\n            Loader {\n                id: slidersLoader\n                Layout.fillWidth: true\n                visible: active\n                active: {\n                    const configQuickSliders = Config.options.sidebar.quickSliders\n                    if (!configQuickSliders.enable) return false\n                    if (!configQuickSliders.showMic && !configQuickSliders.showVolume && !configQuickSliders.showBrightness) return false;\n                    return true;\n                }\n                sourceComponent: QuickSliders {}\n            }\n\n            LoaderedQuickPanelImplementation {\n                styleName: \"classic\"\n                sourceComponent: ClassicQuickPanel {}\n            }\n\n            LoaderedQuickPanelImplementation {\n                styleName: \"android\"\n                sourceComponent: AndroidQuickPanel {\n                    editMode: root.editMode\n                }\n            }\n\n            CenterWidgetGroup {\n                Layout.alignment: Qt.AlignHCenter\n                Layout.fillHeight: true\n                Layout.fillWidth: true\n            }\n\n            BottomWidgetGroup {\n                Layout.alignment: Qt.AlignHCenter\n                Layout.fillHeight: false\n                Layout.fillWidth: true\n                Layout.preferredHeight: implicitHeight\n            }\n        }\n    }\n\n    ToggleDialog {\n        shownPropertyString: \"showAudioOutputDialog\"\n        dialog: VolumeDialog {\n            isSink: true\n        }\n    }\n\n    ToggleDialog {\n        shownPropertyString: \"showAudioInputDialog\"\n        dialog: VolumeDialog {\n            isSink: false\n        }\n    }\n\n    ToggleDialog {\n        shownPropertyString: \"showBluetoothDialog\"\n        dialog: BluetoothDialog {}\n        onShownChanged: {\n            if (!shown) {\n                Bluetooth.defaultAdapter.discovering = false;\n            } else {\n                Bluetooth.defaultAdapter.enabled = true;\n                Bluetooth.defaultAdapter.discovering = true;\n            }\n        }\n    }\n\n    ToggleDialog {\n        shownPropertyString: \"showNightLightDialog\"\n        dialog: NightLightDialog {}\n    }\n\n    ToggleDialog {\n        shownPropertyString: \"showWifiDialog\"\n        dialog: WifiDialog {}\n        onShownChanged: {\n            if (!shown) return;\n            Network.enableWifi();\n            Network.rescanWifi();\n        }\n    }\n\n    component ToggleDialog: Loader {\n        id: toggleDialogLoader\n        required property string shownPropertyString\n        property alias dialog: toggleDialogLoader.sourceComponent\n        readonly property bool shown: root[shownPropertyString]\n        anchors.fill: parent\n\n        onShownChanged: if (shown) toggleDialogLoader.active = true;\n        active: shown\n        onActiveChanged: {\n            if (active) {\n                item.show = true;\n                item.forceActiveFocus();\n            }\n        }\n        Connections {\n            target: toggleDialogLoader.item\n            function onDismiss() {\n                toggleDialogLoader.item.show = false\n                root[toggleDialogLoader.shownPropertyString] = false;\n            }\n            function onVisibleChanged() {\n                if (!toggleDialogLoader.item.visible && !root[toggleDialogLoader.shownPropertyString]) toggleDialogLoader.active = false;\n            }\n        }\n    }\n\n    component LoaderedQuickPanelImplementation: Loader {\n        id: quickPanelImplLoader\n        required property string styleName\n        Layout.alignment: item?.Layout.alignment ?? Qt.AlignHCenter\n        Layout.fillWidth: item?.Layout.fillWidth ?? false\n        visible: active\n        active: Config.options.sidebar.quickToggles.style === styleName\n        Connections {\n            target: quickPanelImplLoader.item\n            function onOpenAudioOutputDialog() {\n                root.showAudioOutputDialog = true;\n            }\n            function onOpenAudioInputDialog() {\n                root.showAudioInputDialog = true;\n            }\n            function onOpenBluetoothDialog() {\n                root.showBluetoothDialog = true;\n            }\n            function onOpenNightLightDialog() {\n                root.showNightLightDialog = true;\n            }\n            function onOpenWifiDialog() {\n                root.showWifiDialog = true;\n            }\n        }\n    }\n\n    component SystemButtonRow: Item {\n        implicitHeight: Math.max(uptimeContainer.implicitHeight, systemButtonsRow.implicitHeight)\n\n        Rectangle {\n            id: uptimeContainer\n            anchors {\n                top: parent.top\n                bottom: parent.bottom\n                left: parent.left\n            }\n            color: Appearance.colors.colLayer1\n            radius: height / 2\n            implicitWidth: uptimeRow.implicitWidth + 24\n            implicitHeight: uptimeRow.implicitHeight + 8\n            \n            Row {\n                id: uptimeRow\n                anchors.centerIn: parent\n                spacing: 8\n                CustomIcon {\n                    id: distroIcon\n                    anchors.verticalCenter: parent.verticalCenter\n                    width: 25\n                    height: 25\n                    source: SystemInfo.distroIcon\n                    colorize: true\n                    color: Appearance.colors.colOnLayer0\n                }\n                StyledText {\n                    anchors.verticalCenter: parent.verticalCenter\n                    font.pixelSize: Appearance.font.pixelSize.normal\n                    color: Appearance.colors.colOnLayer0\n                    text: Translation.tr(\"Up %1\").arg(DateTime.uptime)\n                    textFormat: Text.MarkdownText\n                }\n            }\n        }\n\n        ButtonGroup {\n            id: systemButtonsRow\n            anchors {\n                top: parent.top\n                bottom: parent.bottom\n                right: parent.right\n            }\n            color: Appearance.colors.colLayer1\n            padding: 4\n\n            QuickToggleButton {\n                toggled: root.editMode\n                visible: Config.options.sidebar.quickToggles.style === \"android\"\n                buttonIcon: \"edit\"\n                onClicked: root.editMode = !root.editMode\n                StyledToolTip {\n                    text: Translation.tr(\"Edit quick toggles\") + (root.editMode ? Translation.tr(\"\\nLMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\") : \"\")\n                }\n            }\n            QuickToggleButton {\n                toggled: false\n                buttonIcon: \"restart_alt\"\n                onClicked: {\n                    Quickshell.execDetached([\"hyprctl\", \"reload\"])\n                    Quickshell.reload(true);\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Reload Hyprland & Quickshell\")\n                }\n            }\n            QuickToggleButton {\n                toggled: false\n                buttonIcon: \"settings\"\n                onClicked: {\n                    GlobalStates.sidebarRightOpen = false;\n                    Quickshell.execDetached([\"qs\", \"-p\", root.settingsQmlPath]);\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Settings\")\n                }\n            }\n            QuickToggleButton {\n                toggled: false\n                buttonIcon: \"power_settings_new\"\n                onClicked: {\n                    GlobalStates.sessionOpen = true;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Session\")\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDeviceItem.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\n\nDialogListItem {\n    id: root\n    required property var device\n    property bool expanded: false\n    pointingHandCursor: !expanded\n\n    onClicked: expanded = !expanded\n    altAction: () => expanded = !expanded\n    \n    component ActionButton: DialogButton {\n        colBackground: Appearance.colors.colPrimary\n        colBackgroundHover: Appearance.colors.colPrimaryHover\n        colRipple: Appearance.colors.colPrimaryActive\n        colText: Appearance.colors.colOnPrimary\n    }\n\n    contentItem: ColumnLayout {\n        anchors {\n            fill: parent\n            topMargin: root.verticalPadding\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n        }\n        spacing: 0\n\n        RowLayout {\n            // Name\n            spacing: 10\n\n            MaterialSymbol {\n                iconSize: Appearance.font.pixelSize.larger\n                text: Icons.getBluetoothDeviceMaterialSymbol(root.device?.icon || \"\")\n                color: Appearance.colors.colOnSurfaceVariant\n            }\n\n            ColumnLayout {\n                spacing: 2\n                Layout.fillWidth: true\n                StyledText {\n                    Layout.fillWidth: true\n                    color: Appearance.colors.colOnSurfaceVariant\n                    elide: Text.ElideRight\n                    text: root.device?.name || Translation.tr(\"Unknown device\")\n                    textFormat: Text.PlainText\n                }\n                StyledText {\n                    visible: (root.device?.connected || root.device?.paired) ?? false\n                    Layout.fillWidth: true\n                    font.pixelSize: Appearance.font.pixelSize.smaller\n                    color: Appearance.colors.colSubtext\n                    elide: Text.ElideRight\n                    text: {\n                        if (!root.device?.paired) return \"\";\n                        let statusText = root.device?.connected ? Translation.tr(\"Connected\") : Translation.tr(\"Paired\");\n                        if (!root.device?.batteryAvailable) return statusText;\n                        statusText += ` • ${Math.round(root.device?.battery * 100)}%`;\n                        return statusText;\n                    }\n                }\n            }\n\n            MaterialSymbol {\n                text: \"keyboard_arrow_down\"\n                iconSize: Appearance.font.pixelSize.larger\n                color: Appearance.colors.colOnLayer3\n                rotation: root.expanded ? 180 : 0\n                Behavior on rotation {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n            }\n        }\n\n        RowLayout {\n            visible: root.expanded\n            Layout.topMargin: 8\n            Item {\n                Layout.fillWidth: true\n            }\n            ActionButton {\n                readonly property bool p: root.device?.paired ?? false\n                colBackground: p ? Appearance.colors.colError : ColorUtils.transparentize(Appearance.colors.colLayer3, 1)\n                colBackgroundHover: p ? Appearance.colors.colErrorHover : ColorUtils.transparentize(Appearance.colors.colLayer3, 1)\n                colRipple: p ? Appearance.colors.colErrorActive : Appearance.colors.colLayer3Hover\n                colText: p ? Appearance.colors.colOnError : Appearance.colors.colPrimary\n\n                buttonText: p ? Translation.tr(\"Forget\") : Translation.tr(\"Always connect\")\n                onClicked: {\n                    if (root.device?.paired) {\n                        root.device?.forget();\n                    } else {\n                        root.device?.pair();\n                    }\n                }\n            }\n            ActionButton {\n                buttonText: root.device?.connected ? Translation.tr(\"Disconnect\") : Translation.tr(\"Connect\")\n\n                onClicked: {\n                    if (root.device?.connected) {\n                        root.device.disconnect();\n                    } else {\n                        root.device.connect();\n                    }\n                }\n            }\n        }\n        Item {\n            Layout.fillHeight: true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/bluetoothDevices/BluetoothDialog.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell.Io\nimport Quickshell.Bluetooth\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nWindowDialog {\n    id: root\n    backgroundHeight: 600\n\n    WindowDialogTitle {\n        text: Translation.tr(\"Bluetooth devices\")\n    }\n    WindowDialogSeparator {\n        visible: !(Bluetooth.defaultAdapter?.discovering ?? false)\n    }\n    StyledIndeterminateProgressBar {\n        visible: Bluetooth.defaultAdapter?.discovering ?? false\n        Layout.fillWidth: true\n        Layout.topMargin: -8\n        Layout.bottomMargin: -8\n        Layout.leftMargin: -Appearance.rounding.large\n        Layout.rightMargin: -Appearance.rounding.large\n    }\n    StyledListView {\n        Layout.fillHeight: true\n        Layout.fillWidth: true\n        Layout.topMargin: -15\n        Layout.bottomMargin: -16\n        Layout.leftMargin: -Appearance.rounding.large\n        Layout.rightMargin: -Appearance.rounding.large\n\n        clip: true\n        spacing: 0\n        animateAppearance: false\n\n        model: ScriptModel {\n            values: BluetoothStatus.friendlyDeviceList\n        }\n        delegate: BluetoothDeviceItem {\n            required property BluetoothDevice modelData\n            device: modelData\n            anchors {\n                left: parent?.left\n                right: parent?.right\n            }\n        }\n    }\n    WindowDialogSeparator {}\n    WindowDialogButtonRow {\n        DialogButton {\n            buttonText: Translation.tr(\"Details\")\n            onClicked: {\n                Quickshell.execDetached([\"bash\", \"-c\", `${Config.options.apps.bluetooth}`]);\n                GlobalStates.sidebarRightOpen = false;\n            }\n        }\n\n        Item {\n            Layout.fillWidth: true\n        }\n\n        DialogButton {\n            buttonText: Translation.tr(\"Done\")\n            onClicked: root.dismiss()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/CalendarDayButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nRippleButton {\n    id: button\n    property string day\n    property int isToday\n    property bool bold\n\n    Layout.fillWidth: false\n    Layout.fillHeight: false\n    implicitWidth: 38; \n    implicitHeight: 38;\n\n    toggled: (isToday == 1)\n    buttonRadius: Appearance.rounding.small\n    \n    contentItem: StyledText {\n        anchors.fill: parent\n        text: day\n        horizontalAlignment: Text.AlignHCenter\n        font.weight: bold ? Font.DemiBold : Font.Normal\n        color: (isToday == 1) ? Appearance.m3colors.m3onPrimary : \n            (isToday == 0) ? Appearance.colors.colOnLayer1 : \n            Appearance.colors.colOutlineVariant\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/CalendarHeaderButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nRippleButton {\n    id: button\n    property string buttonText: \"\"\n    property string tooltipText: \"\"\n    property bool forceCircle: false\n\n    implicitHeight: 30\n    implicitWidth: forceCircle ? implicitHeight : (contentItem.implicitWidth + 10 * 2)\n    Behavior on implicitWidth {\n        SmoothedAnimation {\n            velocity: Appearance.animation.elementMove.velocity\n        }\n    }\n\n    background.anchors.fill: button\n    buttonRadius: Appearance.rounding.full\n    colBackground: Appearance.colors.colLayer2\n    colBackgroundHover: Appearance.colors.colLayer2Hover\n    colRipple: Appearance.colors.colLayer2Active\n\n    contentItem: StyledText {\n        text: buttonText\n        horizontalAlignment: Text.AlignHCenter\n        font.pixelSize: Appearance.font.pixelSize.larger\n        color: Appearance.colors.colOnLayer1\n    }\n\n    StyledToolTip {\n        text: tooltipText\n        extraVisibleCondition: tooltipText.length > 0\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/CalendarWidget.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport \"calendar_layout.js\" as CalendarLayout\nimport QtQuick\nimport QtQuick.Layouts\n\nItem {\n    // Layout.topMargin: 10\n    anchors.topMargin: 10\n    property int monthShift: 0\n    property var viewingDate: CalendarLayout.getDateInXMonthsTime(monthShift)\n    property var calendarLayout: CalendarLayout.getCalendarLayout(viewingDate, monthShift === 0)\n    width: calendarColumn.width\n    implicitHeight: calendarColumn.height + 10 * 2\n\n    Keys.onPressed: (event) => {\n        if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp)\n            && event.modifiers === Qt.NoModifier) {\n            if (event.key === Qt.Key_PageDown) {\n                monthShift++;\n            } else if (event.key === Qt.Key_PageUp) {\n                monthShift--;\n            }\n            event.accepted = true;\n        }\n    }\n    MouseArea {\n        anchors.fill: parent\n        onWheel: (event) => {\n            if (event.angleDelta.y > 0) {\n                monthShift--;\n            } else if (event.angleDelta.y < 0) {\n                monthShift++;\n            }\n        }\n    }\n\n    ColumnLayout {\n        id: calendarColumn\n        anchors.centerIn: parent\n        spacing: 5\n\n        // Calendar header\n        RowLayout {\n            Layout.fillWidth: true\n            spacing: 5\n            CalendarHeaderButton {\n                clip: true\n                buttonText: `${monthShift != 0 ? \"• \" : \"\"}${viewingDate.toLocaleDateString(Qt.locale(), \"MMMM yyyy\")}`\n                tooltipText: (monthShift === 0) ? \"\" : Translation.tr(\"Jump to current month\")\n                downAction: () => {\n                    monthShift = 0;\n                }\n            }\n            Item {\n                Layout.fillWidth: true\n                Layout.fillHeight: false\n            }\n            CalendarHeaderButton {\n                forceCircle: true\n                downAction: () => {\n                    monthShift--;\n                }\n                contentItem: MaterialSymbol {\n                    text: \"chevron_left\"\n                    iconSize: Appearance.font.pixelSize.larger\n                    horizontalAlignment: Text.AlignHCenter\n                    color: Appearance.colors.colOnLayer1\n                }\n            }\n            CalendarHeaderButton {\n                forceCircle: true\n                downAction: () => {\n                    monthShift++;\n                }\n                contentItem: MaterialSymbol {\n                    text: \"chevron_right\"\n                    iconSize: Appearance.font.pixelSize.larger\n                    horizontalAlignment: Text.AlignHCenter\n                    color: Appearance.colors.colOnLayer1\n                }\n            }\n        }\n\n        // Week days row\n        RowLayout {\n            id: weekDaysRow\n            Layout.alignment: Qt.AlignHCenter\n            Layout.fillHeight: false\n            spacing: 5\n            Repeater {\n                model: CalendarLayout.weekDays\n                delegate: CalendarDayButton {\n                    day: Translation.tr(modelData.day)\n                    isToday: modelData.today\n                    bold: true\n                    enabled: false\n                }\n            }\n        }\n\n        // Real week rows\n        Repeater {\n            id: calendarRows\n            // model: calendarLayout\n            model: 6\n            delegate: RowLayout {\n                Layout.alignment: Qt.AlignHCenter\n                Layout.fillHeight: false\n                spacing: 5\n                Repeater {\n                    model: Array(7).fill(modelData)\n                    delegate: CalendarDayButton {\n                        day: calendarLayout[modelData][index].day\n                        isToday: calendarLayout[modelData][index].today\n                    }\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/calendar/calendar_layout.js",
    "content": "const weekDays = [ // MONDAY IS THE FIRST DAY OF THE WEEK :HESRIGHTYOUKNOW:\n    { day: 'Mo', today: 0 },\n    { day: 'Tu', today: 0 },\n    { day: 'We', today: 0 },\n    { day: 'Th', today: 0 },\n    { day: 'Fr', today: 0 },\n    { day: 'Sa', today: 0 },\n    { day: 'Su', today: 0 },\n]\n\nfunction checkLeapYear(year) {\n    return (\n        year % 400 == 0 ||\n        (year % 4 == 0 && year % 100 != 0));\n}\n\nfunction getMonthDays(month, year) {\n    const leapYear = checkLeapYear(year);\n    if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 31;\n    if (month == 2 && leapYear) return 29;\n    if (month == 2 && !leapYear) return 28;\n    return 30;\n}\n\nfunction getNextMonthDays(month, year) {\n    const leapYear = checkLeapYear(year);\n    if (month == 1 && leapYear) return 29;\n    if (month == 1 && !leapYear) return 28;\n    if (month == 12) return 31;\n    if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;\n    return 31;\n}\n\nfunction getPrevMonthDays(month, year) {\n    const leapYear = checkLeapYear(year);\n    if (month == 3 && leapYear) return 29;\n    if (month == 3 && !leapYear) return 28;\n    if (month == 1) return 31;\n    if ((month <= 7 && month % 2 == 1) || (month >= 8 && month % 2 == 0)) return 30;\n    return 31;\n}\n\nfunction getDateInXMonthsTime(x) {\n    var currentDate = new Date(); // Get the current date\n    if (x == 0) return currentDate; // If x is 0, return the current date\n\n    var targetMonth = currentDate.getMonth() + x; // Calculate the target month\n    var targetYear = currentDate.getFullYear(); // Get the current year\n\n    // Adjust the year and month if necessary\n    targetYear += Math.floor(targetMonth / 12);\n    targetMonth = (targetMonth % 12 + 12) % 12;\n\n    // Create a new date object with the target year and month\n    var targetDate = new Date(targetYear, targetMonth, 1);\n\n    // Set the day to the last day of the month to get the desired date\n    // targetDate.setDate(0);\n\n    return targetDate;\n}\n\nfunction getCalendarLayout(dateObject, highlight) {\n    if (!dateObject) dateObject = new Date();\n    const weekday = (dateObject.getDay() + 6) % 7; // MONDAY IS THE FIRST DAY OF THE WEEK\n    const day = dateObject.getDate();\n    const month = dateObject.getMonth() + 1;\n    const year = dateObject.getFullYear();\n    const weekdayOfMonthFirst = (weekday + 35 - (day - 1)) % 7;\n    const daysInMonth = getMonthDays(month, year);\n    const daysInNextMonth = getNextMonthDays(month, year);\n    const daysInPrevMonth = getPrevMonthDays(month, year);\n\n    // Fill\n    var monthDiff = (weekdayOfMonthFirst == 0 ? 0 : -1);\n    var toFill, dim;\n    if (weekdayOfMonthFirst == 0) {\n        toFill = 1;\n        dim = daysInMonth;\n    }\n    else {\n        toFill = (daysInPrevMonth - (weekdayOfMonthFirst - 1));\n        dim = daysInPrevMonth;\n    }\n    var calendar = [...Array(6)].map(() => Array(7));\n    var i = 0, j = 0;\n    while (i < 6 && j < 7) {\n        calendar[i][j] = {\n            \"day\": toFill,\n            \"today\": ((toFill == day && monthDiff == 0 && highlight) ? 1 : (\n                monthDiff == 0 ? 0 : -1\n            ))\n        };\n        // Increment\n        toFill++;\n        if (toFill > dim) { // Next month?\n            monthDiff++;\n            if (monthDiff == 0)\n                dim = daysInMonth;\n            else if (monthDiff == 1)\n                dim = daysInNextMonth;\n            toFill = 1;\n        }\n        // Next tile\n        j++;\n        if (j == 7) {\n            j = 0;\n            i++;\n        }\n\n    }\n    return calendar;\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/nightLight/NightLightDialog.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nWindowDialog {\n    id: root\n    property var screen: root.QsWindow.window?.screen\n    property var brightnessMonitor: Brightness.getMonitorForScreen(screen)\n    backgroundHeight: 700\n\n    WindowDialogTitle {\n        text: Translation.tr(\"Eye protection\")\n    }\n    \n    WindowDialogSectionHeader {\n        text: Translation.tr(\"Night Light\")\n    }\n\n    WindowDialogSeparator {\n        Layout.topMargin: -22\n        Layout.leftMargin: 0\n        Layout.rightMargin: 0\n    }\n\n    Column {\n        id: nightLightColumn\n        Layout.topMargin: -16\n        Layout.fillWidth: true\n\n        ConfigSwitch {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            iconSize: Appearance.font.pixelSize.larger\n            buttonIcon: \"check\"\n            text: Translation.tr(\"Enable now\")\n            checked: Hyprsunset.temperatureActive\n            onCheckedChanged: {\n                Hyprsunset.toggleTemperature(checked)\n            }\n        }\n\n        ConfigSwitch {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            iconSize: Appearance.font.pixelSize.larger\n            buttonIcon: \"night_sight_auto\"\n            text: Translation.tr(\"Automatic\")\n            checked: Config.options.light.night.automatic\n            onCheckedChanged: {\n                Config.options.light.night.automatic = checked;\n            }\n        }\n\n        WindowDialogSlider {\n            anchors {\n                left: parent.left\n                right: parent.right\n                leftMargin: 4\n                rightMargin: 4\n            }\n            text: Translation.tr(\"Intensity\")\n            from: 6500\n            to: 1200\n            stopIndicatorValues: [5000, to]\n            value: Config.options.light.night.colorTemperature\n            onMoved: Config.options.light.night.colorTemperature = value\n            tooltipContent: `${Math.round(value)}K`\n        }\n    }\n\n    WindowDialogSectionHeader {\n        text: Translation.tr(\"Anti-flashbang (experimental)\")\n    }\n\n    WindowDialogSeparator {\n        Layout.topMargin: -22\n        Layout.leftMargin: 0\n        Layout.rightMargin: 0\n    }\n\n    Column {\n        id: antiFlashbangColumn\n        Layout.topMargin: -16\n        Layout.fillWidth: true\n\n        ConfigSwitch {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            iconSize: Appearance.font.pixelSize.larger\n            buttonIcon: \"filter\"\n            text: Translation.tr(\"Content adjustment\")\n            checked: HyprlandAntiFlashbangShader.enabled\n            onCheckedChanged: {\n                if (checked) HyprlandAntiFlashbangShader.enable()\n                else HyprlandAntiFlashbangShader.disable()\n            }\n            StyledToolTip {\n                text: Translation.tr(\"<b>Dims screen content</b> as needed.<br><br>Pros: Immediately responsive<br>Cons: Expensive and can hurt color accuracy<br><br><i>Uses a Hyprland screen shader</i>\")\n            }\n        }\n\n        ConfigSwitch {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n            iconSize: Appearance.font.pixelSize.larger\n            buttonIcon: \"light_mode\"\n            text: Translation.tr(\"Brightness adjustment\")\n            checked: Config.options.light.antiFlashbang.enable\n            onCheckedChanged: {\n                Config.options.light.antiFlashbang.enable = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Adapts the <b>display (physical screen) brightness</b><br><br>Pros: Less expensive, retains colors<br>Cons: Not immediately responsive<br><br><i>Adjusts display brightness after each Hyprland IPC event</i>\")\n            }\n        }\n    }\n\n    WindowDialogSectionHeader {\n        text: Translation.tr(\"Brightness\")\n    }\n\n    WindowDialogSeparator {\n        Layout.topMargin: -22\n        Layout.leftMargin: 0\n        Layout.rightMargin: 0\n    }\n\n    Column {\n        id: brightnessColumn\n        Layout.topMargin: -16\n        Layout.fillWidth: true\n\n        WindowDialogSlider {\n            anchors {\n                left: parent.left\n                right: parent.right\n                leftMargin: 4\n                rightMargin: 4\n            }\n            value: root.brightnessMonitor.brightness\n            onMoved: root.brightnessMonitor.setBrightness(value)\n        }\n    }\n\n    WindowDialogSectionHeader {\n        text: Translation.tr(\"Gamma\")\n    }\n\n    WindowDialogSeparator {\n        Layout.topMargin: -22\n        Layout.leftMargin: 0\n        Layout.rightMargin: 0\n    }\n\n    Column {\n        id: gammaColumn\n        Layout.topMargin: -16\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n\n        WindowDialogSlider {\n            anchors {\n                left: parent.left\n                right: parent.right\n                leftMargin: 4\n                rightMargin: 4\n            }\n            from: Hyprsunset.gammaLowerLimit / 100\n            value: Hyprsunset.gamma / 100\n            onMoved: Hyprsunset.setGamma(value * 100)\n            tooltipContent: `${Math.round(value * 100)}%`\n        }\n    }\n    \n    WindowDialogButtonRow {\n        Layout.fillWidth: true\n\n        Item {\n            Layout.fillWidth: true\n        }\n\n        DialogButton {\n            buttonText: Translation.tr(\"Done\")\n            onClicked: root.dismiss()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/notifications/NotificationList.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nItem {\n    id: root\n\n    NotificationListView { // Scrollable window\n        id: listview\n        anchors.left: parent.left\n        anchors.right: parent.right\n        anchors.top: parent.top\n        anchors.bottom: statusRow.top\n        anchors.bottomMargin: 5\n\n        clip: true\n        layer.enabled: true\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                width: listview.width\n                height: listview.height\n                radius: Appearance.rounding.normal\n            }\n        }\n\n        popup: false\n    }\n\n    // Placeholder when list is empty\n    PagePlaceholder {\n        shown: Notifications.list.length === 0\n        icon: \"notifications_active\"\n        description: Translation.tr(\"Nothing\")\n        shape: MaterialShape.Shape.Ghostish\n        descriptionHorizontalAlignment: Text.AlignHCenter\n    }\n\n    ButtonGroup {\n        id: statusRow\n        anchors {\n            left: parent.left\n            right: parent.right\n            bottom: parent.bottom\n        }\n\n        NotificationStatusButton {\n            Layout.fillWidth: false\n            buttonIcon: \"notifications_paused\"\n            toggled: Notifications.silent\n            onClicked: () => {\n                Notifications.silent = !Notifications.silent;\n            }\n        }\n        NotificationStatusButton {\n            enabled: false\n            Layout.fillWidth: true\n            buttonText: Translation.tr(\"%1 notifications\").arg(Notifications.list.length)\n        }\n        NotificationStatusButton {\n            Layout.fillWidth: false\n            buttonIcon: \"delete_sweep\"\n            onClicked: () => {\n                Notifications.discardAllNotifications()\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/notifications/NotificationStatusButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\n\nGroupButton {\n    id: button\n    property string buttonIcon: \"\"\n    property string buttonText: \"\"\n\n    baseHeight: 36\n    baseWidth: content.implicitWidth + 46\n    clickedWidth: baseWidth + 6\n\n    buttonRadius: baseHeight / 2\n    buttonRadiusPressed: Appearance.rounding.small\n    colBackground: Appearance.colors.colLayer2\n    colBackgroundHover: Appearance.colors.colLayer2Hover\n    colBackgroundActive: Appearance.colors.colLayer2Active\n    property color colText: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1\n\n    contentItem: Item {\n        id: content\n        anchors.fill: parent\n        implicitWidth: contentRowLayout.implicitWidth\n        implicitHeight: contentRowLayout.implicitHeight\n        RowLayout {\n            id: contentRowLayout\n            anchors.centerIn: parent\n            spacing: 5\n            MaterialSymbol {\n                visible: buttonIcon !== \"\"\n                text: buttonIcon\n                iconSize: Appearance.font.pixelSize.huge\n                color: button.colText\n            }\n            StyledText {\n                visible: buttonText !== \"\"\n                text: buttonText\n                font.pixelSize: Appearance.font.pixelSize.small\n                color: button.colText\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/pomodoro/PomodoroTimer.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: root\n\n    implicitHeight: contentColumn.implicitHeight\n    implicitWidth: contentColumn.implicitWidth\n\n    ColumnLayout {\n        id: contentColumn\n        anchors.fill: parent\n        spacing: 0\n\n        // The Pomodoro timer circle\n        CircularProgress {\n            Layout.alignment: Qt.AlignHCenter\n            lineWidth: 8\n            value: {\n                return TimerService.pomodoroSecondsLeft / TimerService.pomodoroLapDuration;\n            }\n            implicitSize: 200\n            enableAnimation: true\n\n            ColumnLayout {\n                anchors.centerIn: parent\n                spacing: 0\n\n                StyledText {\n                    Layout.alignment: Qt.AlignHCenter\n                    text: {\n                        let minutes = Math.floor(TimerService.pomodoroSecondsLeft / 60).toString().padStart(2, '0');\n                        let seconds = Math.floor(TimerService.pomodoroSecondsLeft % 60).toString().padStart(2, '0');\n                        return `${minutes}:${seconds}`;\n                    }\n                    font.pixelSize: 40\n                    color: Appearance.m3colors.m3onSurface\n                }\n                StyledText {\n                    Layout.alignment: Qt.AlignHCenter\n                    text: TimerService.pomodoroLongBreak ? Translation.tr(\"Long break\") : TimerService.pomodoroBreak ? Translation.tr(\"Break\") : Translation.tr(\"Focus\")\n                    font.pixelSize: Appearance.font.pixelSize.normal\n                    color: Appearance.colors.colSubtext\n                }\n            }\n\n            Rectangle {\n                radius: Appearance.rounding.full\n                color: Appearance.colors.colLayer2\n                \n                anchors {\n                    right: parent.right\n                    bottom: parent.bottom\n                }\n                implicitWidth: 36\n                implicitHeight: implicitWidth\n\n                StyledText {\n                    id: cycleText\n                    anchors.centerIn: parent\n                    color: Appearance.colors.colOnLayer2\n                    text: TimerService.pomodoroCycle + 1\n                }\n            }\n        }\n\n        // The Start/Stop and Reset buttons\n        RowLayout {\n            Layout.alignment: Qt.AlignHCenter\n            spacing: 10\n\n            RippleButton {\n                contentItem: StyledText {\n                    anchors.centerIn: parent\n                    horizontalAlignment: Text.AlignHCenter\n                    text: TimerService.pomodoroRunning ? Translation.tr(\"Pause\") : (TimerService.pomodoroSecondsLeft === TimerService.focusTime) ? Translation.tr(\"Start\") : Translation.tr(\"Resume\")\n                    color: TimerService.pomodoroRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary\n                }\n                implicitHeight: 35\n                implicitWidth: 90\n                font.pixelSize: Appearance.font.pixelSize.larger\n                onClicked: TimerService.togglePomodoro()\n                colBackground: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary\n                colBackgroundHover: TimerService.pomodoroRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary\n            }\n\n            RippleButton {\n                implicitHeight: 35\n                implicitWidth: 90\n\n                onClicked: TimerService.resetPomodoro()\n                enabled: (TimerService.pomodoroSecondsLeft < TimerService.pomodoroLapDuration) || TimerService.pomodoroCycle > 0 || TimerService.pomodoroBreak\n\n                font.pixelSize: Appearance.font.pixelSize.larger\n                colBackground: Appearance.colors.colErrorContainer\n                colBackgroundHover: Appearance.colors.colErrorContainerHover\n                colRipple: Appearance.colors.colErrorContainerActive\n\n                contentItem: StyledText {\n                    anchors.centerIn: parent\n                    horizontalAlignment: Text.AlignHCenter\n                    text: Translation.tr(\"Reset\")\n                    color: Appearance.colors.colOnErrorContainer\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/pomodoro/PomodoroWidget.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    property var tabButtonList: [\n        {\"name\": Translation.tr(\"Pomodoro\"), \"icon\": \"search_activity\"},\n        {\"name\": Translation.tr(\"Stopwatch\"), \"icon\": \"timer\"}\n    ]\n\n    // These are keybinds for stopwatch and pomodoro\n    Keys.onPressed: (event) => {\n        if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) { // Switch tabs\n            if (event.key === Qt.Key_PageDown) {\n                tabBar.incrementCurrentIndex();\n            } else if (event.key === Qt.Key_PageUp) {\n                tabBar.decrementCurrentIndex();\n            }\n            event.accepted = true\n        } else if (event.key === Qt.Key_Space || event.key === Qt.Key_S) { // Pause/resume with Space or S\n            if (tabBar.currentIndex === 0) {\n                TimerService.togglePomodoro()\n            } else {\n                TimerService.toggleStopwatch()\n            }\n            event.accepted = true\n        } else if (event.key === Qt.Key_R) { // Reset with R\n            if (tabBar.currentIndex === 0) {\n                TimerService.resetPomodoro()\n            } else {\n                TimerService.stopwatchReset()\n            }\n            event.accepted = true\n        } else if (event.key === Qt.Key_L) { // Record lap with L\n            TimerService.stopwatchRecordLap()\n            event.accepted = true\n        }\n    }\n\n    ColumnLayout {\n        anchors.fill: parent\n        spacing: 0\n\n        SecondaryTabBar {\n            id: tabBar\n            currentIndex: swipeView.currentIndex\n\n            Repeater {\n                model: root.tabButtonList\n                delegate: SecondaryTabButton {\n                    buttonText: modelData.name\n                    buttonIcon: modelData.icon\n                }\n            }\n        }\n\n        SwipeView {\n            id: swipeView\n            Layout.topMargin: 10\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            spacing: 10\n            clip: true\n            currentIndex: tabBar.currentIndex\n\n            // Tabs\n            PomodoroTimer {}\n            Stopwatch {}\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/pomodoro/Stopwatch.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: stopwatchTab\n    Layout.fillWidth: true\n    Layout.fillHeight: true\n\n    Item {\n        anchors {\n            fill: parent\n            topMargin: 8\n            leftMargin: 16\n            rightMargin: 16\n        }\n\n        RowLayout { // Elapsed\n            id: elapsedIndicator\n            \n            anchors {\n                top: undefined\n                verticalCenter: parent.verticalCenter\n                left: controlButtons.left\n                leftMargin: 6\n            }\n\n            states: State {\n                name: \"hasLaps\"\n                when: TimerService.stopwatchLaps.length > 0\n                AnchorChanges {\n                    target: elapsedIndicator\n                    anchors.top: parent.top\n                    anchors.verticalCenter: undefined\n                    anchors.left: controlButtons.left\n                }\n            }\n\n            transitions: Transition {\n                AnchorAnimation {\n                    duration: Appearance.animation.elementMoveFast.duration\n                    easing.type: Appearance.animation.elementMoveFast.type\n                    easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                }\n            }\n\n            spacing: 0\n            StyledText {\n                // Layout.preferredWidth: elapsedIndicator.width * 0.6 // Prevent shakiness\n                font.pixelSize: 40\n                color: Appearance.m3colors.m3onSurface\n                text: {\n                    let totalSeconds = Math.floor(TimerService.stopwatchTime) / 100\n                    let minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')\n                    let seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')\n                    return `${minutes}:${seconds}`\n                }\n            }\n            StyledText {\n                Layout.fillWidth: true\n                font.pixelSize: 40\n                color: Appearance.colors.colSubtext\n                text: {\n                    return `:<sub>${(Math.floor(TimerService.stopwatchTime) % 100).toString().padStart(2, '0')}</sub>`\n                }\n            }\n        }\n\n        // Laps\n        StyledListView {\n            id: lapsList\n            anchors {\n                top: elapsedIndicator.bottom\n                bottom: controlButtons.top\n                left: parent.left\n                right: parent.right\n                topMargin: 16\n                bottomMargin: 16\n            }\n            spacing: 4\n            clip: true\n            popin: true\n\n            model: ScriptModel {\n                values: TimerService.stopwatchLaps.map((v, i, arr) => arr[arr.length - 1 - i])\n            }\n\n            delegate: Rectangle {\n                id: lapItem\n                required property int index\n                required property var modelData\n                property var horizontalPadding: 10\n                property var verticalPadding: 6\n                width: lapsList.width\n                implicitHeight: lapRow.implicitHeight + verticalPadding * 2\n                implicitWidth: lapRow.implicitWidth + horizontalPadding * 2\n                color: Appearance.colors.colLayer2\n                radius: Appearance.rounding.small\n\n                RowLayout {\n                    id: lapRow\n                    anchors {\n                        fill: parent\n                        leftMargin: lapItem.horizontalPadding\n                        rightMargin: lapItem.horizontalPadding\n                        topMargin: lapItem.verticalPadding\n                        bottomMargin: lapItem.verticalPadding\n                    }\n\n                    StyledText {\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        color: Appearance.colors.colSubtext\n                        text: `${TimerService.stopwatchLaps.length - lapItem.index}.`\n                    }\n\n                    StyledText {\n                        font.pixelSize: Appearance.font.pixelSize.small\n                        text: {\n                            const lapTime = lapItem.modelData\n                            const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')\n                            const totalSeconds = Math.floor(lapTime) / 100\n                            const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')\n                            const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')\n                            return `${minutes}:${seconds}.${_10ms}`\n                        }\n                    }\n\n                    Item { Layout.fillWidth: true }\n\n                    StyledText {\n                        font.pixelSize: Appearance.font.pixelSize.smaller\n                        color: Appearance.colors.colPrimary\n                        text: {\n                            const originalIndex = TimerService.stopwatchLaps.length - lapItem.index - 1\n                            const lastTime = originalIndex > 0 ? TimerService.stopwatchLaps[originalIndex - 1] : 0\n                            const lapTime = lapItem.modelData - lastTime\n                            const _10ms = (Math.floor(lapTime) % 100).toString().padStart(2, '0')\n                            const totalSeconds = Math.floor(lapTime) / 100\n                            const minutes = Math.floor(totalSeconds / 60).toString().padStart(2, '0')\n                            const seconds = Math.floor(totalSeconds % 60).toString().padStart(2, '0')\n                            return `+${minutes == \"00\" ? \"\" : minutes + \":\"}${seconds}.${_10ms}`\n                        }\n                    }\n                }\n            }\n        }\n\n        RowLayout {\n            id: controlButtons\n            anchors {\n                horizontalCenter: parent.horizontalCenter\n                bottom: parent.bottom\n                bottomMargin: 6\n            }\n            spacing: 4\n\n            RippleButton {\n                Layout.preferredHeight: 35\n                Layout.preferredWidth: 90\n                font.pixelSize: Appearance.font.pixelSize.larger\n\n                onClicked: {\n                    TimerService.toggleStopwatch()\n                }\n\n                colBackground: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainer : Appearance.colors.colPrimary \n                colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerHover : Appearance.colors.colPrimaryHover \n                colRipple: TimerService.stopwatchRunning ? Appearance.colors.colSecondaryContainerActive : Appearance.colors.colPrimaryActive \n\n                contentItem: StyledText {\n                    horizontalAlignment: Text.AlignHCenter\n                    color: TimerService.stopwatchRunning ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnPrimary\n                    text: TimerService.stopwatchRunning ? Translation.tr(\"Pause\") : TimerService.stopwatchTime === 0 ? Translation.tr(\"Start\") : Translation.tr(\"Resume\")\n                }\n            }\n\n            RippleButton {\n                implicitHeight: 35\n                implicitWidth: 90\n                font.pixelSize: Appearance.font.pixelSize.larger\n\n                onClicked: {\n                    if (TimerService.stopwatchRunning) \n                        TimerService.stopwatchRecordLap()\n                    else \n                        TimerService.stopwatchReset()\n                }\n                enabled: TimerService.stopwatchTime > 0 || Persistent.states.timer.stopwatch.laps.length > 0\n\n                colBackground: TimerService.stopwatchRunning ? Appearance.colors.colLayer2 : Appearance.colors.colErrorContainer\n                colBackgroundHover: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Hover : Appearance.colors.colErrorContainerHover\n                colRipple: TimerService.stopwatchRunning ? Appearance.colors.colLayer2Active : Appearance.colors.colErrorContainerActive\n\n                contentItem: StyledText {\n                    horizontalAlignment: Text.AlignHCenter\n                    text: TimerService.stopwatchRunning ? Translation.tr(\"Lap\") : Translation.tr(\"Reset\")\n                    color: TimerService.stopwatchRunning ? Appearance.colors.colOnLayer2 : Appearance.colors.colOnErrorContainer\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/AbstractQuickPanel.qml",
    "content": "import QtQuick\nimport qs.modules.common\n\nRectangle {\n    id: root\n\n    radius: Appearance.rounding.normal\n    color: Appearance.colors.colLayer1\n\n    signal openAudioOutputDialog()\n    signal openAudioInputDialog()\n    signal openBluetoothDialog()\n    signal openNightLightDialog()\n    signal openWifiDialog()\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/AndroidQuickPanel.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Bluetooth\n\nimport qs.modules.ii.sidebarRight.quickToggles.androidStyle\n\nAbstractQuickPanel {\n    id: root\n    property bool editMode: false\n    Layout.fillWidth: true\n\n    // Sizes\n    implicitHeight: (editMode ? contentItem.implicitHeight : usedRows.implicitHeight) + root.padding * 2\n    Behavior on implicitHeight {\n        animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n    }\n    property real spacing: 6\n    property real padding: 6\n    readonly property real baseCellWidth: {\n        // This is the wrong calculation, but it looks correct in reality???\n        // (theoretically spacing should be multiplied by 1 column less)\n        const availableWidth = root.width - (root.padding * 2) - (root.spacing * (root.columns))\n        return availableWidth / root.columns\n    }\n    readonly property real baseCellHeight: 56\n\n    // Toggles\n    readonly property list<string> availableToggleTypes: [\"network\", \"bluetooth\", \"idleInhibitor\", \"easyEffects\", \"nightLight\", \"darkMode\", \"cloudflareWarp\", \"gameMode\", \"screenSnip\", \"colorPicker\", \"onScreenKeyboard\", \"mic\", \"audio\", \"notifications\", \"powerProfile\",\"musicRecognition\", \"antiFlashbang\"]\n    readonly property int columns: Config.options.sidebar.quickToggles.android.columns\n    readonly property list<var> toggles: Config.ready ? Config.options.sidebar.quickToggles.android.toggles : []\n    readonly property list<var> toggleRows: toggleRowsForList(toggles)\n    readonly property list<var> unusedToggles: {\n        const types = availableToggleTypes.filter(type => !toggles.some(toggle => (toggle && toggle.type === type)))\n        return types.map(type => { return { type: type, size: 1 } })\n    }\n    readonly property list<var> unusedToggleRows: toggleRowsForList(unusedToggles)\n\n    function toggleRowsForList(togglesList) {\n        var rows = [];\n        var row = [];\n        var totalSize = 0; // Total cols taken in current row\n        for (var i = 0; i < togglesList.length; i++) {\n            if (!togglesList[i]) continue;\n            if (totalSize + togglesList[i].size > columns) {\n                rows.push(row);\n                row = [];\n                totalSize = 0;\n            }\n            row.push(togglesList[i]);\n            totalSize += togglesList[i].size;\n        }\n        if (row.length > 0) {\n            rows.push(row);\n        }\n        return rows;\n    }\n\n    Column {\n        id: contentItem\n        anchors {\n            fill: parent\n            margins: root.padding\n        }\n        spacing: 12\n        \n        Column {\n            id: usedRows\n            spacing: root.spacing\n\n            Repeater {\n                id: usedRowsRepeater\n                model: ScriptModel {\n                    values: Array(root.toggleRows.length)\n                }\n                delegate: ButtonGroup {\n                    id: toggleRow\n                    required property int index\n                    property var modelData: root.toggleRows[index]\n                    property int startingIndex: {\n                        const rows = root.toggleRows;\n                        let sum = 0;\n                        for (let i = 0; i < index; i++) {\n                            sum += rows[i].length;\n                        }\n                        return sum;\n                    }\n                    spacing: root.spacing\n\n                    Repeater {\n                        model: ScriptModel {\n                            values: toggleRow?.modelData ?? []\n                            objectProp: \"type\"\n                        }\n                        delegate: AndroidToggleDelegateChooser {\n                            startingIndex: toggleRow.startingIndex\n                            editMode: root.editMode\n                            baseCellWidth: root.baseCellWidth\n                            baseCellHeight: root.baseCellHeight\n                            spacing: root.spacing\n                            onOpenAudioOutputDialog: root.openAudioOutputDialog()\n                            onOpenAudioInputDialog: root.openAudioInputDialog()\n                            onOpenBluetoothDialog: root.openBluetoothDialog()\n                            onOpenNightLightDialog: root.openNightLightDialog()\n                            onOpenWifiDialog: root.openWifiDialog()\n                        }\n                    }\n                }\n            }\n        }\n\n        FadeLoader {\n            shown: root.editMode\n            anchors {\n                left: parent.left\n                right: parent.right\n                leftMargin: root.baseCellHeight / 2\n                rightMargin: root.baseCellHeight / 2\n            }\n            sourceComponent: Rectangle {\n                implicitHeight: 1\n                color: Appearance.colors.colOutlineVariant\n            }\n        }\n\n        FadeLoader {\n            shown: root.editMode\n            sourceComponent: Column {\n                id: unusedRows\n                spacing: root.spacing\n\n                Repeater {\n                    model: ScriptModel {\n                        values: Array(root.unusedToggleRows.length)\n                    }\n                    delegate: ButtonGroup {\n                        id: unusedToggleRow\n                        required property int index\n                        property var modelData: root.unusedToggleRows[index]\n                        spacing: root.spacing\n\n                        Repeater {\n                            model: ScriptModel {\n                                values: unusedToggleRow?.modelData ?? []\n                                objectProp: \"type\"\n                            }\n                            delegate: AndroidToggleDelegateChooser {\n                                startingIndex: -1\n                                editMode: root.editMode\n                                baseCellWidth: root.baseCellWidth\n                                baseCellHeight: root.baseCellHeight\n                                spacing: root.spacing\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/ClassicQuickPanel.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell.Bluetooth\n\nimport qs.modules.ii.sidebarRight.quickToggles.classicStyle\n\nAbstractQuickPanel {\n    id: root\n    Layout.alignment: Qt.AlignHCenter\n    implicitWidth: buttonGroup.implicitWidth\n    implicitHeight: buttonGroup.implicitHeight\n    color: \"transparent\"\n\n    ButtonGroup {\n        id: buttonGroup\n        spacing: 5\n        padding: 5\n        color: Appearance.colors.colLayer1\n\n        NetworkToggle {\n            altAction: () => {\n                root.openWifiDialog();\n            }\n        }\n        BluetoothToggle {\n            altAction: () => {\n                root.openBluetoothDialog();\n            }\n        }\n        NightLight {}\n        GameMode {}\n        IdleInhibitor {}\n        EasyEffectsToggle {}\n        CloudflareWarp {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidAntiFlashbangToggle.qml",
    "content": "import qs.modules.common.models.quickToggles\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    id: root\n\n    toggleModel: AntiFlashbangToggle {}\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidAudioToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    id: root\n\n    toggleModel: AudioToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidBluetoothToggle.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\nimport Quickshell\nimport Quickshell.Bluetooth\n\nAndroidQuickToggleButton {\n    id: root\n    \n    toggleModel: BluetoothToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidCloudflareWarpToggle.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nAndroidQuickToggleButton {\n    id: root\n\n    toggleModel: CloudflareWarpToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidColorPickerToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    id: root\n\n    toggleModel: ColorPickerToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidDarkModeToggle.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    toggleModel: DarkModeToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidEasyEffectsToggle.qml",
    "content": "import qs\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    toggleModel: EasyEffectsToggle {}\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidGameModeToggle.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nAndroidQuickToggleButton {\n    toggleModel: GameModeToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidIdleInhibitorToggle.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\n\nAndroidQuickToggleButton {\n    toggleModel: IdleInhibitorToggle {}\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidMicToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    toggleModel: MicToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidMusicRecognition.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.services\n\n\nAndroidQuickToggleButton {\n    toggleModel: MusicRecognitionToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidNetworkToggle.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\n\nAndroidQuickToggleButton {\n    id: root\n    \n    toggleModel: NetworkToggle {}\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidNightLightToggle.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    toggleModel: NightLightToggle {}\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidNotificationToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    toggleModel: NotificationToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidOnScreenKeyboardToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\n\nAndroidQuickToggleButton {\n    toggleModel: OnScreenKeyboardToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidPowerProfileToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.UPower\n\nAndroidQuickToggleButton {\n    toggleModel: PowerProfilesToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidQuickToggleButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nGroupButton {\n    id: root\n    \n    // Info to be passed to by repeater\n    required property int buttonIndex\n    required property var buttonData\n    required property bool expandedSize\n    required property real baseCellWidth\n    required property real baseCellHeight\n    required property real cellSpacing\n    required property int cellSize\n\n    // Signals\n    signal openMenu()\n\n    // Declared in specific toggles\n    property QuickToggleModel toggleModel\n    property string name: toggleModel?.name ?? \"\"\n    property string statusText: (toggleModel?.hasStatusText) ? (toggleModel?.statusText || (toggled ? Translation.tr(\"Active\") : Translation.tr(\"Inactive\"))) : \"\"\n    property string tooltipText: toggleModel?.tooltipText ?? \"\"\n    property string buttonIcon: toggleModel?.icon ?? \"close\"\n    property bool available: toggleModel?.available ?? true\n    toggled: toggleModel?.toggled ?? false\n    property var mainAction: toggleModel?.mainAction ?? null\n    altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null)\n\n    // Edit mode state\n    property bool editMode: false\n\n    // Sizing shenanigans\n    baseWidth: root.baseCellWidth * cellSize + cellSpacing * (cellSize - 1)\n    baseHeight: root.baseCellHeight\n    enableImplicitWidthAnimation: !editMode && root.mouseArea.containsMouse\n    enableImplicitHeightAnimation: !editMode && root.mouseArea.containsMouse\n    Behavior on baseWidth {\n        animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n    }\n    Behavior on baseHeight {\n        animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n    }\n    opacity: 0\n    Component.onCompleted: {\n        opacity = 1\n    }\n    Behavior on opacity {\n        animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n    }\n\n    enabled: available || editMode\n    padding: 6\n    horizontalPadding: padding\n    verticalPadding: padding\n\n    colBackground: Appearance.colors.colLayer2\n    colBackgroundToggled: (altAction && expandedSize) ? Appearance.colors.colLayer2 : Appearance.colors.colPrimary\n    colBackgroundToggledHover: (altAction && expandedSize) ? Appearance.colors.colLayer2Hover : Appearance.colors.colPrimaryHover\n    colBackgroundToggledActive: (altAction && expandedSize) ? Appearance.colors.colLayer2Active : Appearance.colors.colPrimaryActive\n    buttonRadius: toggled ? Appearance.rounding.large : height / 2\n    buttonRadiusPressed: Appearance.rounding.normal\n    property color colText: (toggled && !(altAction && expandedSize) && enabled) ? Appearance.colors.colOnPrimary : ColorUtils.transparentize(Appearance.colors.colOnLayer2, enabled ? 0 : 0.7)\n    property color colIcon: expandedSize ? ((root.toggled) ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer3) : colText\n\n    onClicked: {\n        if (root.expandedSize && root.altAction) root.altAction();\n        else root.mainAction();\n    }\n\n    contentItem: RowLayout {\n        id: contentItem\n        spacing: 4\n        anchors {\n            centerIn: root.expandedSize ? undefined : parent\n            fill: root.expandedSize ? parent : undefined\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n        }\n\n        // Icon\n        MouseArea {\n            id: iconMouseArea\n            hoverEnabled: true\n            acceptedButtons: (root.expandedSize && root.altAction) ? Qt.LeftButton : Qt.NoButton\n            Layout.alignment: Qt.AlignHCenter\n            Layout.fillHeight: true\n            Layout.topMargin: root.verticalPadding\n            Layout.bottomMargin: root.verticalPadding\n            implicitHeight: iconBackground.implicitHeight\n            implicitWidth: iconBackground.implicitWidth\n            cursorShape: Qt.PointingHandCursor\n\n            onClicked: root.mainAction()\n\n            Rectangle {\n                id: iconBackground\n                anchors.fill: parent\n                implicitWidth: height\n                radius: root.radius - root.verticalPadding\n                color: {\n                    const baseColor = root.toggled ? Appearance.colors.colPrimary : Appearance.colors.colLayer3\n                    const transparentizeAmount = (root.altAction && root.expandedSize) ? 0 : 1\n                    return ColorUtils.transparentize(baseColor, transparentizeAmount)\n                }\n\n                Behavior on radius {\n                    animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n                }\n                Behavior on color {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n\n                MaterialSymbol {\n                    anchors.centerIn: parent\n                    fill: root.toggled ? 1 : 0\n                    iconSize: root.expandedSize ? 22 : 24\n                    color: root.colIcon\n                    text: root.buttonIcon\n                }\n\n                // State layer\n                Loader {\n                    anchors.fill: parent\n                    active: (root.expandedSize && root.altAction)\n                    sourceComponent: Rectangle {\n                        radius: iconBackground.radius\n                        color: ColorUtils.transparentize(root.colIcon, iconMouseArea.containsPress ? 0.88 : iconMouseArea.containsMouse ? 0.95 : 1)\n                        Behavior on color {\n                            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                        }\n                    }\n                }\n            }\n        }\n\n        // Text column for expanded size\n        Loader {\n            Layout.alignment: Qt.AlignVCenter\n            Layout.fillWidth: true\n            visible: root.expandedSize\n            active: visible\n            sourceComponent: Column {\n                spacing: -2\n\n                StyledText {\n                    anchors {\n                        left: parent.left\n                        right: parent.right\n                    }\n                    font.pixelSize: Appearance.font.pixelSize.smallie\n                    font.weight: 600\n                    color: root.colText\n                    elide: Text.ElideRight\n                    text: root.name\n                }\n\n                StyledText {\n                    visible: root.statusText\n                    anchors {\n                        left: parent.left\n                        right: parent.right\n                    }\n                    font {\n                        pixelSize: Appearance.font.pixelSize.smaller\n                        weight: 100\n                    }\n                    color: root.colText\n                    elide: Text.ElideRight\n                    text: root.statusText\n                }\n            }\n        }\n    }\n\n    MouseArea { // Blocking MouseArea for edit interactions\n        id: editModeInteraction\n        visible: root.editMode\n        anchors.fill: parent\n        cursorShape: Qt.PointingHandCursor\n        hoverEnabled: true\n        acceptedButtons: Qt.AllButtons\n\n        function toggleEnabled() {\n            const index = root.buttonIndex;\n            const toggleList = Config.options.sidebar.quickToggles.android.toggles;\n            const buttonType = root.buttonData.type;\n            if (!toggleList.find(toggle => toggle.type === buttonType)) {\n                toggleList.push({ type: buttonType, size: 1 });\n            } else {\n                toggleList.splice(index, 1);\n            }\n        }\n\n        function toggleSize() {\n            const index = root.buttonIndex;\n            const toggleList = Config.options.sidebar.quickToggles.android.toggles;\n            const buttonType = root.buttonData.type;\n            if (!toggleList.find(toggle => toggle.type === buttonType)) return;\n            toggleList[index].size = 3 - toggleList[index].size; // Alternate between 1 and 2\n        }\n\n        function movePositionBy(offset) {\n            const index = root.buttonIndex;\n            const toggleList = Config.options.sidebar.quickToggles.android.toggles;\n            const buttonType = root.buttonData.type;\n            const targetIndex = index + offset;\n            if (!toggleList.find(toggle => toggle.type === buttonType)) return;\n            if (targetIndex < 0 || targetIndex >= toggleList.length) return;\n            const temp = toggleList[index];\n            toggleList[index] = toggleList[targetIndex];\n            toggleList[targetIndex] = temp;\n        }\n\n        onReleased: (event) => {\n            if (event.button === Qt.LeftButton)\n                toggleEnabled();\n        }\n        onPressed: (event) => {\n            if (event.button === Qt.RightButton) toggleSize();\n        }\n        onPressAndHold: (event) => { // Also toggle size\n            toggleSize();\n        }\n        onWheel: (event) => {\n            const index = root.buttonIndex;\n            const toggleList = Config.options.sidebar.quickToggles.android.toggles;\n            const buttonType = root.buttonData.type;\n            if (event.angleDelta.y < 0) { // Move to right\n                movePositionBy(1);\n            } else if (event.angleDelta.y > 0) { // Move to left\n                movePositionBy(-1);\n            }\n            event.accepted = true;\n        }\n    }\n\n    StyledToolTip {\n        extraVisibleCondition: root.tooltipText !== \"\"\n        text: root.tooltipText\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidScreenSnipToggle.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\n\nAndroidQuickToggleButton {\n    toggleModel: ScreenSnipToggle {}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/androidStyle/AndroidToggleDelegateChooser.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Bluetooth\n\nDelegateChooser {\n    id: root\n    property bool editMode: false\n    required property real baseCellWidth\n    required property real baseCellHeight\n    required property real spacing\n    required property int startingIndex\n    signal openAudioOutputDialog()\n    signal openAudioInputDialog()\n    signal openBluetoothDialog()\n    signal openNightLightDialog()\n    signal openWifiDialog()\n\n    role: \"type\"\n\n    DelegateChoice { roleValue: \"antiFlashbang\"; AndroidAntiFlashbangToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n        onOpenMenu: {\n            root.openNightLightDialog()\n        }\n    } }\n\n    DelegateChoice { roleValue: \"audio\"; AndroidAudioToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n        onOpenMenu: {\n            root.openAudioOutputDialog()\n        }\n    } }\n\n    DelegateChoice { roleValue: \"bluetooth\"; AndroidBluetoothToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n        onOpenMenu: {\n            root.openBluetoothDialog()\n        }\n    } }\n\n    DelegateChoice { roleValue: \"cloudflareWarp\"; AndroidCloudflareWarpToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"colorPicker\"; AndroidColorPickerToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"darkMode\"; AndroidDarkModeToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"easyEffects\"; AndroidEasyEffectsToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"gameMode\"; AndroidGameModeToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"idleInhibitor\"; AndroidIdleInhibitorToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"mic\"; AndroidMicToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n        onOpenMenu: {\n            root.openAudioInputDialog()\n        }\n    } }\n\n    DelegateChoice { roleValue: \"musicRecognition\"; AndroidMusicRecognition {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"network\"; AndroidNetworkToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n        onOpenMenu: {\n            root.openWifiDialog()\n        }\n    } }\n\n    DelegateChoice { roleValue: \"nightLight\"; AndroidNightLightToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n        onOpenMenu: {\n            root.openNightLightDialog()\n        }\n    } }\n\n    DelegateChoice { roleValue: \"notifications\"; AndroidNotificationToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"onScreenKeyboard\"; AndroidOnScreenKeyboardToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"powerProfile\"; AndroidPowerProfileToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n\n    DelegateChoice { roleValue: \"screenSnip\"; AndroidScreenSnipToggle {\n        required property int index\n        required property var modelData\n        buttonIndex: root.startingIndex + index\n        buttonData: modelData\n        editMode: root.editMode\n        expandedSize: modelData.size > 1\n        baseCellWidth: root.baseCellWidth\n        baseCellHeight: root.baseCellHeight\n        cellSpacing: root.spacing\n        cellSize: modelData.size\n    } }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/BluetoothToggle.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Bluetooth\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nQuickToggleButton {\n    id: root\n    visible: BluetoothStatus.available\n    toggled: BluetoothStatus.enabled\n    buttonIcon: BluetoothStatus.connected ? \"bluetooth_connected\" : BluetoothStatus.enabled ? \"bluetooth\" : \"bluetooth_disabled\"\n    onClicked: {\n        Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter?.enabled\n    }\n    altAction: () => {\n        Quickshell.execDetached([\"bash\", \"-c\", `${Config.options.apps.bluetooth}`])\n        GlobalStates.sidebarRightOpen = false\n    }\n    StyledToolTip {\n        text: Translation.tr(\"%1 | Right-click to configure\").arg(\n            (BluetoothStatus.firstActiveDevice?.name ?? Translation.tr(\"Bluetooth\"))\n            + (BluetoothStatus.activeDeviceCount > 1 ? ` +${BluetoothStatus.activeDeviceCount - 1}` : \"\")\n            )\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/CloudflareWarp.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport Quickshell.Io\nimport Quickshell\n\nQuickToggleButton {\n    id: root\n    toggled: false\n    visible: false\n    \n    contentItem: CustomIcon {\n        id: distroIcon\n        source: 'cloudflare-dns-symbolic'\n\n        anchors.centerIn: parent\n        width: 16\n        height: 16\n        colorize: true\n        color: root.toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n    onClicked: {\n        if (toggled) {\n            root.toggled = false\n            Quickshell.execDetached([\"warp-cli\", \"disconnect\"])\n        } else {\n            root.toggled = true\n            Quickshell.execDetached([\"warp-cli\", \"connect\"])\n        }\n    }\n\n    Process {\n        id: connectProc\n        command: [\"warp-cli\", \"connect\"]\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode !== 0) {\n                Quickshell.execDetached([\"notify-send\", \n                    Translation.tr(\"Cloudflare WARP\"), \n                    Translation.tr(\"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\")\n                    , \"-a\", \"Shell\"\n                ])\n            }\n        }\n    }\n\n    Process {\n        id: registrationProc\n        command: [\"warp-cli\", \"registration\", \"new\"]\n        onExited: (exitCode, exitStatus) => {\n            console.log(\"Warp registration exited with code and status:\", exitCode, exitStatus)\n            if (exitCode === 0) {\n                connectProc.running = true\n            } else {\n                Quickshell.execDetached([\"notify-send\", \n                    Translation.tr(\"Cloudflare WARP\"), \n                    Translation.tr(\"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\"),\n                    \"-a\", \"Shell\"\n                ])\n            }\n        }\n    }\n\n    Process {\n        id: fetchActiveState\n        running: true\n        command: [\"bash\", \"-c\", \"warp-cli status\"]\n        stdout: StdioCollector {\n            id: warpStatusCollector\n            onStreamFinished: {\n                if (warpStatusCollector.text.length > 0) {\n                    root.visible = true\n                }\n                if (warpStatusCollector.text.includes(\"Unable\")) {\n                    registrationProc.running = true\n                } else if (warpStatusCollector.text.includes(\"Connected\")) {\n                    root.toggled = true\n                } else if (warpStatusCollector.text.includes(\"Disconnected\")) {\n                    root.toggled = false\n                }\n            }\n        }\n    }\n    StyledToolTip {\n        text: Translation.tr(\"Cloudflare WARP (1.1.1.1)\")\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/EasyEffectsToggle.qml",
    "content": "import qs.modules.common.widgets\nimport qs\nimport qs.services\nimport QtQuick\nimport Quickshell.Io\nimport Quickshell\nimport Quickshell.Hyprland\n\nQuickToggleButton {\n    id: root\n    visible: EasyEffects.available\n    toggled: EasyEffects.active\n    buttonIcon: \"instant_mix\"\n\n    Component.onCompleted: {\n        EasyEffects.fetchActiveState()\n    }\n\n    onClicked: {\n        EasyEffects.toggle()\n    }\n\n    altAction: () => {\n        Quickshell.execDetached([\"bash\", \"-c\", \"flatpak run com.github.wwmm.easyeffects || easyeffects\"])\n        GlobalStates.sidebarRightOpen = false\n    }\n\n    StyledToolTip {\n        text: Translation.tr(\"EasyEffects | Right-click to configure\")\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/GameMode.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport Quickshell\nimport Quickshell.Io\n\nQuickToggleButton {\n    id: root\n    buttonIcon: \"gamepad\"\n    toggled: toggled\n\n    onClicked: {\n        root.toggled = !root.toggled\n        if (root.toggled) {\n            Quickshell.execDetached([\"bash\", \"-c\", `hyprctl --batch \"keyword animations:enabled 0; keyword decoration:shadow:enabled 0; keyword decoration:blur:enabled 0; keyword general:gaps_in 0; keyword general:gaps_out 0; keyword general:border_size 1; keyword decoration:rounding 0; keyword general:allow_tearing 1\"`])\n        } else {\n            Quickshell.execDetached([\"hyprctl\", \"reload\"])\n        }\n    }\n    Process {\n        id: fetchActiveState\n        running: true\n        command: [\"bash\", \"-c\", `test \"$(hyprctl getoption animations:enabled -j | jq \".int\")\" -ne 0`]\n        onExited: (exitCode, exitStatus) => {\n            root.toggled = exitCode !== 0 // Inverted because enabled = nonzero exit\n        }\n    }\n    StyledToolTip {\n        text: Translation.tr(\"Game mode\")\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/IdleInhibitor.qml",
    "content": "import qs.modules.common.widgets\nimport qs.services\n\nQuickToggleButton {\n    id: root\n    toggled: Idle.inhibit\n    buttonIcon: \"coffee\"\n    onClicked: {\n        Idle.toggleInhibit()\n    }\n    StyledToolTip {\n        text: Translation.tr(\"Keep system awake\")\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/NetworkToggle.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.ii.sidebarRight.quickToggles\nimport qs\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nQuickToggleButton {\n    toggled: Network.wifiStatus !== \"disabled\"\n    buttonIcon: Network.materialSymbol\n    onClicked: Network.toggleWifi()\n    altAction: () => {\n        Quickshell.execDetached([\"bash\", \"-c\", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`])\n        GlobalStates.sidebarRightOpen = false\n    }\n    StyledToolTip {\n        text: Translation.tr(\"%1 | Right-click to configure\").arg(Network.networkName)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/NightLight.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport Quickshell.Io\n\nQuickToggleButton {\n    id: nightLightButton\n    toggled: Hyprsunset.temperatureActive\n    buttonIcon: Config.options.light.night.automatic ? \"night_sight_auto\" : \"bedtime\"\n    onClicked: {\n        Hyprsunset.toggleTemperature()\n    }\n\n    altAction: () => {\n        Config.options.light.night.automatic = !Config.options.light.night.automatic\n    }\n\n    Component.onCompleted: {\n        Hyprsunset.fetchState()\n    }\n    \n    StyledToolTip {\n        text: Translation.tr(\"Night Light | Right-click to toggle Auto mode\")\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/quickToggles/classicStyle/QuickToggleButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nGroupButton {\n    id: button\n    property string buttonIcon\n    baseWidth: 40\n    baseHeight: 40\n    clickedWidth: baseWidth + 20\n    toggled: false\n    buttonRadius: (altAction && toggled) ? Appearance?.rounding.normal : Math.min(baseHeight, baseWidth) / 2\n    buttonRadiusPressed: Appearance?.rounding?.small\n\n    contentItem: MaterialSymbol {\n        anchors.centerIn: parent\n        iconSize: 22\n        fill: toggled ? 1 : 0\n        color: toggled ? Appearance.m3colors.m3onPrimary : Appearance.colors.colOnLayer1\n        horizontalAlignment: Text.AlignHCenter\n        verticalAlignment: Text.AlignVCenter\n        text: buttonIcon\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TaskList.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport Qt5Compat.GraphicalEffects\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: root\n    required property var taskList\n    property string emptyPlaceholderIcon\n    property string emptyPlaceholderText\n    property int todoListItemSpacing: 5\n    property int todoListItemPadding: 8\n    property int listBottomPadding: 80\n\n    StyledListView {\n        id: listView\n        anchors.fill: parent\n        spacing: root.todoListItemSpacing\n        animateAppearance: false\n        model: ScriptModel {\n            values: root.taskList\n        }\n        delegate: Item {\n            id: todoItem\n            required property var modelData\n            property bool pendingDoneToggle: false\n            property bool pendingDelete: false\n            property bool enableHeightAnimation: false\n\n            implicitHeight: todoItemRectangle.implicitHeight\n            width: ListView.view.width\n            clip: true\n\n            Behavior on implicitHeight {\n                enabled: enableHeightAnimation\n                NumberAnimation {\n                    duration: Appearance.animation.elementMoveFast.duration\n                    easing.type: Appearance.animation.elementMoveFast.type\n                    easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n                }\n            }\n\n            Rectangle {\n                id: todoItemRectangle\n                anchors.left: parent.left\n                anchors.right: parent.right\n                anchors.bottom: parent.bottom\n                implicitHeight: todoContentRowLayout.implicitHeight\n                color: Appearance.colors.colLayer2\n                radius: Appearance.rounding.small\n\n                ColumnLayout {\n                    id: todoContentRowLayout\n                    anchors.left: parent.left\n                    anchors.right: parent.right\n\n                    StyledText {\n                        id: todoContentText\n                        Layout.fillWidth: true // Needed for wrapping\n                        Layout.leftMargin: 10\n                        Layout.rightMargin: 10\n                        Layout.topMargin: todoListItemPadding\n                        text: todoItem.modelData.content\n                        wrapMode: Text.Wrap\n                    }\n                    RowLayout {\n                        Layout.leftMargin: 10\n                        Layout.rightMargin: 10\n                        Layout.bottomMargin: todoListItemPadding\n                        Item {\n                            Layout.fillWidth: true\n                        }\n                        TodoItemActionButton {\n                            Layout.fillWidth: false\n                            onClicked: {\n                                if (!todoItem.modelData.done)\n                                    Todo.markDone(todoItem.modelData.originalIndex);\n                                else\n                                    Todo.markUnfinished(todoItem.modelData.originalIndex);\n                            }\n                            contentItem: MaterialSymbol {\n                                anchors.centerIn: parent\n                                horizontalAlignment: Text.AlignHCenter\n                                text: todoItem.modelData.done ? \"remove_done\" : \"check\"\n                                iconSize: Appearance.font.pixelSize.larger\n                                color: Appearance.colors.colOnLayer1\n                            }\n                        }\n                        TodoItemActionButton {\n                            Layout.fillWidth: false\n                            onClicked: {\n                                Todo.deleteItem(todoItem.modelData.originalIndex);\n                            }\n                            contentItem: MaterialSymbol {\n                                anchors.centerIn: parent\n                                horizontalAlignment: Text.AlignHCenter\n                                text: \"delete_forever\"\n                                iconSize: Appearance.font.pixelSize.larger\n                                color: Appearance.colors.colOnLayer1\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Item {\n        // Placeholder when list is empty\n        visible: opacity > 0\n        opacity: taskList.length === 0 ? 1 : 0\n        anchors.fill: parent\n\n        Behavior on opacity {\n            animation: Appearance.animation.elementMove.numberAnimation.createObject(this)\n        }\n\n        ColumnLayout {\n            anchors.centerIn: parent\n            spacing: 5\n\n            MaterialSymbol {\n                Layout.alignment: Qt.AlignHCenter\n                iconSize: 55\n                color: Appearance.m3colors.m3outline\n                text: emptyPlaceholderIcon\n            }\n            StyledText {\n                Layout.alignment: Qt.AlignHCenter\n                font.pixelSize: Appearance.font.pixelSize.normal\n                color: Appearance.m3colors.m3outline\n                horizontalAlignment: Text.AlignHCenter\n                text: emptyPlaceholderText\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TodoItemActionButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\n\nRippleButton {\n    id: button\n    property string buttonText: \"\"\n    property string tooltipText: \"\"\n\n    implicitHeight: 30\n    implicitWidth: implicitHeight\n\n    Behavior on implicitWidth {\n        SmoothedAnimation {\n            velocity: Appearance.animation.elementMove.velocity\n        }\n    }\n\n    buttonRadius: Appearance.rounding.small\n\n    contentItem: StyledText {\n        text: buttonText\n        horizontalAlignment: Text.AlignHCenter\n        font.pixelSize: Appearance.font.pixelSize.larger\n        color: Appearance.colors.colOnLayer1\n    }\n\n    StyledToolTip {\n        text: tooltipText\n        extraVisibleCondition: tooltipText.length > 0\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/todo/TodoWidget.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\n\nItem {\n    id: root\n    property var tabButtonList: [{\"icon\": \"checklist\", \"name\": Translation.tr(\"Unfinished\")}, {\"name\": Translation.tr(\"Done\"), \"icon\": \"check_circle\"}]\n    property bool showAddDialog: false\n    property int dialogMargins: 20\n    property int fabSize: 48\n    property int fabMargins: 14\n\n    Keys.onPressed: (event) => {\n        if ((event.key === Qt.Key_PageDown || event.key === Qt.Key_PageUp) && event.modifiers === Qt.NoModifier) {\n            if (event.key === Qt.Key_PageDown) {\n                tabBar.incrementCurrentIndex();\n            } else if (event.key === Qt.Key_PageUp) {\n                tabBar.decrementCurrentIndex();\n            }\n            event.accepted = true;\n        }\n        // Open add dialog on \"N\" (any modifiers)\n        else if (event.key === Qt.Key_N) {\n            root.showAddDialog = true\n            event.accepted = true;\n        }\n        // Close dialog on Esc if open\n        else if (event.key === Qt.Key_Escape && root.showAddDialog) {\n            root.showAddDialog = false\n            event.accepted = true;\n        }\n    }\n\n    ColumnLayout {\n        anchors.fill: parent\n        spacing: 0\n\n        SecondaryTabBar {\n            id: tabBar\n            currentIndex: swipeView.currentIndex\n\n            Repeater {\n                model: root.tabButtonList\n                delegate: SecondaryTabButton {\n                    buttonText: modelData.name\n                    buttonIcon: modelData.icon\n                }\n            }\n        }\n\n        SwipeView {\n            id: swipeView\n            Layout.topMargin: 10\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            spacing: 10\n            clip: true\n            currentIndex: tabBar.currentIndex\n\n            // To Do tab\n            TaskList {\n                listBottomPadding: root.fabSize + root.fabMargins * 2\n                emptyPlaceholderIcon: \"check_circle\"\n                emptyPlaceholderText: Translation.tr(\"Nothing here!\")\n                taskList: Todo.list\n                    .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); })\n                    .filter(function(item) { return !item.done; })\n            }\n            TaskList {\n                listBottomPadding: root.fabSize + root.fabMargins * 2\n                emptyPlaceholderIcon: \"checklist\"\n                emptyPlaceholderText: Translation.tr(\"Finished tasks will go here\")\n                taskList: Todo.list\n                    .map(function(item, i) { return Object.assign({}, item, {originalIndex: i}); })\n                    .filter(function(item) { return item.done; })\n            }\n\n        }\n    }\n\n    // + FAB\n    StyledRectangularShadow {\n        target: fabButton\n        radius: fabButton.buttonRadius\n        blur: 0.6 * Appearance.sizes.elevationMargin\n    }\n    FloatingActionButton {\n        id: fabButton\n        anchors.right: parent.right\n        anchors.bottom: parent.bottom\n        anchors.rightMargin: root.fabMargins\n        anchors.bottomMargin: root.fabMargins\n\n        onClicked: root.showAddDialog = true\n        iconText: \"add\"\n    }\n\n    Item {\n        anchors.fill: parent\n        z: 9999\n\n        visible: opacity > 0\n        opacity: root.showAddDialog ? 1 : 0\n        Behavior on opacity {\n            NumberAnimation { \n                duration: Appearance.animation.elementMoveFast.duration\n                easing.type: Appearance.animation.elementMoveFast.type\n                easing.bezierCurve: Appearance.animation.elementMoveFast.bezierCurve\n            }\n        }\n\n        onVisibleChanged: {\n            if (!visible) {\n                todoInput.text = \"\"\n                fabButton.focus = true\n            }\n        }\n\n        Rectangle { // Scrim\n            anchors.fill: parent\n            radius: Appearance.rounding.small\n            color: Appearance.colors.colScrim\n            MouseArea {\n                hoverEnabled: true\n                anchors.fill: parent\n                preventStealing: true\n                propagateComposedEvents: false\n            }\n        }\n\n        Rectangle { // The dialog\n            id: dialog\n            anchors.left: parent.left\n            anchors.right: parent.right\n            anchors.verticalCenter: parent.verticalCenter\n            anchors.margins: root.dialogMargins\n            implicitHeight: dialogColumnLayout.implicitHeight\n\n            color: Appearance.m3colors.m3surfaceContainerHigh\n            radius: Appearance.rounding.normal\n\n            function addTask() {\n                if (todoInput.text.length > 0) {\n                    Todo.addTask(todoInput.text)\n                    todoInput.text = \"\"\n                    root.showAddDialog = false\n                    tabBar.setCurrentIndex(0) // Show unfinished tasks\n                }\n            }\n\n            ColumnLayout {\n                id: dialogColumnLayout\n                anchors.fill: parent\n                spacing: 16\n\n                StyledText {\n                    Layout.topMargin: 16\n                    Layout.leftMargin: 16\n                    Layout.rightMargin: 16\n                    Layout.alignment: Qt.AlignLeft\n                    color: Appearance.m3colors.m3onSurface\n                    font.pixelSize: Appearance.font.pixelSize.larger\n                    text: Translation.tr(\"Add task\")\n                }\n\n                TextField {\n                    id: todoInput\n                    Layout.fillWidth: true\n                    Layout.leftMargin: 16\n                    Layout.rightMargin: 16\n                    padding: 10\n                    color: activeFocus ? Appearance.m3colors.m3onSurface : Appearance.m3colors.m3onSurfaceVariant\n                    renderType: Text.NativeRendering\n                    selectedTextColor: Appearance.m3colors.m3onSecondaryContainer\n                    selectionColor: Appearance.colors.colSecondaryContainer\n                    placeholderText: Translation.tr(\"Task description\")\n                    placeholderTextColor: Appearance.m3colors.m3outline\n                    focus: root.showAddDialog\n                    onAccepted: dialog.addTask()\n\n                    background: Rectangle {\n                        anchors.fill: parent\n                        radius: Appearance.rounding.verysmall\n                        border.width: 2\n                        border.color: todoInput.activeFocus ? Appearance.colors.colPrimary : Appearance.m3colors.m3outline\n                        color: \"transparent\"\n                    }\n\n                    cursorDelegate: Rectangle {\n                        width: 1\n                        color: todoInput.activeFocus ? Appearance.colors.colPrimary : \"transparent\"\n                        radius: 1\n                    }\n                }\n\n                RowLayout {\n                    Layout.bottomMargin: 16\n                    Layout.leftMargin: 16\n                    Layout.rightMargin: 16\n                    Layout.alignment: Qt.AlignRight\n                    spacing: 5\n\n                    DialogButton {\n                        buttonText: Translation.tr(\"Cancel\")\n                        onClicked: root.showAddDialog = false\n                    }\n                    DialogButton {\n                        buttonText: Translation.tr(\"Add\")\n                        enabled: todoInput.text.length > 0\n                        onClicked: dialog.addTask()\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/AudioDeviceSelectorButton.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell.Services.Pipewire\n\nRippleButton {\n    id: button\n    required property bool input\n\n    buttonRadius: Appearance.rounding.small\n    colBackground: Appearance.colors.colLayer2\n    colBackgroundHover: Appearance.colors.colLayer2Hover\n    colRipple: Appearance.colors.colLayer2Active\n\n    implicitHeight: contentItem.implicitHeight + 6 * 2\n    implicitWidth: contentItem.implicitWidth + 6 * 2\n\n    contentItem: RowLayout {\n        anchors.fill: parent\n        anchors.margins: 5\n        spacing: 5\n\n        MaterialSymbol {\n            Layout.alignment: Qt.AlignVCenter\n            Layout.fillWidth: false\n            Layout.leftMargin: 5\n            color: Appearance.colors.colOnLayer2\n            iconSize: Appearance.font.pixelSize.hugeass\n            text: input ? \"mic_external_on\" : \"media_output\"\n        }\n\n        ColumnLayout {\n            Layout.fillWidth: true\n            Layout.rightMargin: 5\n            spacing: 0\n            StyledText {\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                font.pixelSize: Appearance.font.pixelSize.normal\n                text: input ? Translation.tr(\"Input\") : Translation.tr(\"Output\")\n                color: Appearance.colors.colOnLayer2\n            }\n            StyledText {\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                text: (input ? Pipewire.defaultAudioSource?.description : Pipewire.defaultAudioSink?.description) ?? Translation.tr(\"Unknown\")\n                color: Appearance.m3colors.m3outline\n                animateChange: true\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialog.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Pipewire\n\nWindowDialog {\n    id: root\n    property bool isSink: true\n    backgroundHeight: 600\n\n    WindowDialogTitle {\n        text: root.isSink ? Translation.tr(\"Audio output\") : Translation.tr(\"Audio input\")\n    }\n\n    WindowDialogSeparator {\n        Layout.topMargin: -22\n        Layout.leftMargin: 0\n        Layout.rightMargin: 0\n    }\n\n    VolumeDialogContent {\n        isSink: root.isSink\n    }\n\n    WindowDialogButtonRow {\n        DialogButton {\n            buttonText: Translation.tr(\"Details\")\n            onClicked: {\n                Quickshell.execDetached([\"bash\", \"-c\", `${Config.options.apps.volumeMixer}`]);\n                GlobalStates.sidebarRightOpen = false;\n            }\n        }\n\n        Item {\n            Layout.fillWidth: true\n        }\n\n        DialogButton {\n            buttonText: Translation.tr(\"Done\")\n            onClicked: root.dismiss()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeDialogContent.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Pipewire\n\nColumnLayout {\n    id: root\n    required property bool isSink\n    readonly property list<var> appPwNodes: isSink ? Audio.outputAppNodes : Audio.inputAppNodes\n    readonly property list<var> devices: isSink ? Audio.outputDevices : Audio.inputDevices\n    readonly property bool hasApps: appPwNodes.length > 0\n    spacing: 16\n\n    DialogSectionListView {\n        Layout.fillHeight: true\n        topMargin: 14\n\n        model: ScriptModel {\n            values: root.appPwNodes\n        }\n        delegate: VolumeMixerEntry {\n            anchors {\n                left: parent?.left\n                right: parent?.right\n            }\n            required property var modelData\n            node: modelData\n        }\n        PagePlaceholder {\n            icon: \"widgets\"\n            title: Translation.tr(\"No applications\")\n            shown: !root.hasApps\n            shape: MaterialShape.Shape.Cookie7Sided\n        }\n    }\n\n    StyledComboBox {\n        id: deviceSelector\n        Layout.fillHeight: false\n        Layout.fillWidth: true\n        Layout.bottomMargin: 6\n        model: root.devices.map(node => Audio.friendlyDeviceName(node))\n        currentIndex: root.devices.findIndex(item => {\n            if (root.isSink) {\n                return item.id === Pipewire.defaultAudioSink?.id\n            } else {\n                return item.id === Pipewire.defaultAudioSource?.id\n            }\n        })\n        onActivated: (index) => {\n            print(index)\n            const item = root.devices[index]\n            if (root.isSink) {\n                Audio.setDefaultSink(item)\n            } else {\n                Audio.setDefaultSource(item)\n            }\n        }\n    }\n\n    component DialogSectionListView: StyledListView {\n        Layout.fillWidth: true\n        Layout.topMargin: -22\n        Layout.bottomMargin: -16\n        Layout.leftMargin: -Appearance.rounding.large\n        Layout.rightMargin: -Appearance.rounding.large\n        topMargin: 12\n        bottomMargin: 12\n        leftMargin: 20\n        rightMargin: 20\n\n        clip: true\n        spacing: 4\n        animateAppearance: false\n    }\n\n    Component {\n        id: listElementComp\n        ListElement {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/volumeMixer/VolumeMixerEntry.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Pipewire\nimport Qt5Compat.GraphicalEffects\n\nItem {\n    id: root\n    required property PwNode node\n    PwObjectTracker {\n        objects: [root.node]\n    }\n\n    implicitHeight: rowLayout.implicitHeight\n\n    RowLayout {\n        id: rowLayout\n        anchors.fill: parent\n        spacing: 6\n\n        MouseArea {\n            property real size: 36\n            Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter\n            Layout.preferredWidth: size\n            Layout.preferredHeight: size\n\n            cursorShape: Qt.PointingHandCursor\n            onClicked: root.node.audio.muted = !root.node.audio.muted\n\n            hoverEnabled: true\n            property bool hovered: containsMouse\n            StyledToolTip {\n                text: root.node?.audio.muted ? Translation.tr(\"Click to unmute\") : Translation.tr(\"Click to mute\")\n            }\n\n            StyledImage {\n                id: iconImg\n                anchors.fill: parent\n                visible: false\n                source: {\n                    let icon;\n                    icon = AppSearch.guessIcon(root.node?.properties[\"application.icon-name\"] ?? \"\");\n                    if (AppSearch.iconExists(icon))\n                        return Quickshell.iconPath(icon, \"image-missing\");\n                    icon = AppSearch.guessIcon(root.node?.properties[\"node.name\"] ?? \"\");\n                    return Quickshell.iconPath(icon, \"image-missing\");\n                }\n            }\n\n            Desaturate {\n                anchors.fill: iconImg\n                source: iconImg\n                desaturation: root.node?.audio.muted ? 1.0 : 0.0\n                visible: iconImg.source !== \"\"\n                opacity: root.node?.audio.muted ? 0.4 : 1.0\n\n                Behavior on opacity {\n                    NumberAnimation {\n                        duration: 150\n                    }\n                }\n                Behavior on desaturation {\n                    NumberAnimation {\n                        duration: 150\n                    }\n                }\n            }\n\n            MaterialSymbol {\n                anchors.centerIn: parent\n                visible: root.node?.audio.muted ?? false\n                text: root.node?.isSink ? \"volume_off\" : \"mic_off\"\n                iconSize: 22\n                color: Appearance.colors.colOnLayer1\n            }\n        }\n\n        ColumnLayout {\n            Layout.fillWidth: true\n            spacing: -4\n\n            StyledText {\n                Layout.fillWidth: true\n                font.pixelSize: Appearance.font.pixelSize.small\n                color: Appearance.colors.colSubtext\n                elide: Text.ElideRight\n                text: {\n                    // application.name -> description -> name\n                    const app = Audio.appNodeDisplayName(root.node);\n                    const media = root.node.properties[\"media.name\"];\n                    return media != undefined ? `${app} • ${media}` : app;\n                }\n            }\n\n            StyledSlider {\n                id: slider\n                value: root.node?.audio.volume ?? 0\n                onMoved: root.node.audio.volume = value\n                configuration: StyledSlider.Configuration.S\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiDialog.qml",
    "content": "import qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\n\nWindowDialog {\n    id: root\n    backgroundHeight: 600\n\n    WindowDialogTitle {\n        text: Translation.tr(\"Connect to Wi-Fi\")\n    }\n    WindowDialogSeparator {\n        visible: !Network.wifiScanning\n    }\n    StyledIndeterminateProgressBar {\n        visible: Network.wifiScanning\n        Layout.fillWidth: true\n        Layout.topMargin: -8\n        Layout.bottomMargin: -8\n        Layout.leftMargin: -Appearance.rounding.large\n        Layout.rightMargin: -Appearance.rounding.large\n    }\n    ListView {\n        Layout.fillHeight: true\n        Layout.fillWidth: true\n        Layout.topMargin: -15\n        Layout.bottomMargin: -16\n        Layout.leftMargin: -Appearance.rounding.large\n        Layout.rightMargin: -Appearance.rounding.large\n\n        clip: true\n        spacing: 0\n\n        model: ScriptModel {\n            values: Network.friendlyWifiNetworks\n        }\n        delegate: WifiNetworkItem {\n            required property WifiAccessPoint modelData\n            wifiNetwork: modelData\n            width: ListView.view.width\n        }\n    }\n    WindowDialogSeparator {}\n    WindowDialogButtonRow {\n        DialogButton {\n            buttonText: Translation.tr(\"Details\")\n            onClicked: {\n                Quickshell.execDetached([\"bash\", \"-c\", `${Network.ethernet ? Config.options.apps.networkEthernet : Config.options.apps.network}`]);\n                GlobalStates.sidebarRightOpen = false;\n            }\n        }\n\n        Item {\n            Layout.fillWidth: true\n        }\n\n        DialogButton {\n            buttonText: Translation.tr(\"Done\")\n            onClicked: root.dismiss()\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/sidebarRight/wifiNetworks/WifiNetworkItem.qml",
    "content": "import qs\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs.services.network\nimport QtQuick\nimport QtQuick.Layouts\n\nDialogListItem {\n    id: root\n    required property WifiAccessPoint wifiNetwork\n    enabled: !(Network.wifiConnectTarget === root.wifiNetwork && !wifiNetwork?.active)\n\n    active: (wifiNetwork?.askingPassword || wifiNetwork?.active) ?? false\n    onClicked: {\n        Network.connectToWifiNetwork(wifiNetwork);\n    }\n\n    contentItem: ColumnLayout {\n        anchors {\n            fill: parent\n            topMargin: root.verticalPadding\n            bottomMargin: root.verticalPadding\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n        }\n        spacing: 0\n\n        RowLayout {\n            // Name\n            spacing: 10\n            MaterialSymbol {\n                iconSize: Appearance.font.pixelSize.larger\n                property int strength: root.wifiNetwork?.strength ?? 0\n                text: strength > 80 ? \"signal_wifi_4_bar\" : strength > 60 ? \"network_wifi_3_bar\" : strength > 40 ? \"network_wifi_2_bar\" : strength > 20 ? \"network_wifi_1_bar\" : \"signal_wifi_0_bar\"\n                color: Appearance.colors.colOnSurfaceVariant\n            }\n            StyledText {\n                Layout.fillWidth: true\n                color: Appearance.colors.colOnSurfaceVariant\n                elide: Text.ElideRight\n                text: root.wifiNetwork?.ssid ?? Translation.tr(\"Unknown\")\n                textFormat: Text.PlainText\n            }\n            MaterialSymbol {\n                visible: (root.wifiNetwork?.isSecure || root.wifiNetwork?.active) ?? false\n                text: root.wifiNetwork?.active ? \"check\" : Network.wifiConnectTarget === root.wifiNetwork ? \"settings_ethernet\" : \"lock\"\n                iconSize: Appearance.font.pixelSize.larger\n                color: Appearance.colors.colOnSurfaceVariant\n            }\n        }\n\n        ColumnLayout { // Password\n            id: passwordPrompt\n            Layout.topMargin: 8\n            visible: root.wifiNetwork?.askingPassword ?? false\n\n            MaterialTextField {\n                id: passwordField\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Password\")\n\n                // Password\n                echoMode: TextInput.Password\n                inputMethodHints: Qt.ImhSensitiveData\n\n                onAccepted: {\n                    Network.changePassword(root.wifiNetwork, passwordField.text);\n                }\n            }\n\n            RowLayout {\n                Layout.fillWidth: true\n\n                Item {\n                    Layout.fillWidth: true\n                }\n\n                DialogButton {\n                    buttonText: Translation.tr(\"Cancel\")\n                    onClicked: {\n                        root.wifiNetwork.askingPassword = false;\n                    }\n                }\n\n                DialogButton {\n                    buttonText: Translation.tr(\"Connect\")\n                    onClicked: {\n                        Network.changePassword(root.wifiNetwork, passwordField.text);\n                    }\n                }\n            }\n        }\n\n        ColumnLayout { // Public wifi login page\n            id: publicWifiPortal\n            Layout.topMargin: 8\n            visible: (root.wifiNetwork?.active && (root.wifiNetwork?.security ?? \"\").trim().length === 0) ?? false\n\n            RowLayout {\n                DialogButton {\n                    Layout.fillWidth: true\n                    buttonText: Translation.tr(\"Open network portal\")\n                    colBackground: Appearance.colors.colLayer4\n                    colBackgroundHover: Appearance.colors.colLayer4Hover\n                    colRipple: Appearance.colors.colLayer4Active\n                    onClicked: {\n                        Network.openPublicWifiPortal()\n                        GlobalStates.sidebarRightOpen = false\n                    }\n                }\n            }\n        }\n\n        Item {\n            Layout.fillHeight: true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/BatteryIndicator.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\nimport qs.modules.ii.bar as Bar\n\nMouseArea {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    readonly property var chargeState: Battery.chargeState\n    readonly property bool isCharging: Battery.isCharging\n    readonly property bool isPluggedIn: Battery.isPluggedIn\n    readonly property real percentage: Battery.percentage\n    readonly property bool isLow: percentage <= Config.options.battery.low / 100\n\n    implicitHeight: batteryProgress.implicitHeight\n    hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n    ClippedProgressBar {\n        id: batteryProgress\n        anchors.centerIn: parent\n        vertical: true\n        valueBarWidth: 20\n        valueBarHeight: 36\n        value: percentage\n        // value: 1\n        highlightColor: (isLow && !isCharging) ? Appearance.m3colors.m3error : Appearance.colors.colOnSecondaryContainer\n\n        font {\n            pixelSize: 13\n            weight: Font.DemiBold\n        }\n\n        textMask: Item {\n            anchors.centerIn: parent\n            width: batteryProgress.valueBarWidth\n            height: batteryProgress.valueBarHeight\n\n            Column {\n                anchors.centerIn: parent\n                spacing: -4\n\n                MaterialSymbol {\n                    id: boltIcon\n                    anchors.horizontalCenter: parent.horizontalCenter\n                    fill: 1\n                    text: {\n                        if (batteryProgress.value == 1) {\n                            return \"check\";\n                        } else if (root.isCharging) {\n                            return \"bolt\";\n                        } else {\n                            return Icons.getBatteryIcon(Battery.percentage * 100);\n                        }\n                    }\n                    iconSize: Appearance.font.pixelSize.normal\n                    animateChange: true\n                }\n                StyledText {\n                    visible: text.length <= 2\n                    anchors.horizontalCenter: parent.horizontalCenter\n                    font: batteryProgress.font\n                    text: batteryProgress.text\n                }\n            }\n        }\n    }\n\n    Bar.BatteryPopup {\n        id: batteryPopup\n        hoverTarget: root\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/Resource.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport QtQuick\n\nItem {\n    id: root\n    required property string iconName\n    required property double percentage\n    property int warningThreshold: 100\n    implicitHeight: resourceProgress.implicitHeight\n    implicitWidth: Appearance.sizes.verticalBarWidth\n\n    property bool warning: percentage * 100 >= warningThreshold\n\n    ClippedFilledCircularProgress {\n        id: resourceProgress\n        anchors.centerIn: parent\n        value: percentage\n        enableAnimation: false\n        colPrimary: root.warning ? Appearance.colors.colError : Appearance.colors.colOnSecondaryContainer\n        accountForLightBleeding: !root.warning\n\n        MaterialSymbol {\n            font.weight: Font.Medium\n            fill: 1\n            text: root.iconName\n            iconSize: 13\n            color: Appearance.colors.colOnSecondaryContainer\n        }\n    }\n\n    MouseArea {\n        id: mouseArea\n        anchors.fill: parent\n        hoverEnabled: true\n        acceptedButtons: Qt.NoButton\n        enabled: root.visible\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/Resources.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport QtQuick\nimport QtQuick.Layouts\nimport qs.modules.ii.bar as Bar\n\nMouseArea {\n    id: root\n    property bool alwaysShowAllResources: false\n    implicitHeight: columnLayout.implicitHeight\n    implicitWidth: columnLayout.implicitWidth\n    hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n    ColumnLayout {\n        id: columnLayout\n        spacing: 10\n        anchors.fill: parent\n\n        Resource {\n            Layout.alignment: Qt.AlignHCenter\n            iconName: \"memory\"\n            percentage: ResourceUsage.memoryUsedPercentage\n            warningThreshold: Config.options.bar.resources.memoryWarningThreshold\n        }\n\n        Resource {\n            Layout.alignment: Qt.AlignHCenter\n            iconName: \"swap_horiz\"\n            percentage: ResourceUsage.swapUsedPercentage\n            warningThreshold: Config.options.bar.resources.swapWarningThreshold\n        }\n\n        Resource {\n            Layout.alignment: Qt.AlignHCenter\n            iconName: \"planner_review\"\n            percentage: ResourceUsage.cpuUsage\n            warningThreshold: Config.options.bar.resources.cpuWarningThreshold\n        }\n\n    }\n\n    Bar.ResourcesPopup {\n        hoverTarget: root\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBar.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport Quickshell.Services.UPower\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nScope {\n    id: bar\n    property bool showBarBackground: Config.options.bar.showBackground\n\n    Variants {\n        // For each monitor\n        model: {\n            const screens = Quickshell.screens;\n            const list = Config.options.bar.screenList;\n            if (!list || list.length === 0)\n                return screens;\n            return screens.filter(screen => list.includes(screen.name));\n        }\n        LazyLoader {\n            id: barLoader\n            active: GlobalStates.barOpen && !GlobalStates.screenLocked\n            required property ShellScreen modelData\n            component: PanelWindow { // Bar window\n                id: barRoot\n                screen: barLoader.modelData\n\n                property var brightnessMonitor: Brightness.getMonitorForScreen(barLoader.modelData)\n                \n                Timer {\n                    id: showBarTimer\n                    interval: (Config?.options.bar.autoHide.showWhenPressingSuper.delay ?? 100)\n                    repeat: false\n                    onTriggered: {\n                        barRoot.superShow = true\n                    }\n                }\n                Connections {\n                    target: GlobalStates\n                    function onSuperDownChanged() {\n                        if (!Config?.options.bar.autoHide.showWhenPressingSuper.enable) return;\n                        if (GlobalStates.superDown) showBarTimer.restart();\n                        else {\n                            showBarTimer.stop();\n                            barRoot.superShow = false;\n                        }\n                    }\n                }\n                property bool superShow: false\n                property bool mustShow: hoverRegion.containsMouse || superShow\n                exclusionMode: ExclusionMode.Ignore\n                exclusiveZone: (Config?.options.bar.autoHide.enable && (!mustShow || !Config?.options.bar.autoHide.pushWindows)) ? 0 :\n                    Appearance.sizes.baseVerticalBarWidth + (Config.options.bar.cornerStyle === 1 ? Appearance.sizes.hyprlandGapsOut : 0)\n                WlrLayershell.namespace: \"quickshell:verticalBar\"\n                // WlrLayershell.layer: WlrLayer.Overlay // TODO enable this when bar can hide when fullscreen\n                implicitWidth: Appearance.sizes.verticalBarWidth + Appearance.rounding.screenRounding\n                mask: Region {\n                    item: hoverMaskRegion\n                }\n                color: \"transparent\"\n\n                // Positioning\n                anchors {\n                    left: !Config.options.bar.bottom\n                    right: Config.options.bar.bottom\n                    top: true\n                    bottom: true\n                }\n\n                // Include in focus grab\n                Component.onCompleted: {\n                    GlobalFocusGrab.addPersistent(barRoot);\n                }\n                Component.onDestruction: {\n                    GlobalFocusGrab.removePersistent(barRoot);\n                }\n\n                MouseArea  {\n                    id: hoverRegion\n                    hoverEnabled: true\n                    anchors.fill: parent\n\n                    Item {\n                        id: hoverMaskRegion\n                        anchors {\n                            fill: barContent\n                            leftMargin: -Config.options.bar.autoHide.hoverRegionWidth\n                            rightMargin: -Config.options.bar.autoHide.hoverRegionWidth\n                        }\n                    }\n\n                    VerticalBarContent {\n                        id: barContent\n                        \n                        implicitWidth: Appearance.sizes.verticalBarWidth\n                        anchors {\n                            top: parent.top\n                            bottom: parent.bottom\n                            left: parent.left\n                            right: undefined\n                            leftMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.verticalBarWidth : 0\n                            rightMargin: 0\n                        }\n                        Behavior on anchors.leftMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        Behavior on anchors.rightMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n\n                        states: State {\n                            name: \"right\"\n                            when: Config.options.bar.bottom\n                            AnchorChanges {\n                                target: barContent\n                                anchors {\n                                    top: parent.top\n                                    bottom: parent.bottom\n                                    left: undefined\n                                    right: parent.right\n                                }\n                            }\n                            PropertyChanges {\n                                target: barContent\n                                anchors.topMargin: 0\n                                anchors.rightMargin: (Config?.options.bar.autoHide.enable && !mustShow) ? -Appearance.sizes.barHeight : 0\n                            }\n                        }\n                    }\n\n                    // Round decorators\n                    Loader {\n                        id: roundDecorators\n                        anchors {\n                            top: parent.top\n                            bottom: parent.bottom\n                            left: barContent.right\n                            right: undefined\n                        }\n                        width: Appearance.rounding.screenRounding\n                        active: showBarBackground && Config.options.bar.cornerStyle === 0 // Hug\n\n                        states: State {\n                            name: \"right\"\n                            when: Config.options.bar.bottom\n                            AnchorChanges {\n                                target: roundDecorators\n                                anchors {\n                                    top: parent.top\n                                    bottom: parent.bottom\n                                    left: undefined\n                                    right: barContent.left\n                                }\n                            }\n                        }\n\n                        sourceComponent: Item {\n                            implicitHeight: Appearance.rounding.screenRounding\n                            RoundCorner {\n                                id: topCorner\n                                anchors {\n                                    left: parent.left\n                                    right: parent.right\n                                    top: parent.top\n                                }\n\n                                implicitSize: Appearance.rounding.screenRounding\n                                color: showBarBackground ? Appearance.colors.colLayer0 : \"transparent\"\n\n                                corner: RoundCorner.CornerEnum.TopLeft\n                                states: State {\n                                    name: \"bottom\"\n                                    when: Config.options.bar.bottom\n                                    PropertyChanges {\n                                        topCorner.corner: RoundCorner.CornerEnum.TopRight\n                                    }\n                                }\n                            }\n                            RoundCorner {\n                                id: bottomCorner\n                                anchors {\n                                    bottom: parent.bottom\n                                    left: !Config.options.bar.bottom ? parent.left : undefined\n                                    right: Config.options.bar.bottom ? parent.right : undefined\n                                }\n                                implicitSize: Appearance.rounding.screenRounding\n                                color: showBarBackground ? Appearance.colors.colLayer0 : \"transparent\"\n\n                                corner: RoundCorner.CornerEnum.BottomLeft\n                                states: State {\n                                    name: \"bottom\"\n                                    when: Config.options.bar.bottom\n                                    PropertyChanges {\n                                        bottomCorner.corner: RoundCorner.CornerEnum.BottomRight\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"bar\"\n\n        function toggle(): void {\n            GlobalStates.barOpen = !GlobalStates.barOpen\n        }\n\n        function close(): void {\n            GlobalStates.barOpen = false\n        }\n\n        function open(): void {\n            GlobalStates.barOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barToggle\"\n        description: \"Toggles bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = !GlobalStates.barOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barOpen\"\n        description: \"Opens bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barClose\"\n        description: \"Closes bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalBarContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Bluetooth\nimport Quickshell.Services.UPower\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.ii.bar as Bar\n\nItem { // Bar content region\n    id: root\n\n    property var screen: root.QsWindow.window?.screen\n    property var brightnessMonitor: Brightness.getMonitorForScreen(screen)\n\n    component HorizontalBarSeparator: Rectangle {\n        Layout.leftMargin: Appearance.sizes.baseBarHeight / 3\n        Layout.rightMargin: Appearance.sizes.baseBarHeight / 3\n        Layout.fillWidth: true\n        implicitHeight: 1\n        color: Appearance.colors.colOutlineVariant\n    }\n\n    // Background shadow\n    Loader {\n        active: Config.options.bar.showBackground && Config.options.bar.cornerStyle === 1\n        anchors.fill: barBackground\n        sourceComponent: StyledRectangularShadow {\n            anchors.fill: undefined // The loader's anchors act on this, and this should not have any anchor\n            target: barBackground\n        }\n    }\n    // Background\n    Rectangle {\n        id: barBackground\n        anchors {\n            fill: parent\n            margins: Config.options.bar.cornerStyle === 1 ? (Appearance.sizes.hyprlandGapsOut) : 0 // idk why but +1 is needed\n        }\n        color: Config.options.bar.showBackground ? Appearance.colors.colLayer0 : \"transparent\"\n        radius: Config.options.bar.cornerStyle === 1 ? Appearance.rounding.windowRounding : 0\n        border.width: Config.options.bar.cornerStyle === 1 ? 1 : 0\n        border.color: Appearance.colors.colLayer0Border\n    }\n\n    FocusedScrollMouseArea { // Top section | scroll to change brightness\n        id: barTopSectionMouseArea\n        anchors.top: parent.top\n        implicitHeight: topSectionColumnLayout.implicitHeight\n        implicitWidth: Appearance.sizes.baseVerticalBarWidth\n        height: (root.height - middleSection.height) / 2\n        width: Appearance.sizes.verticalBarWidth\n\n        onScrollDown: Brightness.decreaseBrightness()\n        onScrollUp: Brightness.increaseBrightness()\n        onMovedAway: GlobalStates.osdBrightnessOpen = false\n        onPressed: event => {\n            if (event.button === Qt.LeftButton)\n                GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n        }\n\n        ColumnLayout { // Content\n            id: topSectionColumnLayout\n            anchors.fill: parent\n            spacing: 10\n\n            Bar.LeftSidebarButton { // Left sidebar button\n                Layout.alignment: Qt.AlignHCenter\n                Layout.topMargin: (Appearance.sizes.baseVerticalBarWidth - implicitWidth) / 2 + Appearance.sizes.hyprlandGapsOut\n                colBackground: barTopSectionMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)\n            }\n\n            Item {\n                Layout.fillHeight: true\n            }\n            \n        }\n    }\n\n    Column { // Middle section\n        id: middleSection\n        anchors.centerIn: parent\n        spacing: 4\n\n        Bar.BarGroup {\n            vertical: true\n            padding: 8\n            Resources {\n                Layout.fillWidth: true\n                Layout.fillHeight: false\n            }\n            \n            HorizontalBarSeparator {}\n\n            VerticalMedia {\n                Layout.fillWidth: true\n                Layout.fillHeight: false\n            }\n        }\n\n        HorizontalBarSeparator {\n            visible: Config.options?.bar.borderless\n        }\n\n        Bar.BarGroup {\n            id: middleCenterGroup\n            vertical: true\n            padding: 6\n\n            Bar.Workspaces {\n                id: workspacesWidget\n                vertical: true\n                MouseArea {\n                    // Right-click to toggle overview\n                    anchors.fill: parent\n                    acceptedButtons: Qt.RightButton\n\n                    onPressed: event => {\n                        if (event.button === Qt.RightButton) {\n                            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n                        }\n                    }\n                }\n            }\n        }\n\n        HorizontalBarSeparator {\n            visible: Config.options?.bar.borderless\n        }\n\n        Bar.BarGroup {\n            vertical: true\n            padding: 8\n            \n            VerticalClockWidget {\n                Layout.fillWidth: true\n                Layout.fillHeight: false\n            }\n\n            HorizontalBarSeparator {\n                visible: Battery.available\n            }\n\n            BatteryIndicator {\n                visible: Battery.available\n                Layout.fillWidth: true\n                Layout.fillHeight: false\n            }\n            \n        }\n    }\n\n    FocusedScrollMouseArea { // Bottom section | scroll to change volume\n        id: barBottomSectionMouseArea\n\n        anchors {\n            left: parent.left\n            right: parent.right\n            bottom: parent.bottom\n        }\n        implicitWidth: Appearance.sizes.baseVerticalBarWidth\n        implicitHeight: bottomSectionColumnLayout.implicitHeight\n        \n        onScrollDown: Audio.decrementVolume();\n        onScrollUp: Audio.incrementVolume();\n        onMovedAway: GlobalStates.osdVolumeOpen = false;\n        onPressed: event => {\n            if (event.button === Qt.LeftButton) {\n                GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n            }\n        }\n\n        ColumnLayout {\n            id: bottomSectionColumnLayout\n            anchors.fill: parent\n            spacing: 4\n\n            Item { \n                Layout.fillWidth: true\n                Layout.fillHeight: true \n            }\n\n            Bar.SysTray {\n                vertical: true\n                Layout.fillWidth: true\n                Layout.fillHeight: false\n                invertSide: Config?.options.bar.bottom\n            }\n\n            RippleButton { // Right sidebar button\n                id: rightSidebarButton\n\n                Layout.alignment: Qt.AlignBottom | Qt.AlignHCenter\n                Layout.bottomMargin: Appearance.rounding.screenRounding\n                Layout.fillHeight: false\n\n                implicitHeight: indicatorsColumnLayout.implicitHeight + 4 * 2\n                implicitWidth: indicatorsColumnLayout.implicitWidth + 6 * 2\n\n                buttonRadius: Appearance.rounding.full\n                colBackground: barBottomSectionMouseArea.hovered ? Appearance.colors.colLayer1Hover : ColorUtils.transparentize(Appearance.colors.colLayer1Hover, 1)\n                colBackgroundHover: Appearance.colors.colLayer1Hover\n                colRipple: Appearance.colors.colLayer1Active\n                colBackgroundToggled: Appearance.colors.colSecondaryContainer\n                colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n                colRippleToggled: Appearance.colors.colSecondaryContainerActive\n                toggled: GlobalStates.sidebarRightOpen\n                property color colText: toggled ? Appearance.m3colors.m3onSecondaryContainer : Appearance.colors.colOnLayer0\n\n                Behavior on colText {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n\n                onPressed: {\n                    GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n                }\n\n                ColumnLayout {\n                    id: indicatorsColumnLayout\n                    anchors.centerIn: parent\n                    property real realSpacing: 6\n                    spacing: 0\n\n                    Revealer {\n                        vertical: true\n                        reveal: Audio.sink?.audio?.muted ?? false\n                        Layout.fillWidth: true\n                        Layout.bottomMargin: reveal ? indicatorsColumnLayout.realSpacing : 0\n                        Behavior on Layout.bottomMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        MaterialSymbol {\n                            text: \"volume_off\"\n                            iconSize: Appearance.font.pixelSize.larger\n                            color: rightSidebarButton.colText\n                        }\n                    }\n                    Revealer {\n                        vertical: true\n                        reveal: Audio.source?.audio?.muted ?? false\n                        Layout.fillWidth: true\n                        Layout.bottomMargin: reveal ? indicatorsColumnLayout.realSpacing : 0\n                        Behavior on Layout.topMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        MaterialSymbol {\n                            text: \"mic_off\"\n                            iconSize: Appearance.font.pixelSize.larger\n                            color: rightSidebarButton.colText\n                        }\n                    }\n                    Bar.HyprlandXkbIndicator {\n                        vertical: true\n                        Layout.alignment: Qt.AlignHCenter\n                        Layout.bottomMargin: indicatorsColumnLayout.realSpacing\n                        color: rightSidebarButton.colText\n                    }\n                    Revealer {\n                        vertical: true\n                        reveal: Notifications.silent || Notifications.unread > 0\n                        Layout.fillWidth: true\n                        Layout.bottomMargin: reveal ? indicatorsColumnLayout.realSpacing : 0\n                        implicitHeight: reveal ? notificationUnreadCount.implicitHeight : 0\n                        implicitWidth: reveal ? notificationUnreadCount.implicitWidth : 0\n                        Behavior on Layout.bottomMargin {\n                            animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                        }\n                        Bar.NotificationUnreadCount {\n                            id: notificationUnreadCount\n                        }\n                    }\n                    MaterialSymbol {\n                        text: Network.materialSymbol\n                        iconSize: Appearance.font.pixelSize.larger\n                        color: rightSidebarButton.colText\n                    }\n                    MaterialSymbol {\n                        Layout.topMargin: indicatorsColumnLayout.realSpacing\n                        visible: BluetoothStatus.available\n                        text: BluetoothStatus.connected ? \"bluetooth_connected\" : BluetoothStatus.enabled ? \"bluetooth\" : \"bluetooth_disabled\"\n                        iconSize: Appearance.font.pixelSize.larger\n                        color: rightSidebarButton.colText\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalClockWidget.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Layouts\nimport qs.modules.ii.bar as Bar\n\nItem {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    implicitHeight: column.implicitHeight\n    implicitWidth: Appearance.sizes.verticalBarWidth\n\n    readonly property string dateTimeString: DateTime.time\n    readonly property bool hasAmPm: dateTimeString.toLowerCase().includes(\"am\") || dateTimeString.toLowerCase().includes(\"pm\")\n\n    Column {\n        id: column\n        anchors.centerIn: parent\n        spacing: root.hasAmPm ? 6 : 0\n\n        Column {\n            anchors.horizontalCenter: parent.horizontalCenter\n            spacing: -4\n\n            Repeater {\n                model: root.dateTimeString.split(/[: ]/)\n                delegate: StyledText {\n                    required property string modelData\n                    anchors.horizontalCenter: parent.horizontalCenter\n                    font.pixelSize: {\n                        if (modelData.match(/am|pm/i))\n                            return Appearance.font.pixelSize.smaller;\n                        else\n                            // Smaller \"am\"/\"pm\" text\n                            return Appearance.font.pixelSize.large;\n                    }\n                    color: Appearance.colors.colOnLayer1\n                    text: modelData.padStart(2, \"0\")\n                }\n            }\n        }\n        StyledText {\n            anchors.horizontalCenter: parent.horizontalCenter\n            font.pixelSize: Appearance.font.pixelSize.smallest\n            color: Appearance.colors.colOnLayer1\n            text: DateTime.shortDate\n        }\n    }\n\n    MouseArea {\n        id: mouseArea\n        anchors.fill: parent\n        hoverEnabled: !Config.options.bar.tooltips.clickToShow\n\n        Bar.ClockWidgetPopup {\n            hoverTarget: mouseArea\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/verticalBar/VerticalMedia.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport qs.services\nimport qs\nimport qs.modules.common.functions\n\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell.Services.Mpris\n\nimport qs.modules.ii.bar as Bar\n\nMouseArea {\n    id: root\n    property bool borderless: Config.options.bar.borderless\n    readonly property MprisPlayer activePlayer: MprisController.activePlayer\n    readonly property string cleanedTitle: StringUtils.cleanMusicTitle(activePlayer?.trackTitle) || Translation.tr(\"No media\")\n\n    Layout.fillHeight: true\n    implicitHeight: mediaCircProg.implicitHeight\n    implicitWidth: Appearance.sizes.verticalBarWidth\n\n    Timer {\n        running: activePlayer?.playbackState == MprisPlaybackState.Playing\n        interval: Config.options.resources.updateInterval\n        repeat: true\n        onTriggered: activePlayer.positionChanged()\n    }\n\n    acceptedButtons: Qt.MiddleButton | Qt.BackButton | Qt.ForwardButton | Qt.RightButton | Qt.LeftButton\n    hoverEnabled: !Config.options.bar.tooltips.clickToShow\n    onPressed: (event) => {\n        if (event.button === Qt.MiddleButton) {\n            activePlayer.togglePlaying();\n        } else if (event.button === Qt.BackButton) {\n            activePlayer.previous();\n        } else if (event.button === Qt.ForwardButton || event.button === Qt.RightButton) {\n            activePlayer.next();\n        } else if (event.button === Qt.LeftButton) {\n            GlobalStates.mediaControlsOpen = !GlobalStates.mediaControlsOpen\n        }\n    }\n\n    ClippedFilledCircularProgress {\n        id: mediaCircProg\n        anchors.centerIn: parent\n        implicitSize: 20\n\n        lineWidth: Appearance.rounding.unsharpen\n        value: activePlayer?.position / activePlayer?.length\n        colPrimary: Appearance.colors.colOnSecondaryContainer\n        enableAnimation: false\n\n        Item {\n            anchors.centerIn: parent\n            width: mediaCircProg.implicitSize\n            height: mediaCircProg.implicitSize\n            \n            MaterialSymbol {\n                anchors.centerIn: parent\n                fill: 1\n                text: activePlayer?.isPlaying ? \"pause\" : \"music_note\"\n                iconSize: Appearance.font.pixelSize.normal\n                color: Appearance.m3colors.m3onSecondaryContainer\n            }\n        }\n    }\n\n    Bar.StyledPopup {\n        hoverTarget: root\n        active: GlobalStates.mediaControlsOpen ? false : root.containsMouse\n\n        Column {\n            anchors.centerIn: parent\n            spacing: 4\n\n            Bar.StyledPopupHeaderRow {\n                icon: \"music_note\"\n                label: Translation.tr(\"Media\")\n            }\n\n            StyledText {\n                color: Appearance.colors.colOnSurfaceVariant\n                text: `${cleanedTitle}${activePlayer?.trackArtist ? '\\n' + activePlayer.trackArtist : ''}`\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperDirectoryItem.qml",
    "content": "import qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\n\nMouseArea {\n    id: root\n    required property var fileModelData\n    property bool isDirectory: fileModelData.fileIsDir\n    property bool useThumbnail: Images.isValidImageByName(fileModelData.fileName)\n\n    property alias colBackground: background.color\n    property alias colText: wallpaperItemName.color\n    property alias radius: background.radius\n    property alias margins: background.anchors.margins\n    property alias padding: wallpaperItemColumnLayout.anchors.margins\n    margins: Appearance.sizes.wallpaperSelectorItemMargins\n    padding: Appearance.sizes.wallpaperSelectorItemPadding\n\n    signal activated()\n\n    hoverEnabled: true\n    onClicked: root.activated()\n\n    Rectangle {\n        id: background\n        anchors.fill: parent\n        radius: Appearance.rounding.normal\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n\n        ColumnLayout {\n            id: wallpaperItemColumnLayout\n            anchors.fill: parent\n            spacing: 4\n\n            Item {\n                id: wallpaperItemImageContainer\n                Layout.fillHeight: true\n                Layout.fillWidth: true\n\n                Loader {\n                    id: thumbnailShadowLoader\n                    active: thumbnailImageLoader.active && thumbnailImageLoader.item.status === Image.Ready\n                    anchors.fill: thumbnailImageLoader\n                    sourceComponent: StyledRectangularShadow {\n                        target: thumbnailImageLoader\n                        anchors.fill: undefined\n                        radius: Appearance.rounding.small\n                    }\n                }\n\n                Loader {\n                    id: thumbnailImageLoader\n                    anchors.fill: parent\n                    active: root.useThumbnail\n                    sourceComponent: ThumbnailImage {\n                        id: thumbnailImage\n                        generateThumbnail: false\n                        sourcePath: fileModelData.filePath\n\n                        cache: false\n                        fillMode: Image.PreserveAspectCrop\n                        clip: true\n\n                        Connections {\n                            target: Wallpapers\n                            function onThumbnailGenerated(directory) {\n                                if (thumbnailImage.status !== Image.Error) return;\n                                if (FileUtils.parentDirectory(thumbnailImage.sourcePath) !== FileUtils.trimFileProtocol(directory)) return;\n                                thumbnailImage.source = \"\";\n                                thumbnailImage.source = thumbnailImage.thumbnailPath;\n                            }\n                            function onThumbnailGeneratedFile(filePath) {\n                                if (thumbnailImage.status !== Image.Error) return;\n                                if (Qt.resolvedUrl(thumbnailImage.sourcePath) !== Qt.resolvedUrl(filePath)) return;\n                                thumbnailImage.source = \"\";\n                                thumbnailImage.source = thumbnailImage.thumbnailPath;\n                            }\n                        }\n\n                        layer.enabled: true\n                        layer.effect: OpacityMask {\n                            maskSource: Rectangle {\n                                width: wallpaperItemImageContainer.width\n                                height: wallpaperItemImageContainer.height\n                                radius: Appearance.rounding.small\n                            }\n                        }\n                    }\n                }\n\n                Loader {\n                    id: iconLoader\n                    active: !root.useThumbnail\n                    anchors.fill: parent\n                    sourceComponent: DirectoryIcon {\n                        fileModelData: root.fileModelData\n                    }\n                }\n            }\n\n            StyledText {\n                id: wallpaperItemName\n                Layout.fillWidth: true\n                Layout.leftMargin: 10\n                Layout.rightMargin: 10\n\n                horizontalAlignment: Text.AlignHCenter\n                elide: Text.ElideRight\n                font.pixelSize: Appearance.font.pixelSize.smaller\n                Behavior on color {\n                    animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n                }\n                text: fileModelData.fileName\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelector.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n\n    Loader {\n        id: wallpaperSelectorLoader\n        active: GlobalStates.wallpaperSelectorOpen\n\n        sourceComponent: PanelWindow {\n            id: panelWindow\n            readonly property HyprlandMonitor monitor: Hyprland.monitorFor(panelWindow.screen)\n            property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)\n\n            exclusionMode: ExclusionMode.Ignore\n            WlrLayershell.namespace: \"quickshell:wallpaperSelector\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n            color: \"transparent\"\n\n            anchors.top: true\n            margins {\n                top: Config?.options.bar.vertical ? Appearance.sizes.hyprlandGapsOut : Appearance.sizes.barHeight + Appearance.sizes.hyprlandGapsOut\n            }\n\n            mask: Region {\n                item: content\n            }\n\n            implicitHeight: Appearance.sizes.wallpaperSelectorHeight\n            implicitWidth: Appearance.sizes.wallpaperSelectorWidth\n\n            Component.onCompleted: {\n                GlobalFocusGrab.addDismissable(panelWindow);\n            }\n            Component.onDestruction: {\n                GlobalFocusGrab.removeDismissable(panelWindow);\n            }\n            Connections {\n                target: GlobalFocusGrab\n                function onDismissed() {\n                    GlobalStates.wallpaperSelectorOpen = false;\n                }\n            }\n\n            WallpaperSelectorContent {\n                id: content\n                anchors {\n                    fill: parent\n                }\n            }\n        }\n    }\n\n    function toggleWallpaperSelector() {\n        if (Config.options.wallpaperSelector.useSystemFileDialog) {\n            Wallpapers.openFallbackPicker(Appearance.m3colors.darkmode);\n            return;\n        }\n        GlobalStates.wallpaperSelectorOpen = !GlobalStates.wallpaperSelectorOpen\n    }\n\n    IpcHandler {\n        target: \"wallpaperSelector\"\n\n        function toggle(): void {\n            root.toggleWallpaperSelector();\n        }\n\n        function random(): void {\n            Wallpapers.randomFromCurrentFolder();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"wallpaperSelectorToggle\"\n        description: \"Toggle wallpaper selector\"\n        onPressed: {\n            root.toggleWallpaperSelector();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"wallpaperSelectorRandom\"\n        description: \"Select random wallpaper in current folder\"\n        onPressed: {\n            Wallpapers.randomFromCurrentFolder();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/ii/wallpaperSelector/WallpaperSelectorContent.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\n\nMouseArea {\n    id: root\n    property int columns: 4\n    property real previewCellAspectRatio: 4 / 3\n    property bool useDarkMode: Appearance.m3colors.darkmode\n\n    function updateThumbnails() {\n        const totalImageMargin = (Appearance.sizes.wallpaperSelectorItemMargins + Appearance.sizes.wallpaperSelectorItemPadding) * 2;\n        const thumbnailSizeName = Images.thumbnailSizeNameForDimensions(grid.cellWidth - totalImageMargin, grid.cellHeight - totalImageMargin);\n        Wallpapers.generateThumbnail(thumbnailSizeName);\n    }\n\n    Connections {\n        target: Wallpapers\n        function onDirectoryChanged() {\n            root.updateThumbnails();\n        }\n    }\n\n    function handleFilePasting(event) {\n        const currentClipboardEntry = Cliphist.entries[0];\n        if (/^\\d+\\tfile:\\/\\/\\S+/.test(currentClipboardEntry)) {\n            const url = StringUtils.cleanCliphistEntry(currentClipboardEntry);\n            Wallpapers.setDirectory(FileUtils.trimFileProtocol(decodeURIComponent(url)));\n            event.accepted = true;\n        } else {\n            event.accepted = false; // No image, let text pasting proceed\n        }\n    }\n\n    function selectWallpaperPath(filePath) {\n        if (filePath && filePath.length > 0) {\n            Wallpapers.select(filePath, root.useDarkMode);\n            filterField.text = \"\";\n        }\n    }\n\n    acceptedButtons: Qt.BackButton | Qt.ForwardButton\n    onPressed: event => {\n        if (event.button === Qt.BackButton) {\n            Wallpapers.navigateBack();\n        } else if (event.button === Qt.ForwardButton) {\n            Wallpapers.navigateForward();\n        }\n    }\n\n    Keys.onPressed: event => {\n        if (event.key === Qt.Key_Escape) {\n            GlobalStates.wallpaperSelectorOpen = false;\n            event.accepted = true;\n        } else if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle \"paste to go to\" in pickers\n            root.handleFilePasting(event);\n        } else if (event.modifiers & Qt.AltModifier && event.key === Qt.Key_Up) {\n            Wallpapers.navigateUp();\n            event.accepted = true;\n        } else if (event.modifiers & Qt.AltModifier && event.key === Qt.Key_Left) {\n            Wallpapers.navigateBack();\n            event.accepted = true;\n        } else if (event.modifiers & Qt.AltModifier && event.key === Qt.Key_Right) {\n            Wallpapers.navigateForward();\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Left) {\n            grid.moveSelection(-1);\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Right) {\n            grid.moveSelection(1);\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Up) {\n            grid.moveSelection(-grid.columns);\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Down) {\n            grid.moveSelection(grid.columns);\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            grid.activateCurrent();\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Backspace) {\n            if (filterField.text.length > 0) {\n                filterField.text = filterField.text.substring(0, filterField.text.length - 1);\n            }\n            filterField.forceActiveFocus();\n            event.accepted = true;\n        } else if (event.modifiers & Qt.ControlModifier && event.key === Qt.Key_L) {\n            addressBar.focusBreadcrumb();\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Slash) {\n            filterField.forceActiveFocus();\n            event.accepted = true;\n        } else {\n            if (event.text.length > 0) {\n                filterField.text += event.text;\n                filterField.cursorPosition = filterField.text.length;\n                filterField.forceActiveFocus();\n            }\n            event.accepted = true;\n        }\n    }\n\n    implicitHeight: mainLayout.implicitHeight\n    implicitWidth: mainLayout.implicitWidth\n\n    StyledRectangularShadow {\n        target: wallpaperGridBackground\n    }\n    Rectangle {\n        id: wallpaperGridBackground\n        anchors {\n            fill: parent\n            margins: Appearance.sizes.elevationMargin\n        }\n        focus: true\n        border.width: 1\n        border.color: Appearance.colors.colLayer0Border\n        color: Appearance.colors.colLayer0\n        radius: Appearance.rounding.screenRounding - Appearance.sizes.hyprlandGapsOut + 1\n\n        property int calculatedRows: Math.ceil(grid.count / grid.columns)\n\n        implicitWidth: gridColumnLayout.implicitWidth\n        implicitHeight: gridColumnLayout.implicitHeight\n\n        RowLayout {\n            id: mainLayout\n            anchors.fill: parent\n            spacing: -4\n\n            Rectangle {\n                Layout.fillHeight: true\n                Layout.margins: 4\n                implicitWidth: quickDirColumnLayout.implicitWidth\n                implicitHeight: quickDirColumnLayout.implicitHeight\n                color: Appearance.colors.colLayer1\n                radius: wallpaperGridBackground.radius - Layout.margins\n\n                ColumnLayout {\n                    id: quickDirColumnLayout\n                    anchors.fill: parent\n                    spacing: 0\n\n                    StyledText {\n                        Layout.margins: 12\n                        font {\n                            pixelSize: Appearance.font.pixelSize.normal\n                            weight: Font.Medium\n                        }\n                        text: Translation.tr(\"Pick a wallpaper\")\n                    }\n                    ListView {\n                        // Quick dirs\n                        Layout.fillHeight: true\n                        Layout.margins: 4\n                        implicitWidth: 140\n                        clip: true\n                        model: [\n                            {\n                                icon: \"home\",\n                                name: \"Home\",\n                                path: Directories.home\n                            },\n                            {\n                                icon: \"docs\",\n                                name: \"Documents\",\n                                path: Directories.documents\n                            },\n                            {\n                                icon: \"download\",\n                                name: \"Downloads\",\n                                path: Directories.downloads\n                            },\n                            {\n                                icon: \"image\",\n                                name: \"Pictures\",\n                                path: Directories.pictures\n                            },\n                            {\n                                icon: \"movie\",\n                                name: \"Videos\",\n                                path: Directories.videos\n                            },\n                            {\n                                icon: \"\",\n                                name: \"---\",\n                                path: \"INTENTIONALLY_INVALID_DIR\"\n                            },\n                            {\n                                icon: \"wallpaper\",\n                                name: \"Wallpapers\",\n                                path: `${Directories.pictures}/Wallpapers`\n                            },\n                            ...(Config.options.policies.weeb === 1 ? [\n                                    {\n                                        icon: \"favorite\",\n                                        name: \"Homework\",\n                                        path: `${Directories.pictures}/homework`\n                                    }\n                                ] : []),]\n                        delegate: RippleButton {\n                            id: quickDirButton\n                            required property var modelData\n                            anchors {\n                                left: parent.left\n                                right: parent.right\n                            }\n                            onClicked: Wallpapers.setDirectory(quickDirButton.modelData.path)\n                            enabled: modelData.icon.length > 0\n                            toggled: Wallpapers.directory === Qt.resolvedUrl(modelData.path)\n                            colBackgroundToggled: Appearance.colors.colSecondaryContainer\n                            colBackgroundToggledHover: Appearance.colors.colSecondaryContainerHover\n                            colRippleToggled: Appearance.colors.colSecondaryContainerActive\n                            buttonRadius: height / 2\n                            implicitHeight: 38\n\n                            contentItem: RowLayout {\n                                MaterialSymbol {\n                                    color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1\n                                    iconSize: Appearance.font.pixelSize.larger\n                                    text: quickDirButton.modelData.icon\n                                    fill: quickDirButton.toggled ? 1 : 0\n                                }\n                                StyledText {\n                                    Layout.fillWidth: true\n                                    horizontalAlignment: Text.AlignLeft\n                                    color: quickDirButton.toggled ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer1\n                                    text: quickDirButton.modelData.name\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n            ColumnLayout {\n                id: gridColumnLayout\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n\n                AddressBar {\n                    id: addressBar\n                    Layout.margins: 4\n                    Layout.fillWidth: true\n                    Layout.fillHeight: false\n                    directory: Wallpapers.effectiveDirectory\n                    onNavigateToDirectory: path => {\n                        Wallpapers.setDirectory(path.length == 0 ? \"/\" : path);\n                    }\n                    radius: wallpaperGridBackground.radius - Layout.margins\n                }\n\n                Item {\n                    id: gridDisplayRegion\n                    Layout.fillWidth: true\n                    Layout.fillHeight: true\n\n                    StyledIndeterminateProgressBar {\n                        id: indeterminateProgressBar\n                        visible: Wallpapers.thumbnailGenerationRunning && value == 0\n                        anchors {\n                            bottom: parent.top\n                            left: parent.left\n                            right: parent.right\n                            leftMargin: 4\n                            rightMargin: 4\n                        }\n                    }\n\n                    StyledProgressBar {\n                        visible: Wallpapers.thumbnailGenerationRunning && value > 0\n                        value: Wallpapers.thumbnailGenerationProgress\n                        anchors.fill: indeterminateProgressBar\n                    }\n\n                    GridView {\n                        id: grid\n                        visible: Wallpapers.folderModel.count > 0\n\n                        readonly property int columns: root.columns\n                        readonly property int rows: Math.max(1, Math.ceil(count / columns))\n                        property int currentIndex: 0\n\n                        anchors.fill: parent\n                        cellWidth: width / root.columns\n                        cellHeight: cellWidth / root.previewCellAspectRatio\n                        interactive: true\n                        clip: true\n                        keyNavigationWraps: true\n                        boundsBehavior: Flickable.StopAtBounds\n                        bottomMargin: extraOptions.implicitHeight\n                        ScrollBar.vertical: StyledScrollBar {}\n\n                        Component.onCompleted: {\n                            root.updateThumbnails();\n                        }\n\n                        function moveSelection(delta) {\n                            currentIndex = Math.max(0, Math.min(grid.model.count - 1, currentIndex + delta));\n                            positionViewAtIndex(currentIndex, GridView.Contain);\n                        }\n\n                        function activateCurrent() {\n                            const filePath = grid.model.get(currentIndex, \"filePath\");\n                            root.selectWallpaperPath(filePath);\n                        }\n\n                        model: Wallpapers.folderModel\n                        onModelChanged: currentIndex = 0\n                        delegate: WallpaperDirectoryItem {\n                            required property var modelData\n                            required property int index\n                            fileModelData: modelData\n                            width: grid.cellWidth\n                            height: grid.cellHeight\n                            colBackground: (index === grid?.currentIndex || containsMouse) ? Appearance.colors.colPrimary : (fileModelData.filePath === Config.options.background.wallpaperPath) ? Appearance.colors.colSecondaryContainer : ColorUtils.transparentize(Appearance.colors.colPrimaryContainer)\n                            colText: (index === grid.currentIndex || containsMouse) ? Appearance.colors.colOnPrimary : (fileModelData.filePath === Config.options.background.wallpaperPath) ? Appearance.colors.colOnSecondaryContainer : Appearance.colors.colOnLayer0\n\n                            onEntered: {\n                                grid.currentIndex = index;\n                            }\n\n                            onActivated: {\n                                root.selectWallpaperPath(fileModelData.filePath);\n                            }\n                        }\n\n                        layer.enabled: true\n                        layer.effect: OpacityMask {\n                            maskSource: Rectangle {\n                                width: gridDisplayRegion.width\n                                height: gridDisplayRegion.height\n                                radius: wallpaperGridBackground.radius\n                            }\n                        }\n                    }\n\n                    Row {\n                        id: extraOptions\n                        anchors {\n                            bottom: parent.bottom\n                            horizontalCenter: parent.horizontalCenter\n                            bottomMargin: 8\n                        }\n                        spacing: 6\n                        Toolbar {\n\n                            IconToolbarButton {\n                                implicitWidth: height\n                                onClicked: {\n                                    Wallpapers.openFallbackPicker(root.useDarkMode);\n                                    GlobalStates.wallpaperSelectorOpen = false;\n                                }\n                                altAction: () => {\n                                    Wallpapers.openFallbackPicker(root.useDarkMode);\n                                    GlobalStates.wallpaperSelectorOpen = false;\n                                    Config.options.wallpaperSelector.useSystemFileDialog = true;\n                                }\n                                text: \"open_in_new\"\n                                StyledToolTip {\n                                    text: Translation.tr(\"Use the system file picker instead\\nRight-click to make this the default behavior\")\n                                }\n                            }\n\n                            IconToolbarButton {\n                                implicitWidth: height\n                                onClicked: {\n                                    Wallpapers.randomFromCurrentFolder();\n                                }\n                                text: \"ifl\"\n                                StyledToolTip {\n                                    text: Translation.tr(\"Pick random from this folder\")\n                                }\n                            }\n\n                            IconToolbarButton {\n                                implicitWidth: height\n                                onClicked: root.useDarkMode = !root.useDarkMode\n                                text: root.useDarkMode ? \"dark_mode\" : \"light_mode\"\n                                StyledToolTip {\n                                    text: Translation.tr(\"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\")\n                                }\n                            }\n\n                            ToolbarTextField {\n                                id: filterField\n                                placeholderText: focus ? Translation.tr(\"Search wallpapers\") : Translation.tr(\"Hit \\\"/\\\" to search\")\n\n                                // Style\n                                clip: true\n                                font.pixelSize: Appearance.font.pixelSize.small\n\n                                // Search\n                                onTextChanged: {\n                                    Wallpapers.searchQuery = text;\n                                }\n\n                                Keys.onPressed: event => {\n                                    if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_V) { // Intercept Ctrl+V to handle \"paste to go to\" in pickers\n                                        root.handleFilePasting(event);\n                                        return;\n                                    } else if (text.length !== 0) {\n                                        // No filtering, just navigate grid\n                                        if (event.key === Qt.Key_Down) {\n                                            grid.moveSelection(grid.columns);\n                                            event.accepted = true;\n                                            return;\n                                        }\n                                        if (event.key === Qt.Key_Up) {\n                                            grid.moveSelection(-grid.columns);\n                                            event.accepted = true;\n                                            return;\n                                        }\n                                    }\n                                    event.accepted = false;\n                                }\n                            }\n                        }\n\n                        ToolbarPairedFab {\n                            iconText: \"close\"\n                            onClicked: GlobalStates.wallpaperSelectorOpen = false;\n                            StyledToolTip {\n                                text: Translation.tr(\"Cancel wallpaper selection\")\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Connections {\n        target: GlobalStates\n        function onWallpaperSelectorOpenChanged() {\n            if (GlobalStates.wallpaperSelectorOpen && monitorIsFocused) {\n                filterField.forceActiveFocus();\n            }\n        }\n    }\n\n    Connections {\n        target: Wallpapers\n        function onChanged() {\n            GlobalStates.wallpaperSelectorOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/About.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Widgets\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    ContentSection {\n        icon: \"box\"\n        title: Translation.tr(\"Distro\")\n        \n        RowLayout {\n            Layout.alignment: Qt.AlignHCenter\n            spacing: 20\n            Layout.topMargin: 10\n            Layout.bottomMargin: 10\n            IconImage {\n                implicitSize: 80\n                source: Quickshell.iconPath(SystemInfo.logo)\n            }\n            ColumnLayout {\n                Layout.alignment: Qt.AlignVCenter\n                // spacing: 10\n                StyledText {\n                    text: SystemInfo.distroName\n                    font.pixelSize: Appearance.font.pixelSize.title\n                }\n                StyledText {\n                    font.pixelSize: Appearance.font.pixelSize.normal\n                    text: SystemInfo.homeUrl\n                    textFormat: Text.MarkdownText\n                    onLinkActivated: (link) => {\n                        Qt.openUrlExternally(link)\n                    }\n                    PointingHandLinkHover {}\n                }\n            }\n        }\n\n        Flow {\n            Layout.fillWidth: true\n            spacing: 5\n\n            RippleButtonWithIcon {\n                materialIcon: \"auto_stories\"\n                mainText: Translation.tr(\"Documentation\")\n                onClicked: {\n                    Qt.openUrlExternally(SystemInfo.documentationUrl)\n                }\n            }\n            RippleButtonWithIcon {\n                materialIcon: \"support\"\n                mainText: Translation.tr(\"Help & Support\")\n                onClicked: {\n                    Qt.openUrlExternally(SystemInfo.supportUrl)\n                }\n            }\n            RippleButtonWithIcon {\n                materialIcon: \"bug_report\"\n                mainText: Translation.tr(\"Report a Bug\")\n                onClicked: {\n                    Qt.openUrlExternally(SystemInfo.bugReportUrl)\n                }\n            }\n            RippleButtonWithIcon {\n                materialIcon: \"policy\"\n                materialIconFill: false\n                mainText: Translation.tr(\"Privacy Policy\")\n                onClicked: {\n                    Qt.openUrlExternally(SystemInfo.privacyPolicyUrl)\n                }\n            }\n            \n        }\n\n    }\n    ContentSection {\n        icon: \"folder_managed\"\n        title: Translation.tr(\"Dotfiles\")\n\n        RowLayout {\n            Layout.alignment: Qt.AlignHCenter\n            spacing: 20\n            Layout.topMargin: 10\n            Layout.bottomMargin: 10\n            IconImage {\n                implicitSize: 80\n                source: Quickshell.iconPath(\"illogical-impulse\")\n            }\n            ColumnLayout {\n                Layout.alignment: Qt.AlignVCenter\n                // spacing: 10\n                StyledText {\n                    text: Translation.tr(\"illogical-impulse\")\n                    font.pixelSize: Appearance.font.pixelSize.title\n                }\n                StyledText {\n                    text: \"https://github.com/end-4/dots-hyprland\"\n                    font.pixelSize: Appearance.font.pixelSize.normal\n                    textFormat: Text.MarkdownText\n                    onLinkActivated: (link) => {\n                        Qt.openUrlExternally(link)\n                    }\n                    PointingHandLinkHover {}\n                }\n            }\n        }\n\n        Flow {\n            Layout.fillWidth: true\n            spacing: 5\n\n            RippleButtonWithIcon {\n                materialIcon: \"auto_stories\"\n                mainText: Translation.tr(\"Documentation\")\n                onClicked: {\n                    Qt.openUrlExternally(\"https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/\")\n                }\n            }\n            RippleButtonWithIcon {\n                materialIcon: \"adjust\"\n                materialIconFill: false\n                mainText: Translation.tr(\"Issues\")\n                onClicked: {\n                    Qt.openUrlExternally(\"https://github.com/end-4/dots-hyprland/issues\")\n                }\n            }\n            RippleButtonWithIcon {\n                materialIcon: \"forum\"\n                mainText: Translation.tr(\"Discussions\")\n                onClicked: {\n                    Qt.openUrlExternally(\"https://github.com/end-4/dots-hyprland/discussions\")\n                }\n            }\n            RippleButtonWithIcon {\n                materialIcon: \"favorite\"\n                mainText: Translation.tr(\"Donate\")\n                onClicked: {\n                    Qt.openUrlExternally(\"https://github.com/sponsors/end-4\")\n                }\n            }\n\n            \n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/AdvancedConfig.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    ContentSection {\n        icon: \"colors\"\n        title: Translation.tr(\"Color generation\")\n\n        ConfigSwitch {\n            buttonIcon: \"hardware\"\n            text: Translation.tr(\"Shell & utilities\")\n            checked: Config.options.appearance.wallpaperTheming.enableAppsAndShell\n            onCheckedChanged: {\n                Config.options.appearance.wallpaperTheming.enableAppsAndShell = checked;\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"tv_options_input_settings\"\n            text: Translation.tr(\"Qt apps\")\n            checked: Config.options.appearance.wallpaperTheming.enableQtApps\n            onCheckedChanged: {\n                Config.options.appearance.wallpaperTheming.enableQtApps = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Shell & utilities theming must also be enabled\")\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"terminal\"\n            text: Translation.tr(\"Terminal\")\n            checked: Config.options.appearance.wallpaperTheming.enableTerminal\n            onCheckedChanged: {\n                Config.options.appearance.wallpaperTheming.enableTerminal = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Shell & utilities theming must also be enabled\")\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"dark_mode\"\n                text: Translation.tr(\"Force dark mode in terminal\")\n                checked: Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode\n                onCheckedChanged: {\n                     Config.options.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode= checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Ignored if terminal theming is not enabled\")\n                }\n            }\n        }\n\n        ConfigSpinBox {\n            icon: \"invert_colors\"\n            text: Translation.tr(\"Terminal: Harmony (%)\")\n            value: Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmony * 100\n            from: 0\n            to: 100\n            stepSize: 10\n            onValueChanged: {\n                Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmony = value / 100;\n            }\n        }\n        ConfigSpinBox {\n            icon: \"gradient\"\n            text: Translation.tr(\"Terminal: Harmonize threshold\")\n            value: Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmonizeThreshold\n            from: 0\n            to: 100\n            stepSize: 10\n            onValueChanged: {\n                Config.options.appearance.wallpaperTheming.terminalGenerationProps.harmonizeThreshold = value;\n            }\n        }\n        ConfigSpinBox {\n            icon: \"format_color_text\"\n            text: Translation.tr(\"Terminal: Foreground boost (%)\")\n            value: Config.options.appearance.wallpaperTheming.terminalGenerationProps.termFgBoost * 100\n            from: 0\n            to: 100\n            stepSize: 10\n            onValueChanged: {\n                Config.options.appearance.wallpaperTheming.terminalGenerationProps.termFgBoost = value / 100;\n            }\n        }\n    }\n\n\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/BackgroundConfig.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    ContentSection {\n        icon: \"sync_alt\"\n        title: Translation.tr(\"Parallax\")\n\n        ConfigSwitch {\n            buttonIcon: \"unfold_more_double\"\n            text: Translation.tr(\"Vertical\")\n            checked: Config.options.background.parallax.vertical\n            onCheckedChanged: {\n                Config.options.background.parallax.vertical = checked;\n            }\n        }\n\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"counter_1\"\n                text: Translation.tr(\"Depends on workspace\")\n                checked: Config.options.background.parallax.enableWorkspace\n                onCheckedChanged: {\n                    Config.options.background.parallax.enableWorkspace = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"side_navigation\"\n                text: Translation.tr(\"Depends on sidebars\")\n                checked: Config.options.background.parallax.enableSidebar\n                onCheckedChanged: {\n                    Config.options.background.parallax.enableSidebar = checked;\n                }\n            }\n        }\n        ConfigSpinBox {\n            icon: \"loupe\"\n            text: Translation.tr(\"Preferred wallpaper zoom (%)\")\n            value: Config.options.background.parallax.workspaceZoom * 100\n            from: 10\n            to: 200\n            stepSize: 1\n            onValueChanged: {\n                Config.options.background.parallax.workspaceZoom = value / 100;\n            }\n        }\n    }\n\n    ContentSection {\n        id: settingsClock\n        icon: \"clock_loader_40\"\n        title: Translation.tr(\"Widget: Clock\")\n\n        function stylePresent(styleName) {\n            if (!Config.options.background.widgets.clock.showOnlyWhenLocked && Config.options.background.widgets.clock.style === styleName) {\n                return true;\n            }\n            if (Config.options.background.widgets.clock.styleLocked === styleName) {\n                return true;\n            }\n            return false;\n        }\n\n        readonly property bool digitalPresent: stylePresent(\"digital\")\n        readonly property bool cookiePresent: stylePresent(\"cookie\")\n\n        ConfigRow {\n            Layout.fillWidth: true\n\n            ConfigSwitch {\n                Layout.fillWidth: false\n                buttonIcon: \"check\"\n                text: Translation.tr(\"Enable\")\n                checked: Config.options.background.widgets.clock.enable\n                onCheckedChanged: {\n                    Config.options.background.widgets.clock.enable = checked;\n                }\n            }\n            Item {\n                Layout.fillWidth: true\n            }\n            ConfigSelectionArray {\n                Layout.fillWidth: false\n                currentValue: Config.options.background.widgets.clock.placementStrategy\n                onSelected: newValue => {\n                    Config.options.background.widgets.clock.placementStrategy = newValue;\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"Draggable\"),\n                        icon: \"drag_pan\",\n                        value: \"free\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Least busy\"),\n                        icon: \"category\",\n                        value: \"leastBusy\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Most busy\"),\n                        icon: \"shapes\",\n                        value: \"mostBusy\"\n                    },\n                ]\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"lock_clock\"\n            text: Translation.tr(\"Show only when locked\")\n            checked: Config.options.background.widgets.clock.showOnlyWhenLocked\n            onCheckedChanged: {\n                Config.options.background.widgets.clock.showOnlyWhenLocked = checked;\n            }\n        }\n\n        ConfigRow {\n            ContentSubsection {\n                visible: !Config.options.background.widgets.clock.showOnlyWhenLocked\n                title: Translation.tr(\"Clock style\")\n                Layout.fillWidth: true\n                ConfigSelectionArray {\n                    currentValue: Config.options.background.widgets.clock.style\n                    onSelected: newValue => {\n                        Config.options.background.widgets.clock.style = newValue;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Digital\"),\n                            icon: \"timer_10\",\n                            value: \"digital\"\n                        },\n                        {\n                            displayName: Translation.tr(\"Cookie\"),\n                            icon: \"cookie\",\n                            value: \"cookie\"\n                        }\n                    ]\n                }\n            }\n\n            ContentSubsection {\n                title: Translation.tr(\"Clock style (locked)\")\n                Layout.fillWidth: false\n                ConfigSelectionArray {\n                    currentValue: Config.options.background.widgets.clock.styleLocked\n                    onSelected: newValue => {\n                        Config.options.background.widgets.clock.styleLocked = newValue;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Digital\"),\n                            icon: \"timer_10\",\n                            value: \"digital\"\n                        },\n                        {\n                            displayName: Translation.tr(\"Cookie\"),\n                            icon: \"cookie\",\n                            value: \"cookie\"\n                        }\n                    ]\n                }\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.digitalPresent\n            title: Translation.tr(\"Digital clock settings\")\n            tooltip: Translation.tr(\"Font width and roundness settings are only available for some fonts like Google Sans Flex\")\n\n            ConfigRow {\n                uniform: true\n                ConfigSwitch {\n                    buttonIcon: \"vertical_distribute\"\n                    text: Translation.tr(\"Vertical\")\n                    checked: Config.options.background.widgets.clock.digital.vertical\n                    onCheckedChanged: {\n                        Config.options.background.widgets.clock.digital.vertical = checked;\n                    }\n                }\n                ConfigSwitch {\n                    buttonIcon: \"animation\"\n                    text: Translation.tr(\"Animate time change\")\n                    checked: Config.options.background.widgets.clock.digital.animateChange\n                    onCheckedChanged: {\n                        Config.options.background.widgets.clock.digital.animateChange = checked;\n                    }\n                }\n            }\n\n            ConfigRow {\n                uniform: true\n\n                ConfigSwitch {\n                    buttonIcon: \"date_range\"\n                    text: Translation.tr(\"Show date\")\n                    checked: Config.options.background.widgets.clock.digital.showDate\n                    onCheckedChanged: {\n                        Config.options.background.widgets.clock.digital.showDate = checked;\n                    }\n                }\n                ConfigSwitch {\n                    buttonIcon: \"activity_zone\"\n                    text: Translation.tr(\"Use adaptive alignment\")\n                    checked: Config.options.background.widgets.clock.digital.adaptiveAlignment\n                    onCheckedChanged: {\n                        Config.options.background.widgets.clock.digital.adaptiveAlignment = checked;\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Aligns the date and quote to left, center or right depending on its position on the screen.\")\n                    }\n                }\n            }\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family\")\n                text: Config.options.background.widgets.clock.digital.font.family\n                wrapMode: TextEdit.Wrap\n                onTextChanged: {\n                    Config.options.background.widgets.clock.digital.font.family = text;\n                }\n            }\n\n            ConfigSlider {\n                text: Translation.tr(\"Font weight\")\n                value: Config.options.background.widgets.clock.digital.font.weight\n                usePercentTooltip: false\n                buttonIcon: \"format_bold\"\n                from: 1\n                to: 1000\n                stopIndicatorValues: [350]\n                onValueChanged: {\n                    Config.options.background.widgets.clock.digital.font.weight = value;\n                }\n            }\n\n            ConfigSlider {\n                text: Translation.tr(\"Font size\")\n                value: Config.options.background.widgets.clock.digital.font.size\n                usePercentTooltip: false\n                buttonIcon: \"format_size\"\n                from: 50\n                to: 700\n                stopIndicatorValues: [90]\n                onValueChanged: {\n                    Config.options.background.widgets.clock.digital.font.size = value;\n                }\n            }\n\n            ConfigSlider {\n                text: Translation.tr(\"Font width\")\n                value: Config.options.background.widgets.clock.digital.font.width\n                usePercentTooltip: false\n                buttonIcon: \"fit_width\"\n                from: 25\n                to: 125\n                stopIndicatorValues: [100]\n                onValueChanged: {\n                    Config.options.background.widgets.clock.digital.font.width = value;\n                }\n            }\n            ConfigSlider {\n                text: Translation.tr(\"Font roundness\")\n                value: Config.options.background.widgets.clock.digital.font.roundness\n                usePercentTooltip: false\n                buttonIcon: \"line_curve\"\n                from: 0\n                to: 100\n                onValueChanged: {\n                    Config.options.background.widgets.clock.digital.font.roundness = value;\n                }\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.cookiePresent\n            title: Translation.tr(\"Cookie clock settings\")\n\n            ConfigSwitch {\n                buttonIcon: \"wand_stars\"\n                text: Translation.tr(\"Auto styling with Gemini\")\n                checked: Config.options.background.widgets.clock.cookie.aiStyling\n                onCheckedChanged: {\n                    Config.options.background.widgets.clock.cookie.aiStyling = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\")\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"airwave\"\n                text: Translation.tr(\"Use old sine wave cookie implementation\")\n                checked: Config.options.background.widgets.clock.cookie.useSineCookie\n                onCheckedChanged: {\n                    Config.options.background.widgets.clock.cookie.useSineCookie = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Looks a bit softer and more consistent with different number of sides,\\nbut has less impressive morphing\")\n                }\n            }\n\n            ConfigSpinBox {\n                icon: \"add_triangle\"\n                text: Translation.tr(\"Sides\")\n                value: Config.options.background.widgets.clock.cookie.sides\n                from: 0\n                to: 40\n                stepSize: 1\n                onValueChanged: {\n                    Config.options.background.widgets.clock.cookie.sides = value;\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"autoplay\"\n                text: Translation.tr(\"Constantly rotate\")\n                checked: Config.options.background.widgets.clock.cookie.constantlyRotate\n                onCheckedChanged: {\n                    Config.options.background.widgets.clock.cookie.constantlyRotate = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Makes the clock always rotate. This is extremely expensive\\n(expect 50% usage on Intel UHD Graphics) and thus impractical.\")\n                }\n            }\n\n            ConfigRow {\n\n                ConfigSwitch {\n                    enabled: Config.options.background.widgets.clock.cookie.dialNumberStyle === \"dots\" || Config.options.background.widgets.clock.cookie.dialNumberStyle === \"full\"\n                    buttonIcon: \"brightness_7\"\n                    text: Translation.tr(\"Hour marks\")\n                    checked: Config.options.background.widgets.clock.cookie.hourMarks\n                    onEnabledChanged: {\n                        checked = Config.options.background.widgets.clock.cookie.hourMarks;\n                    }\n                    onCheckedChanged: {\n                        Config.options.background.widgets.clock.cookie.hourMarks = checked;\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons\")\n                    }\n                }\n\n                ConfigSwitch {\n                    enabled: Config.options.background.widgets.clock.cookie.dialNumberStyle !== \"numbers\"\n                    buttonIcon: \"timer_10\"\n                    text: Translation.tr(\"Digits in the middle\")\n                    checked: Config.options.background.widgets.clock.cookie.timeIndicators\n                    onEnabledChanged: {\n                        checked = Config.options.background.widgets.clock.cookie.timeIndicators;\n                    }\n                    onCheckedChanged: {\n                        Config.options.background.widgets.clock.cookie.timeIndicators = checked;\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Can't be turned on when using 'Numbers' dial style for aesthetic reasons\")\n                    }\n                }\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.cookiePresent\n            title: Translation.tr(\"Dial style\")\n            ConfigSelectionArray {\n                currentValue: Config.options.background.widgets.clock.cookie.dialNumberStyle\n                onSelected: newValue => {\n                    Config.options.background.widgets.clock.cookie.dialNumberStyle = newValue;\n                    if (newValue !== \"dots\" && newValue !== \"full\") {\n                        Config.options.background.widgets.clock.cookie.hourMarks = false;\n                    }\n                    if (newValue === \"numbers\") {\n                        Config.options.background.widgets.clock.cookie.timeIndicators = false;\n                    }\n                }\n                options: [\n                    {\n                        displayName: \"\",\n                        icon: \"block\",\n                        value: \"none\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Dots\"),\n                        icon: \"graph_6\",\n                        value: \"dots\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Full\"),\n                        icon: \"history_toggle_off\",\n                        value: \"full\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Numbers\"),\n                        icon: \"counter_1\",\n                        value: \"numbers\"\n                    }\n                ]\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.cookiePresent\n            title: Translation.tr(\"Hour hand\")\n            ConfigSelectionArray {\n                currentValue: Config.options.background.widgets.clock.cookie.hourHandStyle\n                onSelected: newValue => {\n                    Config.options.background.widgets.clock.cookie.hourHandStyle = newValue;\n                }\n                options: [\n                    {\n                        displayName: \"\",\n                        icon: \"block\",\n                        value: \"hide\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Classic\"),\n                        icon: \"radio\",\n                        value: \"classic\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Hollow\"),\n                        icon: \"circle\",\n                        value: \"hollow\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Fill\"),\n                        icon: \"eraser_size_5\",\n                        value: \"fill\"\n                    },\n                ]\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.cookiePresent\n            title: Translation.tr(\"Minute hand\")\n\n            ConfigSelectionArray {\n                currentValue: Config.options.background.widgets.clock.cookie.minuteHandStyle\n                onSelected: newValue => {\n                    Config.options.background.widgets.clock.cookie.minuteHandStyle = newValue;\n                }\n                options: [\n                    {\n                        displayName: \"\",\n                        icon: \"block\",\n                        value: \"hide\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Classic\"),\n                        icon: \"radio\",\n                        value: \"classic\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Thin\"),\n                        icon: \"line_end\",\n                        value: \"thin\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Medium\"),\n                        icon: \"eraser_size_2\",\n                        value: \"medium\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Bold\"),\n                        icon: \"eraser_size_4\",\n                        value: \"bold\"\n                    },\n                ]\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.cookiePresent\n            title: Translation.tr(\"Second hand\")\n\n            ConfigSelectionArray {\n                currentValue: Config.options.background.widgets.clock.cookie.secondHandStyle\n                onSelected: newValue => {\n                    Config.options.background.widgets.clock.cookie.secondHandStyle = newValue;\n                }\n                options: [\n                    {\n                        displayName: \"\",\n                        icon: \"block\",\n                        value: \"hide\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Classic\"),\n                        icon: \"radio\",\n                        value: \"classic\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Line\"),\n                        icon: \"line_end\",\n                        value: \"line\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Dot\"),\n                        icon: \"adjust\",\n                        value: \"dot\"\n                    },\n                ]\n            }\n        }\n\n        ContentSubsection {\n            visible: settingsClock.cookiePresent\n            title: Translation.tr(\"Date style\")\n\n            ConfigSelectionArray {\n                currentValue: Config.options.background.widgets.clock.cookie.dateStyle\n                onSelected: newValue => {\n                    Config.options.background.widgets.clock.cookie.dateStyle = newValue;\n                }\n                options: [\n                    {\n                        displayName: \"\",\n                        icon: \"block\",\n                        value: \"hide\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Bubble\"),\n                        icon: \"bubble_chart\",\n                        value: \"bubble\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Border\"),\n                        icon: \"rotate_right\",\n                        value: \"border\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Rect\"),\n                        icon: \"rectangle\",\n                        value: \"rect\"\n                    }\n                ]\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Quote\")\n\n            ConfigSwitch {\n                buttonIcon: \"check\"\n                text: Translation.tr(\"Enable\")\n                checked: Config.options.background.widgets.clock.quote.enable\n                onCheckedChanged: {\n                    Config.options.background.widgets.clock.quote.enable = checked;\n                }\n            }\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Quote\")\n                text: Config.options.background.widgets.clock.quote.text\n                wrapMode: TextEdit.Wrap\n                onTextChanged: {\n                    Config.options.background.widgets.clock.quote.text = text;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"weather_mix\"\n        title: Translation.tr(\"Widget: Weather\")\n\n        ConfigRow {\n            Layout.fillWidth: true\n\n            ConfigSwitch {\n                Layout.fillWidth: false\n                buttonIcon: \"check\"\n                text: Translation.tr(\"Enable\")\n                checked: Config.options.background.widgets.weather.enable\n                onCheckedChanged: {\n                    Config.options.background.widgets.weather.enable = checked;\n                }\n            }\n            Item {\n                Layout.fillWidth: true\n            }\n            ConfigSelectionArray {\n                Layout.fillWidth: false\n                currentValue: Config.options.background.widgets.weather.placementStrategy\n                onSelected: newValue => {\n                    Config.options.background.widgets.weather.placementStrategy = newValue;\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"Draggable\"),\n                        icon: \"drag_pan\",\n                        value: \"free\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Least busy\"),\n                        icon: \"category\",\n                        value: \"leastBusy\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Most busy\"),\n                        icon: \"shapes\",\n                        value: \"mostBusy\"\n                    },\n                ]\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/BarConfig.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    ContentSection {\n        icon: \"notifications\"\n        title: Translation.tr(\"Notifications\")\n        ConfigSwitch {\n            buttonIcon: \"counter_2\"\n            text: Translation.tr(\"Unread indicator: show count\")\n            checked: Config.options.bar.indicators.notifications.showUnreadCount\n            onCheckedChanged: {\n                Config.options.bar.indicators.notifications.showUnreadCount = checked;\n            }\n        }\n    }\n    \n    ContentSection {\n        icon: \"spoke\"\n        title: Translation.tr(\"Positioning\")\n\n        ConfigRow {\n            ContentSubsection {\n                title: Translation.tr(\"Bar position\")\n                Layout.fillWidth: true\n\n                ConfigSelectionArray {\n                    currentValue: (Config.options.bar.bottom ? 1 : 0) | (Config.options.bar.vertical ? 2 : 0)\n                    onSelected: newValue => {\n                        Config.options.bar.bottom = (newValue & 1) !== 0;\n                        Config.options.bar.vertical = (newValue & 2) !== 0;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Top\"),\n                            icon: \"arrow_upward\",\n                            value: 0 // bottom: false, vertical: false\n                        },\n                        {\n                            displayName: Translation.tr(\"Left\"),\n                            icon: \"arrow_back\",\n                            value: 2 // bottom: false, vertical: true\n                        },\n                        {\n                            displayName: Translation.tr(\"Bottom\"),\n                            icon: \"arrow_downward\",\n                            value: 1 // bottom: true, vertical: false\n                        },\n                        {\n                            displayName: Translation.tr(\"Right\"),\n                            icon: \"arrow_forward\",\n                            value: 3 // bottom: true, vertical: true\n                        }\n                    ]\n                }\n            }\n            ContentSubsection {\n                title: Translation.tr(\"Automatically hide\")\n                Layout.fillWidth: false\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.bar.autoHide.enable\n                    onSelected: newValue => {\n                        Config.options.bar.autoHide.enable = newValue; // Update local copy\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"No\"),\n                            icon: \"close\",\n                            value: false\n                        },\n                        {\n                            displayName: Translation.tr(\"Yes\"),\n                            icon: \"check\",\n                            value: true\n                        }\n                    ]\n                }\n            }\n        }\n\n        ConfigRow {\n            \n            ContentSubsection {\n                title: Translation.tr(\"Corner style\")\n                Layout.fillWidth: true\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.bar.cornerStyle\n                    onSelected: newValue => {\n                        Config.options.bar.cornerStyle = newValue; // Update local copy\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Hug\"),\n                            icon: \"line_curve\",\n                            value: 0\n                        },\n                        {\n                            displayName: Translation.tr(\"Float\"),\n                            icon: \"page_header\",\n                            value: 1\n                        },\n                        {\n                            displayName: Translation.tr(\"Rect\"),\n                            icon: \"toolbar\",\n                            value: 2\n                        }\n                    ]\n                }\n            }\n\n            ContentSubsection {\n                title: Translation.tr(\"Group style\")\n                Layout.fillWidth: false\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.bar.borderless\n                    onSelected: newValue => {\n                        Config.options.bar.borderless = newValue; // Update local copy\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Pills\"),\n                            icon: \"location_chip\",\n                            value: false\n                        },\n                        {\n                            displayName: Translation.tr(\"Line-separated\"),\n                            icon: \"split_scene\",\n                            value: true\n                        }\n                    ]\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"shelf_auto_hide\"\n        title: Translation.tr(\"Tray\")\n\n        ConfigSwitch {\n            buttonIcon: \"keep\"\n            text: Translation.tr('Make icons pinned by default')\n            checked: Config.options.tray.invertPinnedItems\n            onCheckedChanged: {\n                Config.options.tray.invertPinnedItems = checked;\n            }\n        }\n        \n        ConfigSwitch {\n            buttonIcon: \"colors\"\n            text: Translation.tr('Tint icons')\n            checked: Config.options.tray.monochromeIcons\n            onCheckedChanged: {\n                Config.options.tray.monochromeIcons = checked;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"widgets\"\n        title: Translation.tr(\"Utility buttons\")\n\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"content_cut\"\n                text: Translation.tr(\"Screen snip\")\n                checked: Config.options.bar.utilButtons.showScreenSnip\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showScreenSnip = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"colorize\"\n                text: Translation.tr(\"Color picker\")\n                checked: Config.options.bar.utilButtons.showColorPicker\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showColorPicker = checked;\n                }\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"keyboard\"\n                text: Translation.tr(\"Keyboard toggle\")\n                checked: Config.options.bar.utilButtons.showKeyboardToggle\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showKeyboardToggle = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"mic\"\n                text: Translation.tr(\"Mic toggle\")\n                checked: Config.options.bar.utilButtons.showMicToggle\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showMicToggle = checked;\n                }\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"dark_mode\"\n                text: Translation.tr(\"Dark/Light toggle\")\n                checked: Config.options.bar.utilButtons.showDarkModeToggle\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showDarkModeToggle = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"speed\"\n                text: Translation.tr(\"Performance Profile toggle\")\n                checked: Config.options.bar.utilButtons.showPerformanceProfileToggle\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showPerformanceProfileToggle = checked;\n                }\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"videocam\"\n                text: Translation.tr(\"Record\")\n                checked: Config.options.bar.utilButtons.showScreenRecord\n                onCheckedChanged: {\n                    Config.options.bar.utilButtons.showScreenRecord = checked;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"cloud\"\n        title: Translation.tr(\"Weather\")\n        ConfigSwitch {\n            buttonIcon: \"check\"\n            text: Translation.tr(\"Enable\")\n            checked: Config.options.bar.weather.enable\n            onCheckedChanged: {\n                Config.options.bar.weather.enable = checked;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"workspaces\"\n        title: Translation.tr(\"Workspaces\")\n\n        ConfigSwitch {\n            buttonIcon: \"counter_1\"\n            text: Translation.tr('Always show numbers')\n            checked: Config.options.bar.workspaces.alwaysShowNumbers\n            onCheckedChanged: {\n                Config.options.bar.workspaces.alwaysShowNumbers = checked;\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"award_star\"\n            text: Translation.tr('Show app icons')\n            checked: Config.options.bar.workspaces.showAppIcons\n            onCheckedChanged: {\n                Config.options.bar.workspaces.showAppIcons = checked;\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"colors\"\n            text: Translation.tr('Tint app icons')\n            checked: Config.options.bar.workspaces.monochromeIcons\n            onCheckedChanged: {\n                Config.options.bar.workspaces.monochromeIcons = checked;\n            }\n        }\n\n        ConfigSpinBox {\n            icon: \"view_column\"\n            text: Translation.tr(\"Workspaces shown\")\n            value: Config.options.bar.workspaces.shown\n            from: 1\n            to: 30\n            stepSize: 1\n            onValueChanged: {\n                Config.options.bar.workspaces.shown = value;\n            }\n        }\n\n        ConfigSpinBox {\n            icon: \"touch_long\"\n            text: Translation.tr(\"Number show delay when pressing Super (ms)\")\n            value: Config.options.bar.workspaces.showNumberDelay\n            from: 0\n            to: 1000\n            stepSize: 50\n            onValueChanged: {\n                Config.options.bar.workspaces.showNumberDelay = value;\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Number style\")\n\n            ConfigSelectionArray {\n                currentValue: JSON.stringify(Config.options.bar.workspaces.numberMap)\n                onSelected: newValue => {\n                    Config.options.bar.workspaces.numberMap = JSON.parse(newValue)\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"Normal\"),\n                        icon: \"timer_10\",\n                        value: '[]'\n                    },\n                    {\n                        displayName: Translation.tr(\"Han chars\"),\n                        icon: \"square_dot\",\n                        value: '[\"一\",\"二\",\"三\",\"四\",\"五\",\"六\",\"七\",\"八\",\"九\",\"十\",\"十一\",\"十二\",\"十三\",\"十四\",\"十五\",\"十六\",\"十七\",\"十八\",\"十九\",\"二十\"]'\n                    },\n                    {\n                        displayName: Translation.tr(\"Roman\"),\n                        icon: \"account_balance\",\n                        value: '[\"I\",\"II\",\"III\",\"IV\",\"V\",\"VI\",\"VII\",\"VIII\",\"IX\",\"X\",\"XI\",\"XII\",\"XIII\",\"XIV\",\"XV\",\"XVI\",\"XVII\",\"XVIII\",\"XIX\",\"XX\"]'\n                    }\n                ]\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"tooltip\"\n        title: Translation.tr(\"Tooltips\")\n        ConfigSwitch {\n            buttonIcon: \"ads_click\"\n            text: Translation.tr(\"Click to show\")\n            checked: Config.options.bar.tooltips.clickToShow\n            onCheckedChanged: {\n                Config.options.bar.tooltips.clickToShow = checked;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/GeneralConfig.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    Process {\n        id: translationProc\n        property string locale: \"\"\n        command: [Directories.aiTranslationScriptPath, translationProc.locale]\n    }\n\n    ContentSection {\n        icon: \"volume_up\"\n        title: Translation.tr(\"Audio\")\n\n        ConfigSwitch {\n            buttonIcon: \"hearing\"\n            text: Translation.tr(\"Earbang protection\")\n            checked: Config.options.audio.protection.enable\n            onCheckedChanged: {\n                Config.options.audio.protection.enable = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Prevents abrupt increments and restricts volume limit\")\n            }\n        }\n        ConfigRow {\n            enabled: Config.options.audio.protection.enable\n            ConfigSpinBox {\n                icon: \"arrow_warm_up\"\n                text: Translation.tr(\"Max allowed increase\")\n                value: Config.options.audio.protection.maxAllowedIncrease\n                from: 0\n                to: 100\n                stepSize: 2\n                onValueChanged: {\n                    Config.options.audio.protection.maxAllowedIncrease = value;\n                }\n            }\n            ConfigSpinBox {\n                icon: \"vertical_align_top\"\n                text: Translation.tr(\"Volume limit\")\n                value: Config.options.audio.protection.maxAllowed\n                from: 0\n                to: 154 // pavucontrol allows up to 153%\n                stepSize: 2\n                onValueChanged: {\n                    Config.options.audio.protection.maxAllowed = value;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"battery_android_full\"\n        title: Translation.tr(\"Battery\")\n\n        ConfigRow {\n            uniform: true\n            ConfigSpinBox {\n                icon: \"warning\"\n                text: Translation.tr(\"Low warning\")\n                value: Config.options.battery.low\n                from: 0\n                to: 100\n                stepSize: 5\n                onValueChanged: {\n                    Config.options.battery.low = value;\n                }\n            }\n            ConfigSpinBox {\n                icon: \"dangerous\"\n                text: Translation.tr(\"Critical warning\")\n                value: Config.options.battery.critical\n                from: 0\n                to: 100\n                stepSize: 5\n                onValueChanged: {\n                    Config.options.battery.critical = value;\n                }\n            }\n        }\n        ConfigRow {\n            uniform: false\n            Layout.fillWidth: false\n            ConfigSwitch {\n                buttonIcon: \"pause\"\n                text: Translation.tr(\"Automatic suspend\")\n                checked: Config.options.battery.automaticSuspend\n                onCheckedChanged: {\n                    Config.options.battery.automaticSuspend = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Automatically suspends the system when battery is low\")\n                }\n            }\n            ConfigSpinBox {\n                enabled: Config.options.battery.automaticSuspend\n                text: Translation.tr(\"at\")\n                value: Config.options.battery.suspend\n                from: 0\n                to: 100\n                stepSize: 5\n                onValueChanged: {\n                    Config.options.battery.suspend = value;\n                }\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSpinBox {\n                icon: \"charger\"\n                text: Translation.tr(\"Full warning\")\n                value: Config.options.battery.full\n                from: 0\n                to: 101\n                stepSize: 5\n                onValueChanged: {\n                    Config.options.battery.full = value;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"language\"\n        title: Translation.tr(\"Language\")\n\n        ContentSubsection {\n            title: Translation.tr(\"Interface Language\")\n            tooltip: Translation.tr(\"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\")\n\n            StyledComboBox {\n                id: languageSelector\n                buttonIcon: \"language\"\n                textRole: \"displayName\"\n\n                model: [\n                    {\n                        displayName: Translation.tr(\"Auto (System)\"),\n                        value: \"auto\"\n                    },\n                    ...Translation.allAvailableLanguages.map(lang => {\n                        return {\n                            displayName: lang,\n                            value: lang\n                        };\n                    })]\n\n                currentIndex: {\n                    const index = model.findIndex(item => item.value === Config.options.language.ui);\n                    return index !== -1 ? index : 0;\n                }\n\n                onActivated: index => {\n                    Config.options.language.ui = model[index].value;\n                }\n            }\n        }\n        ContentSubsection {\n            title: Translation.tr(\"Generate translation with Gemini\")\n            tooltip: Translation.tr(\"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\")\n\n            ConfigRow {\n                MaterialTextArea {\n                    id: localeInput\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Locale code, e.g. fr_FR, de_DE, zh_CN...\")\n                    text: Config.options.language.ui === \"auto\" ? Qt.locale().name : Config.options.language.ui\n                }\n                RippleButtonWithIcon {\n                    id: generateTranslationBtn\n                    Layout.fillHeight: true\n                    nerdIcon: \"\"\n                    enabled: !translationProc.running || (translationProc.locale !== localeInput.text.trim())\n                    mainText: enabled ? Translation.tr(\"Generate\\nTypically takes 2 minutes\") : Translation.tr(\"Generating...\\nDon't close this window!\")\n                    onClicked: {\n                        translationProc.locale = localeInput.text.trim();\n                        translationProc.running = false;\n                        translationProc.running = true;\n                    }\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"rule\"\n        title: Translation.tr(\"Policies\")\n\n        ConfigRow {\n\n            // AI policy\n            ColumnLayout {\n                ContentSubsectionLabel {\n                    text: Translation.tr(\"AI\")\n                }\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.policies.ai\n                    onSelected: newValue => {\n                        Config.options.policies.ai = newValue;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"No\"),\n                            icon: \"close\",\n                            value: 0\n                        },\n                        {\n                            displayName: Translation.tr(\"Yes\"),\n                            icon: \"check\",\n                            value: 1\n                        },\n                        {\n                            displayName: Translation.tr(\"Local only\"),\n                            icon: \"sync_saved_locally\",\n                            value: 2\n                        }\n                    ]\n                }\n            }\n\n            // Weeb policy\n            ColumnLayout {\n\n                ContentSubsectionLabel {\n                    text: Translation.tr(\"Weeb\")\n                }\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.policies.weeb\n                    onSelected: newValue => {\n                        Config.options.policies.weeb = newValue;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"No\"),\n                            icon: \"close\",\n                            value: 0\n                        },\n                        {\n                            displayName: Translation.tr(\"Yes\"),\n                            icon: \"check\",\n                            value: 1\n                        },\n                        {\n                            displayName: Translation.tr(\"Closet\"),\n                            icon: \"ev_shadow\",\n                            value: 2\n                        }\n                    ]\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"notification_sound\"\n        title: Translation.tr(\"Sounds\")\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"battery_android_full\"\n                text: Translation.tr(\"Battery\")\n                checked: Config.options.sounds.battery\n                onCheckedChanged: {\n                    Config.options.sounds.battery = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"av_timer\"\n                text: Translation.tr(\"Pomodoro\")\n                checked: Config.options.sounds.pomodoro\n                onCheckedChanged: {\n                    Config.options.sounds.pomodoro = checked;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"nest_clock_farsight_analog\"\n        title: Translation.tr(\"Time\")\n\n        ConfigSwitch {\n            buttonIcon: \"pace\"\n            text: Translation.tr(\"Second precision\")\n            checked: Config.options.time.secondPrecision\n            onCheckedChanged: {\n                Config.options.time.secondPrecision = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Enable if you want clocks to show seconds accurately\")\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Format\")\n            tooltip: \"\"\n\n            ConfigSelectionArray {\n                currentValue: Config.options.time.format\n                onSelected: newValue => {\n                    if (newValue === \"hh:mm\") {\n                        Quickshell.execDetached([\"bash\", \"-c\", `sed -i 's/\\\\TIME12\\\\b/TIME/' '${FileUtils.trimFileProtocol(Directories.config)}/hypr/hyprlock.conf'`]);\n                    } else {\n                        Quickshell.execDetached([\"bash\", \"-c\", `sed -i 's/\\\\TIME\\\\b/TIME12/' '${FileUtils.trimFileProtocol(Directories.config)}/hypr/hyprlock.conf'`]);\n                    }\n\n                    Config.options.time.format = newValue;\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"24h\"),\n                        value: \"hh:mm\"\n                    },\n                    {\n                        displayName: Translation.tr(\"12h am/pm\"),\n                        value: \"h:mm ap\"\n                    },\n                    {\n                        displayName: Translation.tr(\"12h AM/PM\"),\n                        value: \"h:mm AP\"\n                    },\n                ]\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"work_alert\"\n        title: Translation.tr(\"Work safety\")\n\n        ConfigSwitch {\n            buttonIcon: \"assignment\"\n            text: Translation.tr(\"Hide clipboard images copied from sussy sources\")\n            checked: Config.options.workSafety.enable.clipboard\n            onCheckedChanged: {\n                Config.options.workSafety.enable.clipboard = checked;\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"wallpaper\"\n            text: Translation.tr(\"Hide sussy/anime wallpapers\")\n            checked: Config.options.workSafety.enable.wallpaper\n            onCheckedChanged: {\n                Config.options.workSafety.enable.wallpaper = checked;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/InterfaceConfig.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    ContentSection {\n        icon: \"keyboard\"\n        title: Translation.tr(\"Cheat sheet\")\n\n        ContentSubsection {\n            title: Translation.tr(\"Super key symbol\")\n            tooltip: Translation.tr(\"You can also manually edit cheatsheet.superKey\")\n            ConfigSelectionArray {\n                currentValue: Config.options.cheatsheet.superKey\n                onSelected: newValue => {\n                    Config.options.cheatsheet.superKey = newValue;\n                }\n                // Use a nerdfont to see the icons\n                options: ([\n                  \"󰖳\", \"\", \"󰨡\", \"\", \"󰌽\", \"󰣇\", \"\", \"\", \"\", \n                  \"\", \"\", \"󱄛\", \"\", \"\", \"\", \"⌘\", \"󰀲\", \"󰟍\", \"\"\n                ]).map(icon => { return {\n                  displayName: icon,\n                  value: icon\n                  }\n                })\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"󰘵\"\n            text: Translation.tr(\"Use macOS-like symbols for mods keys\")\n            checked: Config.options.cheatsheet.useMacSymbol\n            onCheckedChanged: {\n                Config.options.cheatsheet.useMacSymbol = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"e.g. 󰘴  for Ctrl, 󰘵  for Alt, 󰘶  for Shift, etc\")\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"󱊶\"\n            text: Translation.tr(\"Use symbols for function keys\")\n            checked: Config.options.cheatsheet.useFnSymbol\n            onCheckedChanged: {\n                Config.options.cheatsheet.useFnSymbol = checked;\n            }\n            StyledToolTip {\n              text: Translation.tr(\"e.g. 󱊫 for F1, 󱊶  for F12\")\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"󰍽\"\n            text: Translation.tr(\"Use symbols for mouse\")\n            checked: Config.options.cheatsheet.useMouseSymbol\n            onCheckedChanged: {\n                Config.options.cheatsheet.useMouseSymbol = checked;\n            }\n            StyledToolTip {\n              text: Translation.tr(\"Replace 󱕐   for \\\"Scroll ↓\\\", 󱕑   \\\"Scroll ↑\\\", L󰍽   \\\"LMB\\\", R󰍽   \\\"RMB\\\", 󱕒   \\\"Scroll ↑/↓\\\" and ⇞/⇟ for \\\"Page_↑/↓\\\"\")\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"highlight_keyboard_focus\"\n            text: Translation.tr(\"Split buttons\")\n            checked: Config.options.cheatsheet.splitButtons\n            onCheckedChanged: {\n                Config.options.cheatsheet.splitButtons = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Display modifiers and keys in multiple keycap (e.g., \\\"Ctrl + A\\\" instead of \\\"Ctrl A\\\" or \\\"󰘴 + A\\\" instead of \\\"󰘴 A\\\")\")\n            }\n\n        }\n\n        ConfigSpinBox {\n            text: Translation.tr(\"Keybind font size\")\n            value: Config.options.cheatsheet.fontSize.key\n            from: 8\n            to: 30\n            stepSize: 1\n            onValueChanged: {\n                Config.options.cheatsheet.fontSize.key = value;\n            }\n        }\n        ConfigSpinBox {\n            text: Translation.tr(\"Description font size\")\n            value: Config.options.cheatsheet.fontSize.comment\n            from: 8\n            to: 30\n            stepSize: 1\n            onValueChanged: {\n                Config.options.cheatsheet.fontSize.comment = value;\n            }\n        }\n    }\n    ContentSection {\n        icon: \"call_to_action\"\n        title: Translation.tr(\"Dock\")\n\n        ConfigSwitch {\n            buttonIcon: \"check\"\n            text: Translation.tr(\"Enable\")\n            checked: Config.options.dock.enable\n            onCheckedChanged: {\n                Config.options.dock.enable = checked;\n            }\n        }\n\n        ConfigRow {\n            uniform: true\n            ConfigSwitch {\n                buttonIcon: \"highlight_mouse_cursor\"\n                text: Translation.tr(\"Hover to reveal\")\n                checked: Config.options.dock.hoverToReveal\n                onCheckedChanged: {\n                    Config.options.dock.hoverToReveal = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"keep\"\n                text: Translation.tr(\"Pinned on startup\")\n                checked: Config.options.dock.pinnedOnStartup\n                onCheckedChanged: {\n                    Config.options.dock.pinnedOnStartup = checked;\n                }\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"colors\"\n            text: Translation.tr(\"Tint app icons\")\n            checked: Config.options.dock.monochromeIcons\n            onCheckedChanged: {\n                Config.options.dock.monochromeIcons = checked;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"lock\"\n        title: Translation.tr(\"Lock screen\")\n\n        ConfigSwitch {\n            buttonIcon: \"water_drop\"\n            text: Translation.tr('Use Hyprlock (instead of Quickshell)')\n            checked: Config.options.lock.useHyprlock\n            onCheckedChanged: {\n                Config.options.lock.useHyprlock = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"If you want to somehow use fingerprint unlock...\")\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"account_circle\"\n            text: Translation.tr('Launch on startup')\n            checked: Config.options.lock.launchOnStartup\n            onCheckedChanged: {\n                Config.options.lock.launchOnStartup = checked;\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Security\")\n\n            ConfigSwitch {\n                buttonIcon: \"settings_power\"\n                text: Translation.tr('Require password to power off/restart')\n                checked: Config.options.lock.security.requirePasswordToPower\n                onCheckedChanged: {\n                    Config.options.lock.security.requirePasswordToPower = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\")\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"key_vertical\"\n                text: Translation.tr('Also unlock keyring')\n                checked: Config.options.lock.security.unlockKeyring\n                onCheckedChanged: {\n                    Config.options.lock.security.unlockKeyring = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\")\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Style: general\")\n\n            ConfigSwitch {\n                buttonIcon: \"center_focus_weak\"\n                text: Translation.tr('Center clock')\n                checked: Config.options.lock.centerClock\n                onCheckedChanged: {\n                    Config.options.lock.centerClock = checked;\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"info\"\n                text: Translation.tr('Show \"Locked\" text')\n                checked: Config.options.lock.showLockedText\n                onCheckedChanged: {\n                    Config.options.lock.showLockedText = checked;\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"shapes\"\n                text: Translation.tr('Use varying shapes for password characters')\n                checked: Config.options.lock.materialShapeChars\n                onCheckedChanged: {\n                    Config.options.lock.materialShapeChars = checked;\n                }\n            }\n        }\n        ContentSubsection {\n            title: Translation.tr(\"Style: Blurred\")\n\n            ConfigSwitch {\n                buttonIcon: \"blur_on\"\n                text: Translation.tr('Enable blur')\n                checked: Config.options.lock.blur.enable\n                onCheckedChanged: {\n                    Config.options.lock.blur.enable = checked;\n                }\n            }\n\n            ConfigSpinBox {\n                icon: \"loupe\"\n                text: Translation.tr(\"Extra wallpaper zoom (%)\")\n                value: Config.options.lock.blur.extraZoom * 100\n                from: 1\n                to: 150\n                stepSize: 2\n                onValueChanged: {\n                    Config.options.lock.blur.extraZoom = value / 100;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"notifications\"\n        title: Translation.tr(\"Notifications\")\n\n        ConfigSpinBox {\n            icon: \"av_timer\"\n            text: Translation.tr(\"Timeout duration (if not defined by notification) (ms)\")\n            value: Config.options.notifications.timeout\n            from: 1000\n            to: 60000\n            stepSize: 1000\n            onValueChanged: {\n                Config.options.notifications.timeout = value;\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"monitor\"\n            text: Translation.tr(\"Force specific monitor\")\n            checked: Config.options.notifications.forceMonitor.enable\n            onCheckedChanged: {\n                Config.options.notifications.forceMonitor.enable = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"If you have multiple monitors and want notifications to only show on one of them, enable this and enter the monitor name below (e.g., eDP-1)\")\n            }\n        }\n\n        ConfigRow {\n            enabled: Config.options.notifications.forceMonitor.enable\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Monitor name to show notifications on (e.g., eDP-1)\")\n                text: Config.options.notifications.forceMonitor.name\n                wrapMode: TextEdit.Wrap\n                onTextChanged: {\n                    Config.options.notifications.forceMonitor.name = text;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"select_window\"\n        title: Translation.tr(\"Overlay: General\")\n\n        ConfigSwitch {\n            buttonIcon: \"high_density\"\n            text: Translation.tr(\"Enable opening zoom animation\")\n            checked: Config.options.overlay.openingZoomAnimation\n            onCheckedChanged: {\n                Config.options.overlay.openingZoomAnimation = checked;\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"texture\"\n            text: Translation.tr(\"Darken screen\")\n            checked: Config.options.overlay.darkenScreen\n            onCheckedChanged: {\n                Config.options.overlay.darkenScreen = checked;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"point_scan\"\n        title: Translation.tr(\"Overlay: Crosshair\")\n\n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"Crosshair code (in Valorant's format)\")\n            text: Config.options.crosshair.code\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Config.options.crosshair.code = text;\n            }\n        }\n\n        RowLayout {\n            StyledText {\n                Layout.leftMargin: 10\n                color: Appearance.colors.colSubtext\n                font.pixelSize: Appearance.font.pixelSize.smallie\n                text: Translation.tr(\"Press Super+G to open the overlay and pin the crosshair\")\n            }\n            Item {\n                Layout.fillWidth: true\n            }\n            RippleButtonWithIcon {\n                id: editorButton\n                buttonRadius: Appearance.rounding.full\n                materialIcon: \"open_in_new\"\n                mainText: Translation.tr(\"Open editor\")\n                onClicked: {\n                    Qt.openUrlExternally(`https://www.vcrdb.net/builder?c=${Config.options.crosshair.code}`);\n                }\n                StyledToolTip {\n                    text: \"www.vcrdb.net\"\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"point_scan\"\n        title: Translation.tr(\"Overlay: Floating Image\")\n\n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"Image source\")\n            text: Config.options.overlay.floatingImage.imageSource\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Config.options.overlay.floatingImage.imageSource = text;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"screenshot_frame_2\"\n        title: Translation.tr(\"Region selector (screen snipping/Google Lens)\")\n\n        ContentSubsection {\n            title: Translation.tr(\"Hint target regions\")\n            ConfigRow {\n                ConfigSwitch {\n                    buttonIcon: \"select_window\"\n                    text: Translation.tr('Windows')\n                    checked: Config.options.regionSelector.targetRegions.windows\n                    onCheckedChanged: {\n                        Config.options.regionSelector.targetRegions.windows = checked;\n                    }\n                }\n                ConfigSwitch {\n                    buttonIcon: \"right_panel_open\"\n                    text: Translation.tr('Layers')\n                    checked: Config.options.regionSelector.targetRegions.layers\n                    onCheckedChanged: {\n                        Config.options.regionSelector.targetRegions.layers = checked;\n                    }\n                }\n                ConfigSwitch {\n                    buttonIcon: \"nearby\"\n                    text: Translation.tr('Content')\n                    checked: Config.options.regionSelector.targetRegions.content\n                    onCheckedChanged: {\n                        Config.options.regionSelector.targetRegions.content = checked;\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\")\n                    }\n                }\n            }\n        }\n        \n        ContentSubsection {\n            title: Translation.tr(\"Google Lens\")\n            \n            ConfigSelectionArray {\n                currentValue: Config.options.search.imageSearch.useCircleSelection ? \"circle\" : \"rectangles\"\n                onSelected: newValue => {\n                    Config.options.search.imageSearch.useCircleSelection = (newValue === \"circle\");\n                }\n                options: [\n                    { icon: \"activity_zone\", value: \"rectangles\", displayName: Translation.tr(\"Rectangular selection\") },\n                    { icon: \"gesture\", value: \"circle\", displayName: Translation.tr(\"Circle to Search\") }\n                ]\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Rectangular selection\")\n\n            ConfigSwitch {\n                buttonIcon: \"point_scan\"\n                text: Translation.tr(\"Show aim lines\")\n                checked: Config.options.regionSelector.rect.showAimLines\n                onCheckedChanged: {\n                    Config.options.regionSelector.rect.showAimLines = checked;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Circle selection\")\n            \n            ConfigSpinBox {\n                icon: \"eraser_size_3\"\n                text: Translation.tr(\"Stroke width\")\n                value: Config.options.regionSelector.circle.strokeWidth\n                from: 1\n                to: 20\n                stepSize: 1\n                onValueChanged: {\n                    Config.options.regionSelector.circle.strokeWidth = value;\n                }\n            }\n\n            ConfigSpinBox {\n                icon: \"screenshot_frame_2\"\n                text: Translation.tr(\"Padding\")\n                value: Config.options.regionSelector.circle.padding\n                from: 0\n                to: 100\n                stepSize: 5\n                onValueChanged: {\n                    Config.options.regionSelector.circle.padding = value;\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"side_navigation\"\n        title: Translation.tr(\"Sidebars\")\n\n        ConfigSwitch {\n            buttonIcon: \"memory\"\n            text: Translation.tr('Keep right sidebar loaded')\n            checked: Config.options.sidebar.keepRightSidebarLoaded\n            onCheckedChanged: {\n                Config.options.sidebar.keepRightSidebarLoaded = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\")\n            }\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"translate\"\n            text: Translation.tr('Enable translator')\n            checked: Config.options.sidebar.translator.enable\n            onCheckedChanged: {\n                Config.options.sidebar.translator.enable = checked;\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Quick toggles\")\n            \n            ConfigSelectionArray {\n                Layout.fillWidth: false\n                currentValue: Config.options.sidebar.quickToggles.style\n                onSelected: newValue => {\n                    Config.options.sidebar.quickToggles.style = newValue;\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"Classic\"),\n                        icon: \"password_2\",\n                        value: \"classic\"\n                    },\n                    {\n                        displayName: Translation.tr(\"Android\"),\n                        icon: \"action_key\",\n                        value: \"android\"\n                    }\n                ]\n            }\n\n            ConfigSpinBox {\n                enabled: Config.options.sidebar.quickToggles.style === \"android\"\n                icon: \"splitscreen_left\"\n                text: Translation.tr(\"Columns\")\n                value: Config.options.sidebar.quickToggles.android.columns\n                from: 1\n                to: 8\n                stepSize: 1\n                onValueChanged: {\n                    Config.options.sidebar.quickToggles.android.columns = value;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Sliders\")\n\n            ConfigSwitch {\n                buttonIcon: \"check\"\n                text: Translation.tr(\"Enable\")\n                checked: Config.options.sidebar.quickSliders.enable\n                onCheckedChanged: {\n                    Config.options.sidebar.quickSliders.enable = checked;\n                }\n            }\n            \n            ConfigSwitch {\n                buttonIcon: \"brightness_6\"\n                text: Translation.tr(\"Brightness\")\n                enabled: Config.options.sidebar.quickSliders.enable\n                checked: Config.options.sidebar.quickSliders.showBrightness\n                onCheckedChanged: {\n                    Config.options.sidebar.quickSliders.showBrightness = checked;\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"volume_up\"\n                text: Translation.tr(\"Volume\")\n                enabled: Config.options.sidebar.quickSliders.enable\n                checked: Config.options.sidebar.quickSliders.showVolume\n                onCheckedChanged: {\n                    Config.options.sidebar.quickSliders.showVolume = checked;\n                }\n            }\n\n            ConfigSwitch {\n                buttonIcon: \"mic\"\n                text: Translation.tr(\"Microphone\")\n                enabled: Config.options.sidebar.quickSliders.enable\n                checked: Config.options.sidebar.quickSliders.showMic\n                onCheckedChanged: {\n                    Config.options.sidebar.quickSliders.showMic = checked;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Corner open\")\n            tooltip: Translation.tr(\"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\")\n            ConfigRow {\n                uniform: true\n                ConfigSwitch {\n                    buttonIcon: \"check\"\n                    text: Translation.tr(\"Enable\")\n                    checked: Config.options.sidebar.cornerOpen.enable\n                    onCheckedChanged: {\n                        Config.options.sidebar.cornerOpen.enable = checked;\n                    }\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"highlight_mouse_cursor\"\n                text: Translation.tr(\"Hover to trigger\")\n                checked: Config.options.sidebar.cornerOpen.clickless\n                onCheckedChanged: {\n                    Config.options.sidebar.cornerOpen.clickless = checked;\n                }\n\n                StyledToolTip {\n                    text: Translation.tr(\"When this is off you'll have to click\")\n                }\n            }\n            Row {\n                ConfigSwitch {\n                    enabled: !Config.options.sidebar.cornerOpen.clickless\n                    text: Translation.tr(\"Force hover open at absolute corner\")\n                    checked: Config.options.sidebar.cornerOpen.clicklessCornerEnd\n                    onCheckedChanged: {\n                        Config.options.sidebar.cornerOpen.clicklessCornerEnd = checked;\n                    }\n\n                    StyledToolTip {\n                        text: Translation.tr(\"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\")\n                    }\n                }\n                ConfigSpinBox {\n                    icon: \"arrow_cool_down\"\n                    text: Translation.tr(\"with vertical offset\")\n                    value: Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset\n                    from: 0\n                    to: 20\n                    stepSize: 1\n                    onValueChanged: {\n                        Config.options.sidebar.cornerOpen.clicklessCornerVerticalOffset = value;\n                    }\n                    MouseArea {\n                        id: mouseArea\n                        anchors.fill: parent\n                        hoverEnabled: true\n                        acceptedButtons: Qt.NoButton\n                        StyledToolTip {\n                            extraVisibleCondition: mouseArea.containsMouse\n                            text: Translation.tr(\"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\")\n                        }\n                    }\n                }\n            }\n            \n            ConfigRow {\n                uniform: true\n                ConfigSwitch {\n                    buttonIcon: \"vertical_align_bottom\"\n                    text: Translation.tr(\"Place at bottom\")\n                    checked: Config.options.sidebar.cornerOpen.bottom\n                    onCheckedChanged: {\n                        Config.options.sidebar.cornerOpen.bottom = checked;\n                    }\n\n                    StyledToolTip {\n                        text: Translation.tr(\"Place the corners to trigger at the bottom\")\n                    }\n                }\n                ConfigSwitch {\n                    buttonIcon: \"unfold_more_double\"\n                    text: Translation.tr(\"Value scroll\")\n                    checked: Config.options.sidebar.cornerOpen.valueScroll\n                    onCheckedChanged: {\n                        Config.options.sidebar.cornerOpen.valueScroll = checked;\n                    }\n\n                    StyledToolTip {\n                        text: Translation.tr(\"Brightness and volume\")\n                    }\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"visibility\"\n                text: Translation.tr(\"Visualize region\")\n                checked: Config.options.sidebar.cornerOpen.visualize\n                onCheckedChanged: {\n                    Config.options.sidebar.cornerOpen.visualize = checked;\n                }\n            }\n            ConfigRow {\n                ConfigSpinBox {\n                    icon: \"arrow_range\"\n                    text: Translation.tr(\"Region width\")\n                    value: Config.options.sidebar.cornerOpen.cornerRegionWidth\n                    from: 1\n                    to: 300\n                    stepSize: 1\n                    onValueChanged: {\n                        Config.options.sidebar.cornerOpen.cornerRegionWidth = value;\n                    }\n                }\n                ConfigSpinBox {\n                    icon: \"height\"\n                    text: Translation.tr(\"Region height\")\n                    value: Config.options.sidebar.cornerOpen.cornerRegionHeight\n                    from: 1\n                    to: 300\n                    stepSize: 1\n                    onValueChanged: {\n                        Config.options.sidebar.cornerOpen.cornerRegionHeight = value;\n                    }\n                }\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"voting_chip\"\n        title: Translation.tr(\"On-screen display\")\n\n        ConfigSpinBox {\n            icon: \"av_timer\"\n            text: Translation.tr(\"Timeout (ms)\")\n            value: Config.options.osd.timeout\n            from: 100\n            to: 3000\n            stepSize: 100\n            onValueChanged: {\n                Config.options.osd.timeout = value;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"overview_key\"\n        title: Translation.tr(\"Overview\")\n\n        ConfigSwitch {\n            buttonIcon: \"check\"\n            text: Translation.tr(\"Enable\")\n            checked: Config.options.overview.enable\n            onCheckedChanged: {\n                Config.options.overview.enable = checked;\n            }\n        }\n        ConfigSwitch {\n            buttonIcon: \"center_focus_strong\"\n            text: Translation.tr(\"Center icons\")\n            checked: Config.options.overview.centerIcons\n            onCheckedChanged: {\n                Config.options.overview.centerIcons = checked;\n            }\n        }\n        ConfigSpinBox {\n            icon: \"loupe\"\n            text: Translation.tr(\"Scale (%)\")\n            value: Config.options.overview.scale * 100\n            from: 1\n            to: 100\n            stepSize: 1\n            onValueChanged: {\n                Config.options.overview.scale = value / 100;\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSpinBox {\n                icon: \"splitscreen_bottom\"\n                text: Translation.tr(\"Rows\")\n                value: Config.options.overview.rows\n                from: 1\n                to: 20\n                stepSize: 1\n                onValueChanged: {\n                    Config.options.overview.rows = value;\n                }\n            }\n            ConfigSpinBox {\n                icon: \"splitscreen_right\"\n                text: Translation.tr(\"Columns\")\n                value: Config.options.overview.columns\n                from: 1\n                to: 20\n                stepSize: 1\n                onValueChanged: {\n                    Config.options.overview.columns = value;\n                }\n            }\n        }\n        ConfigRow {\n            uniform: true\n            ConfigSelectionArray {\n                currentValue: Config.options.overview.orderRightLeft\n                onSelected: newValue => {\n                    Config.options.overview.orderRightLeft = newValue\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"Left to right\"),\n                        icon: \"arrow_forward\",\n                        value: 0\n                    },\n                    {\n                        displayName: Translation.tr(\"Right to left\"),\n                        icon: \"arrow_back\",\n                        value: 1\n                    }\n                ]\n            }\n            ConfigSelectionArray {\n                currentValue: Config.options.overview.orderBottomUp\n                onSelected: newValue => {\n                    Config.options.overview.orderBottomUp = newValue\n                }\n                options: [\n                    {\n                        displayName: Translation.tr(\"Top-down\"),\n                        icon: \"arrow_downward\",\n                        value: 0\n                    },\n                    {\n                        displayName: Translation.tr(\"Bottom-up\"),\n                        icon: \"arrow_upward\",\n                        value: 1\n                    }\n                ]\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"wallpaper_slideshow\"\n        title: Translation.tr(\"Wallpaper selector\")\n\n        ConfigSwitch {\n            buttonIcon: \"ad\"\n            text: Translation.tr('Use system file picker')\n            checked: Config.options.wallpaperSelector.useSystemFileDialog\n            onCheckedChanged: {\n                Config.options.wallpaperSelector.useSystemFileDialog = checked;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"text_format\"\n        title: Translation.tr(\"Fonts\")\n\n        ContentSubsection {\n            title: Translation.tr(\"Main font\")\n            tooltip: Translation.tr(\"Used for general UI text\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name (e.g., Google Sans Flex)\")\n                text: Config.options.appearance.fonts.main\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.main = text;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Numbers font\")\n            tooltip: Translation.tr(\"Used for displaying numbers\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name\")\n                text: Config.options.appearance.fonts.numbers\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.numbers = text;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Title font\")\n            tooltip: Translation.tr(\"Used for headings and titles\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name\")\n                text: Config.options.appearance.fonts.title\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.title = text;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Monospace font\")\n            tooltip: Translation.tr(\"Used for code and terminal\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name (e.g., JetBrains Mono NF)\")\n                text: Config.options.appearance.fonts.monospace\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.monospace = text;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Nerd font icons\")\n            tooltip: Translation.tr(\"Font used for Nerd Font icons\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name (e.g., JetBrains Mono NF)\")\n                text: Config.options.appearance.fonts.iconNerd\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.iconNerd = text;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Reading font\")\n            tooltip: Translation.tr(\"Used for reading large blocks of text\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name (e.g., Readex Pro)\")\n                text: Config.options.appearance.fonts.reading\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.reading = text;\n                }\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Expressive font\")\n            tooltip: Translation.tr(\"Used for decorative/expressive text\")\n\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Font family name (e.g., Space Grotesk)\")\n                text: Config.options.appearance.fonts.expressive\n                wrapMode: TextEdit.NoWrap\n                onTextChanged: {\n                    Config.options.appearance.fonts.expressive = text;\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/QuickConfig.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nContentPage {\n    forceWidth: true\n\n    Process {\n        id: randomWallProc\n        property string status: \"\"\n        property string scriptPath: `${Directories.scriptPath}/colors/random/random_konachan_wall.sh`\n        command: [\"bash\", \"-c\", FileUtils.trimFileProtocol(randomWallProc.scriptPath)]\n        stdout: SplitParser {\n            onRead: data => {\n                randomWallProc.status = data.trim();\n            }\n        }\n    }\n\n    component SmallLightDarkPreferenceButton: RippleButton {\n        id: smallLightDarkPreferenceButton\n        required property bool dark\n        property color colText: toggled ? Appearance.colors.colOnPrimary : Appearance.colors.colOnLayer2\n        padding: 5\n        Layout.fillWidth: true\n        toggled: Appearance.m3colors.darkmode === dark\n        colBackground: Appearance.colors.colLayer2\n        onClicked: {\n            Quickshell.execDetached([\"bash\", \"-c\", `${Directories.wallpaperSwitchScriptPath} --mode ${dark ? \"dark\" : \"light\"} --noswitch`]);\n        }\n        contentItem: Item {\n            anchors.centerIn: parent\n            ColumnLayout {\n                anchors.centerIn: parent\n                spacing: 0\n                MaterialSymbol {\n                    Layout.alignment: Qt.AlignHCenter\n                    iconSize: 30\n                    text: dark ? \"dark_mode\" : \"light_mode\"\n                    color: smallLightDarkPreferenceButton.colText\n                }\n                StyledText {\n                    Layout.alignment: Qt.AlignHCenter\n                    text: dark ? Translation.tr(\"Dark\") : Translation.tr(\"Light\")\n                    font.pixelSize: Appearance.font.pixelSize.smaller\n                    color: smallLightDarkPreferenceButton.colText\n                }\n            }\n        }\n    }\n\n    // Wallpaper selection\n    ContentSection {\n        icon: \"format_paint\"\n        title: Translation.tr(\"Wallpaper & Colors\")\n        Layout.fillWidth: true\n\n        RowLayout {\n            Layout.fillWidth: true\n\n            Item {\n                implicitWidth: 340\n                implicitHeight: 200\n                \n                StyledImage {\n                    id: wallpaperPreview\n                    anchors.fill: parent\n                    fillMode: Image.PreserveAspectCrop\n                    source: Config.options.background.wallpaperPath\n                    cache: false\n                    layer.enabled: true\n                    layer.effect: OpacityMask {\n                        maskSource: Rectangle {\n                            width: 360\n                            height: 200\n                            radius: Appearance.rounding.normal\n                        }\n                    }\n                }\n            }\n\n            ColumnLayout {\n                RippleButtonWithIcon {\n                    enabled: !randomWallProc.running\n                    visible: Config.options.policies.weeb === 1\n                    Layout.fillWidth: true\n                    buttonRadius: Appearance.rounding.small\n                    materialIcon: \"ifl\"\n                    mainText: randomWallProc.running ? Translation.tr(\"Be patient...\") : Translation.tr(\"Random: Konachan\")\n                    onClicked: {\n                        randomWallProc.scriptPath = `${Directories.scriptPath}/colors/random/random_konachan_wall.sh`;\n                        randomWallProc.running = true;\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\")\n                    }\n                }\n                RippleButtonWithIcon {\n                    enabled: !randomWallProc.running\n                    visible: Config.options.policies.weeb === 1\n                    Layout.fillWidth: true\n                    buttonRadius: Appearance.rounding.small\n                    materialIcon: \"ifl\"\n                    mainText: randomWallProc.running ? Translation.tr(\"Be patient...\") : Translation.tr(\"Random: osu! seasonal\")\n                    onClicked: {\n                        randomWallProc.scriptPath = `${Directories.scriptPath}/colors/random/random_osu_wall.sh`;\n                        randomWallProc.running = true;\n                    }\n                    StyledToolTip {\n                        text: Translation.tr(\"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\")\n                    }\n                }\n                RippleButtonWithIcon {\n                    Layout.fillWidth: true\n                    materialIcon: \"wallpaper\"\n                    StyledToolTip {\n                        text: Translation.tr(\"Pick wallpaper image on your system\")\n                    }\n                    onClicked: {\n                        Quickshell.execDetached(`${Directories.wallpaperSwitchScriptPath}`);\n                    }\n                    mainContentComponent: Component {\n                        RowLayout {\n                            spacing: 10\n                            StyledText {\n                                font.pixelSize: Appearance.font.pixelSize.small\n                                text: Translation.tr(\"Choose file\")\n                                color: Appearance.colors.colOnSecondaryContainer\n                            }\n                            RowLayout {\n                                spacing: 3\n                                KeyboardKey {\n                                    key: \"Ctrl\"\n                                }\n                                KeyboardKey {\n                                    key: Config.options.cheatsheet.superKey ?? \"󰖳\"\n                                }\n                                StyledText {\n                                    Layout.alignment: Qt.AlignVCenter\n                                    text: \"+\"\n                                }\n                                KeyboardKey {\n                                    key: \"T\"\n                                }\n                            }\n                        }\n                    }\n                }\n                RowLayout {\n                    Layout.alignment: Qt.AlignHCenter\n                    Layout.fillWidth: true\n                    Layout.fillHeight: true\n                    uniformCellSizes: true\n\n                    SmallLightDarkPreferenceButton {\n                        Layout.fillHeight: true\n                        dark: false\n                    }\n                    SmallLightDarkPreferenceButton {\n                        Layout.fillHeight: true\n                        dark: true\n                    }\n                }\n            }\n        }\n\n        ConfigSelectionArray {\n            currentValue: Config.options.appearance.palette.type\n            onSelected: newValue => {\n                Config.options.appearance.palette.type = newValue;\n                Quickshell.execDetached([\"bash\", \"-c\", `${Directories.wallpaperSwitchScriptPath} --noswitch`]);\n            }\n            options: [\n                {\n                    \"value\": \"auto\",\n                    \"displayName\": Translation.tr(\"Auto\")\n                },\n                {\n                    \"value\": \"scheme-content\",\n                    \"displayName\": Translation.tr(\"Content\")\n                },\n                {\n                    \"value\": \"scheme-expressive\",\n                    \"displayName\": Translation.tr(\"Expressive\")\n                },\n                {\n                    \"value\": \"scheme-fidelity\",\n                    \"displayName\": Translation.tr(\"Fidelity\")\n                },\n                {\n                    \"value\": \"scheme-fruit-salad\",\n                    \"displayName\": Translation.tr(\"Fruit Salad\")\n                },\n                {\n                    \"value\": \"scheme-monochrome\",\n                    \"displayName\": Translation.tr(\"Monochrome\")\n                },\n                {\n                    \"value\": \"scheme-neutral\",\n                    \"displayName\": Translation.tr(\"Neutral\")\n                },\n                {\n                    \"value\": \"scheme-rainbow\",\n                    \"displayName\": Translation.tr(\"Rainbow\")\n                },\n                {\n                    \"value\": \"scheme-tonal-spot\",\n                    \"displayName\": Translation.tr(\"Tonal Spot\")\n                }\n            ]\n        }\n\n        ConfigSwitch {\n            buttonIcon: \"ev_shadow\"\n            text: Translation.tr(\"Transparency\")\n            checked: Config.options.appearance.transparency.enable\n            onCheckedChanged: {\n                Config.options.appearance.transparency.enable = checked;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"screenshot_monitor\"\n        title: Translation.tr(\"Bar & screen\")\n\n        ConfigRow {\n            ContentSubsection {\n                title: Translation.tr(\"Bar position\")\n                ConfigSelectionArray {\n                    currentValue: (Config.options.bar.bottom ? 1 : 0) | (Config.options.bar.vertical ? 2 : 0)\n                    onSelected: newValue => {\n                        Config.options.bar.bottom = (newValue & 1) !== 0;\n                        Config.options.bar.vertical = (newValue & 2) !== 0;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Top\"),\n                            icon: \"arrow_upward\",\n                            value: 0 // bottom: false, vertical: false\n                        },\n                        {\n                            displayName: Translation.tr(\"Left\"),\n                            icon: \"arrow_back\",\n                            value: 2 // bottom: false, vertical: true\n                        },\n                        {\n                            displayName: Translation.tr(\"Bottom\"),\n                            icon: \"arrow_downward\",\n                            value: 1 // bottom: true, vertical: false\n                        },\n                        {\n                            displayName: Translation.tr(\"Right\"),\n                            icon: \"arrow_forward\",\n                            value: 3 // bottom: true, vertical: true\n                        }\n                    ]\n                }\n            }\n            ContentSubsection {\n                title: Translation.tr(\"Bar style\")\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.bar.cornerStyle\n                    onSelected: newValue => {\n                        Config.options.bar.cornerStyle = newValue; // Update local copy\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"Hug\"),\n                            icon: \"line_curve\",\n                            value: 0\n                        },\n                        {\n                            displayName: Translation.tr(\"Float\"),\n                            icon: \"page_header\",\n                            value: 1\n                        },\n                        {\n                            displayName: Translation.tr(\"Rect\"),\n                            icon: \"toolbar\",\n                            value: 2\n                        }\n                    ]\n                }\n            }\n        }\n\n        ConfigRow {\n            ContentSubsection {\n                title: Translation.tr(\"Screen round corner\")\n\n                ConfigSelectionArray {\n                    currentValue: Config.options.appearance.fakeScreenRounding\n                    onSelected: newValue => {\n                        Config.options.appearance.fakeScreenRounding = newValue;\n                    }\n                    options: [\n                        {\n                            displayName: Translation.tr(\"No\"),\n                            icon: \"close\",\n                            value: 0\n                        },\n                        {\n                            displayName: Translation.tr(\"Yes\"),\n                            icon: \"check\",\n                            value: 1\n                        },\n                        {\n                            displayName: Translation.tr(\"When not fullscreen\"),\n                            icon: \"fullscreen_exit\",\n                            value: 2\n                        }\n                    ]\n                }\n            }\n            \n        }\n    }\n\n    NoticeBox {\n        Layout.fillWidth: true\n        text: Translation.tr('Not all options are available in this app. You should also check the config file by hitting the \"Config file\" button on the topleft corner or opening %1 manually.').arg(Directories.shellConfigPath)\n\n        Item {\n            Layout.fillWidth: true\n        }\n        RippleButtonWithIcon {\n            id: copyPathButton\n            property bool justCopied: false\n            Layout.fillWidth: false\n            buttonRadius: Appearance.rounding.small\n            materialIcon: justCopied ? \"check\" : \"content_copy\"\n            mainText: justCopied ? Translation.tr(\"Path copied\") : Translation.tr(\"Copy path\")\n            onClicked: {\n                copyPathButton.justCopied = true\n                Quickshell.clipboardText = FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`);\n                revertTextTimer.restart();\n            }\n            colBackground: ColorUtils.transparentize(Appearance.colors.colPrimaryContainer)\n            colBackgroundHover: Appearance.colors.colPrimaryContainerHover\n            colRipple: Appearance.colors.colPrimaryContainerActive\n\n            Timer {\n                id: revertTextTimer\n                interval: 1500\n                onTriggered: {\n                    copyPathButton.justCopied = false\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/settings/ServicesConfig.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nContentPage {\n    forceWidth: true\n\n    ContentSection {\n        icon: \"neurology\"\n        title: Translation.tr(\"AI\")\n\n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"System prompt\")\n            text: Config.options.ai.systemPrompt\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Qt.callLater(() => {\n                    Config.options.ai.systemPrompt = text;\n                });\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"music_cast\"\n        title: Translation.tr(\"Music Recognition\")\n\n        ConfigSpinBox {\n            icon: \"timer_off\"\n            text: Translation.tr(\"Total duration timeout (s)\")\n            value: Config.options.musicRecognition.timeout\n            from: 10\n            to: 100\n            stepSize: 2\n            onValueChanged: {\n                Config.options.musicRecognition.timeout = value;\n            }\n        }\n        ConfigSpinBox {\n            icon: \"av_timer\"\n            text: Translation.tr(\"Polling interval (s)\")\n            value: Config.options.musicRecognition.interval\n            from: 2\n            to: 10\n            stepSize: 1\n            onValueChanged: {\n                Config.options.musicRecognition.interval = value;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"cell_tower\"\n        title: Translation.tr(\"Networking\")\n\n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"User agent (for services that require it)\")\n            text: Config.options.networking.userAgent\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Config.options.networking.userAgent = text;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"memory\"\n        title: Translation.tr(\"Resources\")\n\n        ConfigSpinBox {\n            icon: \"av_timer\"\n            text: Translation.tr(\"Polling interval (ms)\")\n            value: Config.options.resources.updateInterval\n            from: 100\n            to: 10000\n            stepSize: 100\n            onValueChanged: {\n                Config.options.resources.updateInterval = value;\n            }\n        }\n        \n    }\n\n    ContentSection {\n        icon: \"file_open\"\n        title: Translation.tr(\"Save paths\")\n\n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"Video Recording Path\")\n            text: Config.options.screenRecord.savePath\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Config.options.screenRecord.savePath = text;\n            }\n        }\n        \n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"Screenshot Path (leave empty to just copy)\")\n            text: Config.options.screenSnip.savePath\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Config.options.screenSnip.savePath = text;\n            }\n        }\n    }\n\n    ContentSection {\n        icon: \"search\"\n        title: Translation.tr(\"Search\")\n\n        ConfigSwitch {\n            text: Translation.tr(\"Use Levenshtein distance-based algorithm instead of fuzzy\")\n            checked: Config.options.search.sloppy\n            onCheckedChanged: {\n                Config.options.search.sloppy = checked;\n            }\n            StyledToolTip {\n                text: Translation.tr(\"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\")\n            }\n        }\n\n        ContentSubsection {\n            title: Translation.tr(\"Prefixes\")\n            ConfigRow {\n                uniform: true\n                MaterialTextArea {\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Action\")\n                    text: Config.options.search.prefix.action\n                    wrapMode: TextEdit.Wrap\n                    onTextChanged: {\n                        Config.options.search.prefix.action = text;\n                    }\n                }\n                MaterialTextArea {\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Clipboard\")\n                    text: Config.options.search.prefix.clipboard\n                    wrapMode: TextEdit.Wrap\n                    onTextChanged: {\n                        Config.options.search.prefix.clipboard = text;\n                    }\n                }\n                MaterialTextArea {\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Emojis\")\n                    text: Config.options.search.prefix.emojis\n                    wrapMode: TextEdit.Wrap\n                    onTextChanged: {\n                        Config.options.search.prefix.emojis = text;\n                    }\n                }\n            }\n\n            ConfigRow {\n                uniform: true\n                MaterialTextArea {\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Math\")\n                    text: Config.options.search.prefix.math\n                    wrapMode: TextEdit.Wrap\n                    onTextChanged: {\n                        Config.options.search.prefix.math = text;\n                    }\n                }\n                MaterialTextArea {\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Shell command\")\n                    text: Config.options.search.prefix.shellCommand\n                    wrapMode: TextEdit.Wrap\n                    onTextChanged: {\n                        Config.options.search.prefix.shellCommand = text;\n                    }\n                }\n                MaterialTextArea {\n                    Layout.fillWidth: true\n                    placeholderText: Translation.tr(\"Web search\")\n                    text: Config.options.search.prefix.webSearch\n                    wrapMode: TextEdit.Wrap\n                    onTextChanged: {\n                        Config.options.search.prefix.webSearch = text;\n                    }\n                }\n            }\n        }\n        ContentSubsection {\n            title: Translation.tr(\"Web search\")\n            MaterialTextArea {\n                Layout.fillWidth: true\n                placeholderText: Translation.tr(\"Base URL\")\n                text: Config.options.search.engineBaseUrl\n                wrapMode: TextEdit.Wrap\n                onTextChanged: {\n                    Config.options.search.engineBaseUrl = text;\n                }\n            }\n        }\n    }\n\n    // There's no update indicator in ii for now so we shouldn't show this yet\n    // ContentSection {\n    //     icon: \"deployed_code_update\"\n    //     title: Translation.tr(\"System updates (Arch only)\")\n\n    //     ConfigSwitch {\n    //         text: Translation.tr(\"Enable update checks\")\n    //         checked: Config.options.updates.enableCheck\n    //         onCheckedChanged: {\n    //             Config.options.updates.enableCheck = checked;\n    //         }\n    //     }\n\n    //     ConfigSpinBox {\n    //         icon: \"av_timer\"\n    //         text: Translation.tr(\"Check interval (mins)\")\n    //         value: Config.options.updates.checkInterval\n    //         from: 60\n    //         to: 1440\n    //         stepSize: 60\n    //         onValueChanged: {\n    //             Config.options.updates.checkInterval = value;\n    //         }\n    //     }\n    // }\n\n    ContentSection {\n        icon: \"weather_mix\"\n        title: Translation.tr(\"Weather\")\n        ConfigRow {\n            ConfigSwitch {\n                buttonIcon: \"assistant_navigation\"\n                text: Translation.tr(\"Enable GPS based location\")\n                checked: Config.options.bar.weather.enableGPS\n                onCheckedChanged: {\n                    Config.options.bar.weather.enableGPS = checked;\n                }\n            }\n            ConfigSwitch {\n                buttonIcon: \"thermometer\"\n                text: Translation.tr(\"Fahrenheit unit\")\n                checked: Config.options.bar.weather.useUSCS\n                onCheckedChanged: {\n                    Config.options.bar.weather.useUSCS = checked;\n                }\n                StyledToolTip {\n                    text: Translation.tr(\"It may take a few seconds to update\")\n                }\n            }\n        }\n        \n        MaterialTextArea {\n            Layout.fillWidth: true\n            placeholderText: Translation.tr(\"City name\")\n            text: Config.options.bar.weather.city\n            wrapMode: TextEdit.Wrap\n            onTextChanged: {\n                Config.options.bar.weather.city = text;\n            }\n        }\n        ConfigSpinBox {\n            icon: \"av_timer\"\n            text: Translation.tr(\"Polling interval (m)\")\n            value: Config.options.bar.weather.fetchInterval\n            from: 5\n            to: 50\n            stepSize: 5\n            onValueChanged: {\n                Config.options.bar.weather.fetchInterval = value;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/README.md",
    "content": "## Waffle\n\nA recreation of Windoes. It's WIP!\n\n- If you install illogical-impulse fully, you can press Super+Alt+W to switch to this style.\n- If you're just copying the Quickshell config, run the config as usual (`qs -c ii`) then run `qs -c ii ipc call panelFamily cycle`\n\n## From EWW version to Quickshell\n\nJust a reflection, in case anyone's interested. My blog is probably a better place for this, but it does not exist. Besides, this is going to change as I do more stuff. Currently there's just the bar.\n\n### Improvements\n\n- QtQuick's `Button` has the `{top/bottom/left/right}Inset` properties, so we can have clickable regions expanding beyond the button background for free. With EWW it was annoying to wrap the button content with an `eventbox` that has some padding, then somehow use CSS selectors to make sure hovering effects work. I have to admit, (a large) part of that annoyance was with how bad my copy-pasting coding practice was at the time, but still...\n\n- Fancy effects: Gtk3 CSS does not support transformations. In QtQuick we can smack `rotation` and `scale` almost everywhere, so it's simple to make bouncy icons and rotating chevrons\n\n- Quickshell provides a system tray service (EWW does now but didn't at the time I created the EWW Windoes version), so now there's no Waybar needed for the tray.\n\n- QtQuick has `Loader`s, so we can have this live-switchable from the main style without killing the widget system, moving styles to the correct folder, and relaunching.\n\n- This time my computer is powerful enough to run a VM, so I don't have to occasionally reboot to take quick screenshots for reference. I try to make everything pixel-perfect so this is necessary. Speaking about pixel-perfectness, in the EWW version I hardcoded sizes, but this time I'm still doing that (lol), BUT that's normal and not a problem because Qt has the `QT_SCALE_FACTOR` env var for scaling. (Please feel free to prove me wrong in saying Gtk3 doesn't have that magic)\n\n### Challenges\n\n- Qt is not Gtk and definitely not React\n  - We don't get directional border on QtQuick `Rectangle`s like in CSS. I was able to get around this with manual drawing, but it was a bit more work\n\n- Fluent Icons is difficult to use, compared to Material Symbols\n  - No React, so no clean use via a library.\n  - If we use the font, there's no proper, searchable **codepoint** cheatsheet like Nerd Fonts, and there's no ligatures\n  - I resorted to downloading individual SVGs. Not that nice, but it's better than scanning the whole table of icons every time I want one. For this we have fluenticon.com and fluenticons.co, but icons are awkwardly named and there's no alias. Why is the reload/refresh icon called \"arrow-sync\"? Well, the name is not misleading, but arguably reload/refresh are more common actions. From Fluent Design's [page on Iconography](https://fluent2.microsoft.design/iconography):\n\n  > Fluent system icons are literal metaphors and are named for the shape or object they represent, not the functionality they provide\n\n  \"sync\" is functionality.\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter.mainPage\n\nWBarAttachedPanelContent {\n    id: root\n\n    readonly property bool barAtBottom: Config.options.waffles.bar.bottom\n    \n    contentItem: ColumnLayout {\n        // This somewhat sophisticated anchoring is needed to make opening anim not jump abruptly when stuff appear\n        anchors {\n            left: parent.left\n            right: parent.right\n            top: root.barAtBottom ? undefined : parent.top\n            bottom: root.barAtBottom ? parent.bottom : undefined\n            margins: root.visualMargin\n            bottomMargin: 0\n        }\n        spacing: 12\n\n        WPane {\n            opacity: (MprisController.activePlayer != null && MprisController.isRealPlayer(MprisController.activePlayer)) ? 1 : 0\n            Layout.fillWidth: true\n            contentItem: MediaPaneContent {}\n        }\n        WPane {\n            Layout.fillWidth: true\n            contentItem: WStackView {\n                id: stackView\n                implicitWidth: initItem.implicitWidth\n                implicitHeight: initItem.implicitHeight\n\n                initialItem: WPanelPageColumn {\n                    id: initItem\n                    MainPageBody {}\n                    WPanelSeparator {}\n                    MainPageFooter {}\n                }\n\n                Component.onCompleted: {\n                    ActionCenterContext.stackView = this;\n                }\n\n                MouseArea {\n                    anchors.fill: parent\n                    acceptedButtons: Qt.BackButton\n                    onClicked: {\n                        ActionCenterContext.back();\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/ActionCenterContext.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport Quickshell\n\nSingleton {\n    id: root\n    \n    property StackView stackView\n\n    function push(component) {\n        if (stackView) {\n            stackView.push(component)\n        }\n    }\n\n    function back() {\n        if (stackView && stackView.depth > 1) {\n            stackView.pop()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/ExpandableChoiceButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nWChoiceButton {\n    id: root\n\n    property bool expanded: false\n    checked: expanded\n    clip: true\n\n    horizontalPadding: 12\n    verticalPadding: 6\n    animateChoiceHighlight: false\n\n    Behavior on implicitHeight {\n        animation: Looks.transition.resize.createObject(this)\n    }\n    onClicked: expanded = !expanded\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/HeaderRow.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nRowLayout {\n    id: root\n\n    required property string title\n    spacing: 4\n\n    WPanelIconButton {\n        iconName: \"arrow-left\"\n        onClicked: ActionCenterContext.back()\n    }\n\n    WText {\n        id: titleText\n        Layout.fillWidth: true\n        elide: Text.ElideRight\n        text: root.title\n        font.pixelSize: Looks.font.pixelSize.large\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/MediaPaneContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nRectangle {\n    id: root\n    implicitHeight: 176\n    implicitWidth: 358\n    color: Looks.colors.bgPanelBody\n    anchors.fill: parent\n\n    readonly property var activePlayer: MprisController.activePlayer\n\n    Column {\n        anchors {\n            fill: parent\n            leftMargin: 23\n            rightMargin: 23\n            topMargin: 16\n            bottomMargin: 20\n        }\n        spacing: 25\n\n        AppInfoRow {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n        }\n\n        TrackInfoRow {\n            anchors {\n                left: parent.left\n                right: parent.right\n            }\n        }\n\n        ControlButtonsRow {\n            anchors.horizontalCenter: parent.horizontalCenter\n        }\n    }\n\n    component AppInfoRow: RowLayout {\n        id: appInfo\n        spacing: 8\n\n        property var desktopEntry: {\n            const desktopEntryString = root.activePlayer?.desktopEntry ?? \"\";\n            return DesktopEntries.byId(desktopEntryString);\n        }\n\n        FluentIcon {\n            implicitSize: 20\n            icon: appInfo.desktopEntry?.icon || \"music-note-2\"\n            monochrome: !appInfo.desktopEntry?.icon\n        }\n\n        WText {\n            Layout.fillWidth: true\n            text: appInfo.desktopEntry?.name ?? Translation.tr(\"Media\")\n            horizontalAlignment: Text.AlignLeft\n            elide: Text.ElideRight\n        }\n    }\n\n    component TrackInfoRow: RowLayout {\n        spacing: 16\n\n        ColumnLayout {\n            id: trackInfo\n            Layout.fillWidth: true\n            spacing: 0\n\n            WText {\n                Layout.fillWidth: true\n                font.weight: Looks.font.weight.strong\n                font.pixelSize: Looks.font.pixelSize.large\n                elide: Text.ElideRight\n                text: StringUtils.cleanMusicTitle(root.activePlayer?.trackTitle) || Translation.tr(\"Unknown Title\")\n            }\n\n            WText {\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                text: root.activePlayer?.trackArtist || Translation.tr(\"Unknown Artist\")\n            }\n        }\n\n        StyledImage {\n            id: artImage\n            Layout.preferredWidth: 58\n            Layout.preferredHeight: trackInfo.implicitHeight\n            source: MprisController.activeTrack?.artUrl || \"\"\n            fillMode: Image.PreserveAspectFit\n\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Item {\n                    width: artImage.width\n                    height: artImage.height\n                    Rectangle {\n                        anchors.centerIn: parent\n                        width: artImage.paintedWidth\n                        height: artImage.paintedHeight\n                        radius: Looks.radius.medium\n                    }\n                }\n            }\n        }\n    }\n\n    component ControlButtonsRow: RowLayout {\n        spacing: 26\n\n        MediaControlButton {\n            iconName: \"previous\"\n            enabled: root.activePlayer?.canGoPrevious ?? false\n            onClicked: root.activePlayer?.previous()\n        }\n        MediaControlButton {\n            readonly property bool playing: root.activePlayer?.isPlaying ?? false\n            iconName: playing ? \"pause\" : \"play\"\n            enabled: (playing && root.activePlayer?.canPause) || (!playing && root.activePlayer?.canPlay)\n            onClicked: root.activePlayer?.togglePlaying()\n        }\n        MediaControlButton {\n            iconName: \"next\"\n            enabled: root.activePlayer?.canGoNext ?? false\n            onClicked: root.activePlayer?.next()\n        }\n    }\n\n    component MediaControlButton: WBorderlessButton {\n        id: controlButton\n        required property string iconName\n        implicitHeight: 40\n        implicitWidth: 40\n\n        contentItem: Item {\n            FluentIcon {\n                anchors.centerIn: parent\n                icon: controlButton.iconName\n                monochrome: true\n                filled: true\n                implicitSize: 18\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/SectionText.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nWText {\n    Layout.leftMargin: 12\n    Layout.rightMargin: 12\n    Layout.topMargin: 6\n    Layout.bottomMargin: 6\n    \n    font {\n        weight: Looks.font.weight.stronger\n        pixelSize: Looks.font.pixelSize.large\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/ToggleItem.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Pipewire\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nRowLayout {\n    id: root\n\n    required property string name\n    property alias description: descriptionText.text\n    property alias iconName: iconWidget.icon\n    property alias checked: switchWidget.checked\n\n    spacing: 10\n\n    FluentIcon {\n        id: iconWidget\n        visible: !!root.iconName\n        Layout.leftMargin: 12\n        Layout.topMargin: 4\n        Layout.bottomMargin: 4\n        Layout.alignment: Qt.AlignTop\n        icon: root.iconName\n        implicitSize: 18\n    }\n\n    ColumnLayout {\n        Layout.topMargin: 4\n        Layout.bottomMargin: 4\n        Layout.alignment: Qt.AlignTop\n        Layout.fillWidth: true\n        spacing: 1\n\n        // Name\n        WText {\n            Layout.fillWidth: true\n            elide: Text.ElideRight\n            font.pixelSize: Looks.font.pixelSize.large\n            text: root.name\n        }\n        // Description\n        WText {\n            id: descriptionText\n            Layout.fillWidth: true\n            wrapMode: Text.Wrap\n            color: Looks.colors.subfg\n        }\n    }\n\n    MouseArea {\n        Layout.rightMargin: 12\n        implicitWidth: switchRow.implicitWidth\n        implicitHeight: switchRow.implicitHeight\n        onPressed: switchWidget.down = true\n        onReleased: switchWidget.down = false\n        onClicked: switchWidget.checked = !switchWidget.checked\n\n        Row {\n            id: switchRow\n            spacing: 12\n\n            WTextWithFixedWidth {\n                longestText: \"Off\" // The larger one\n                text: switchWidget.checked ? Translation.tr(\"On\") : Translation.tr(\"Off\")\n                font.pixelSize: Looks.font.pixelSize.large\n            }\n            \n            WSwitch {\n                id: switchWidget\n                Layout.alignment: Qt.AlignVCenter\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/WaffleActionCenter.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nScope {\n    id: root\n\n    Connections {\n        target: GlobalStates\n\n        function onSidebarLeftOpenChanged() {\n            if (GlobalStates.sidebarLeftOpen)\n                panelLoader.active = true;\n        }\n    }\n\n    Loader {\n        id: panelLoader\n        active: GlobalStates.sidebarLeftOpen\n        sourceComponent: PanelWindow {\n            id: panelWindow\n            exclusiveZone: 0\n            WlrLayershell.namespace: \"quickshell:actionCenter\"\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n            color: \"transparent\"\n\n            anchors {\n                bottom: Config.options.waffles.bar.bottom\n                top: !Config.options.waffles.bar.bottom\n                right: true\n            }\n\n            implicitWidth: content.implicitWidth\n            implicitHeight: content.implicitHeight\n\n            HyprlandFocusGrab {\n                id: focusGrab\n                active: true\n                windows: [panelWindow]\n                onCleared: content.close()\n            }\n\n            Connections {\n                target: GlobalStates\n                function onSidebarLeftOpenChanged() {\n                    if (!GlobalStates.sidebarLeftOpen)\n                        content.close();\n                }\n            }\n\n            ActionCenterContent {\n                id: content\n                anchors.fill: parent\n\n                onClosed: {\n                    GlobalStates.sidebarLeftOpen = false;\n                    panelLoader.active = false;\n                }\n            }\n        }\n    }\n\n    function toggleOpen() {\n        GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n    }\n\n    IpcHandler {\n        target: \"sidebarLeft\"\n\n        function toggle() {\n            root.toggleOpen();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarLeftToggle\"\n        description: \"Toggles left sidebar on press\"\n\n        onPressed: root.toggleOpen()\n    }\n\n    IpcHandler {\n        target: \"mediaControls\"\n\n        function toggle(): void {\n            GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"mediaControlsToggle\"\n        description: \"Toggles media controls on press\"\n\n        onPressed: {\n            GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothControl.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Bluetooth\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nItem {\n    id: root\n\n    Component.onCompleted: {\n        if (Bluetooth.defaultAdapter.enabled) Bluetooth.defaultAdapter.discovering = true;\n    }\n    Component.onDestruction: {\n        Bluetooth.defaultAdapter.discovering = false;\n    }\n\n    WPanelPageColumn {\n        anchors.fill: parent\n\n        BodyRectangle {\n            implicitHeight: 400\n            implicitWidth: 50\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 4\n                spacing: 4\n\n                ColumnLayout {\n                    implicitHeight: headerRow.implicitHeight\n                    Layout.fillWidth: true\n                    spacing: 0\n                    RowLayout {\n                        Layout.fillWidth: true\n                        spacing: 0\n                        HeaderRow {\n                            id: headerRow\n                            Layout.fillWidth: true\n                            title: Translation.tr(\"Bluetooth\")\n                        }\n                        WSwitch {\n                            id: toggleSwitch\n                            Layout.rightMargin: 12\n                            checked: Bluetooth.defaultAdapter?.enabled ?? false\n                            onCheckedChanged: {\n                                if (Bluetooth.defaultAdapter) {\n                                    Bluetooth.defaultAdapter.enabled = checked;\n                                    if (checked) {\n                                        Bluetooth.defaultAdapter.discovering = true;\n                                    } else {\n                                        Bluetooth.defaultAdapter.discovering = false;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    FadeLoader {\n                        Layout.leftMargin: -4\n                        Layout.rightMargin: -4\n                        Layout.fillWidth: true\n                        shown: Bluetooth.defaultAdapter?.discovering ?? false\n                        visible: true\n                        sourceComponent: WIndeterminateProgressBar {}\n                    }\n                }\n\n                StyledListView {\n                    id: listView\n                    Layout.fillHeight: true\n                    Layout.fillWidth: true\n                    animateAppearance: false\n\n                    contentHeight: contentLayout.implicitHeight\n                    contentWidth: width\n                    clip: true\n                    spacing: 4\n\n                    model: ScriptModel {\n                        values: BluetoothStatus.friendlyDeviceList\n                    }\n                    delegate: BluetoothDeviceItem {\n                        required property BluetoothDevice modelData\n                        device: modelData\n                        width: ListView.view.width\n                    }\n                }\n            }\n        }\n\n        WPanelSeparator {}\n\n        FooterRectangle {\n            WTextButton {\n                anchors {\n                    verticalCenter: parent.verticalCenter\n                    left: parent.left\n                }\n                text: Translation.tr(\"More Bluetooth settings\")\n                onClicked: {\n                    Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"sidebarLeft\", \"toggle\"]);\n                    Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.bluetooth]);\n                }\n            }\n            WBorderlessButton {\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.right: parent.right\n                anchors.rightMargin: 12\n                enabled: !Bluetooth.defaultAdapter?.discovering && Bluetooth.defaultAdapter?.enabled\n\n                onClicked: {\n                    Bluetooth.defaultAdapter.discovering = true;\n                }\n\n                contentItem: FluentIcon {\n                    icon: \"arrow-counterclockwise\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/bluetooth/BluetoothDeviceItem.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Bluetooth\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nExpandableChoiceButton {\n    id: root\n    required property BluetoothDevice device\n\n    contentItem: RowLayout {\n        id: contentItem\n        spacing: 20\n\n        // Device icon\n        FluentIcon {\n            Layout.topMargin: 4\n            Layout.bottomMargin: 4\n            Layout.alignment: Qt.AlignTop\n            icon: WIcons.bluetoothDeviceIcon(root?.device)\n            implicitSize: 18\n        }\n\n        ColumnLayout {\n            Layout.topMargin: 4\n            Layout.bottomMargin: 4\n            Layout.alignment: Qt.AlignTop\n            Layout.fillWidth: true\n            spacing: 0\n\n            WText {\n                // Network name\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                font.pixelSize: Looks.font.pixelSize.large\n                text: root.device?.name || Translation.tr(\"Unknown device\")\n                textFormat: Text.PlainText\n            }\n            WText { // Status\n                id: statusText\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                font.pixelSize: Looks.font.pixelSize.large\n                color: Looks.colors.subfg\n                visible: root.device?.connected || root.expanded\n                Behavior on opacity {\n                    animation: Looks.transition.opacity.createObject(this)\n                }\n                text: {\n                    if (!root.device?.paired)\n                        return Translation.tr(\"Not connected\");\n                    let statusText = root.device?.connected ? Translation.tr(\"Connected\") : Translation.tr(\"Paired\");\n                    if (!root.device?.batteryAvailable)\n                        return statusText;\n                    statusText += ` • ${Math.round(root.device?.battery * 100)}%`;\n                    return statusText;\n                }\n            }\n\n            WButton {\n                Layout.alignment: Qt.AlignRight\n                horizontalAlignment: Text.AlignHCenter\n                visible: root.expanded\n                checked: !(root.device?.connected ?? false)\n                colBackground: Looks.colors.bg2\n                colBackgroundHover: Looks.colors.bg2Hover\n                colBackgroundActive: Looks.colors.bg2Active\n                implicitHeight: 30\n                implicitWidth: 148\n                text: root.device?.connected ? Translation.tr(\"Disconnect\") : Translation.tr(\"Connect\")\n\n                onClicked: {\n                    if (root.device?.connected) {\n                        root.device.disconnect();\n                    } else {\n                        root.device.connect();\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBody.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nBodyRectangle {\n    id: root\n    implicitHeight: contentLayout.implicitHeight\n\n    ColumnLayout {\n        id: contentLayout\n        anchors.fill: parent\n        spacing: 0\n\n        MainPageBodyToggles {\n            id: togglesContainer\n            Layout.fillWidth: true\n        }\n\n        Rectangle {\n            implicitHeight: 1\n            Layout.fillWidth: true\n            color: Looks.colors.bg1Border\n        }\n\n        MainPageBodySliders {\n            Layout.margins: 12\n            Layout.topMargin: 18\n            Layout.bottomMargin: 14\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodySliders.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\nimport qs.modules.waffle.actionCenter.volumeControl\n\nColumnLayout {\n    id: root\n    property var screen: root.QsWindow.window?.screen\n    property var brightnessMonitor: Brightness.getMonitorForScreen(screen)\n    spacing: 12\n\n    RowLayout {\n        spacing: 4\n\n        WPanelIconButton {\n            color: colBackground\n            property real animationValue: root.brightnessMonitor?.brightness ?? 0\n            rotation: animationValue * 180\n            scale: 0.8 + animationValue * 0.2\n            iconName: \"weather-sunny\"\n\n            Behavior on animationValue {\n                animation: Looks.transition.longMovement.createObject(this)\n            }\n        }\n        \n        WSlider {\n            Layout.fillWidth: true\n            value: root.brightnessMonitor?.brightness ?? 0\n            scrollable: true\n            onMoved: {\n                root.brightnessMonitor?.setBrightness(value)\n            }\n        }\n\n        WPanelIconButton {\n            opacity: 0\n        }\n    }\n    \n    RowLayout {\n        spacing: 4\n\n        WPanelIconButton {\n            iconName: WIcons.volumeIcon\n            onClicked: Audio.toggleMute();\n        }\n        \n        WSlider {\n            Layout.fillWidth: true\n            value: Audio.sink.audio.volume\n            scrollable: true\n            onMoved: {\n                Audio.sink.audio.volume = value;\n            }\n        }\n\n        WPanelIconButton {\n            Component {\n                id: volumeControlComp\n                VolumeControl {}\n            }\n            onClicked: {\n                ActionCenterContext.push(volumeControlComp)\n            }\n            contentItem: Item {\n                anchors.centerIn: parent\n                Row {\n                    anchors.centerIn: parent\n                    spacing: -1\n                    FluentIcon {\n                        anchors.verticalCenter: parent.verticalCenter\n                        implicitSize: 18\n                        icon: \"options\"\n                    }\n                    FluentIcon {\n                        anchors.verticalCenter: parent.verticalCenter\n                        implicitSize: 12\n                        icon: \"chevron-right\"\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageBodyToggles.qml",
    "content": "pragma ComponentBehavior: Bound\nimport Qt.labs.synchronizer\nimport QtQuick\nimport QtQuick.Layouts\nimport QtQuick.Controls\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter.toggles\n\nItem {\n    id: root\n\n    property int columns: 3\n    property int rows: 2\n    property int currentPage: 0\n    readonly property int itemsPerPage: columns * rows\n    readonly property int pages: Math.ceil(toggles.length / itemsPerPage)\n    property list<string> toggles: Config.options.waffles.actionCenter.toggles\n\n    property real padding: 22\n    property real reducedBottomPadding: 12\n    implicitHeight: swipeView.implicitHeight + (padding - swipeView.padding) * 2 - reducedBottomPadding\n\n    function togglesInPage(index) {\n        var start = index * root.itemsPerPage;\n        var end = start + root.itemsPerPage;\n        return root.toggles.slice(start, end);\n    }\n\n    function decreasePage() {\n        if (root.currentPage > 0) {\n            root.currentPage -= 1;\n        }\n    }\n\n    function increasePage() {\n        if (root.currentPage < (root.pages - 1)) {\n            root.currentPage += 1;\n        }\n    }\n\n    clip: true\n    SwipeView {\n        id: swipeView\n        anchors {\n            fill: parent\n            topMargin: root.padding - swipeView.padding\n            bottomMargin: root.padding - swipeView.padding - root.reducedBottomPadding\n        }\n        padding: 4\n        leftPadding: root.padding\n        rightPadding: root.padding\n        spacing: padding\n\n        orientation: Qt.Vertical\n        clip: true\n        Synchronizer on currentIndex {\n            property alias source: root.currentPage\n        }\n\n        Repeater {\n            model: root.pages\n            delegate: GridLayout {\n                id: grid\n                required property int index\n                // width: SwipeView.view.width - root.padding * 2\n                // height: SwipeView.view.height - root.padding * 2\n\n                columns: root.columns\n                rows: root.rows\n                rowSpacing: 12\n                columnSpacing: 12\n\n                Repeater {\n                    model: ScriptModel {\n                        values: root.togglesInPage(grid.index)\n                    }\n                    delegate: ActionCenterTogglesDelegateChooser {}\n                }\n            }\n        }\n    }\n\n    VerticalPageIndicator {\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.right: parent.right\n        anchors.rightMargin: 6\n        \n        currentIndex: root.currentPage\n        count: root.pages\n        onClicked: (index) => root.currentPage = index\n        onIncreasePage: root.increasePage();\n        onDecreasePage: root.decreasePage();\n    }\n\n    FocusedScrollMouseArea {\n        z: 999\n        anchors.fill: parent\n        acceptedButtons: Qt.NoButton\n        hoverEnabled: false\n        onScrollUp: root.decreasePage();\n        onScrollDown: root.increasePage();\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/mainPage/MainPageFooter.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nFooterRectangle {\n\n    // Battery button\n    WBorderlessButton {\n        visible: Battery.available\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.left: parent.left\n        anchors.leftMargin: 12\n\n        contentItem: Row {\n            spacing: 4\n\n            FluentIcon {\n                anchors.verticalCenter: parent.verticalCenter\n                icon: WIcons.batteryLevelIcon\n                FluentIcon {\n                    anchors.fill: parent\n                    icon: WIcons.batteryIcon\n                }\n            }\n            WText {\n                anchors.verticalCenter: parent.verticalCenter\n                text: `${Math.round(Battery.percentage * 100) || 0}%`\n            }\n        }\n    }\n\n    // Settings button\n    WBorderlessButton {\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.right: parent.right\n        anchors.rightMargin: 12\n\n        onClicked: {\n            GlobalStates.sidebarLeftOpen = false;\n            Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"settings.qml\")]);\n        }\n\n        contentItem: FluentIcon {\n            icon: \"settings\"\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/nightLight/NightLightControl.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Bluetooth\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nItem {\n    id: root\n\n    Component.onCompleted: {\n        if (Bluetooth.defaultAdapter.enabled)\n            Bluetooth.defaultAdapter.discovering = true;\n    }\n    Component.onDestruction: {\n        Bluetooth.defaultAdapter.discovering = false;\n    }\n\n    WPanelPageColumn {\n        anchors.fill: parent\n\n        BodyRectangle {\n            implicitHeight: 400\n            implicitWidth: 50\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 4\n                spacing: 4\n\n                HeaderRow {\n                    id: headerRow\n                    Layout.fillWidth: true\n                    title: Translation.tr(\"Eye protection\")\n                }\n\n                StyledFlickable {\n                    id: flickable\n                    Layout.fillHeight: true\n                    Layout.fillWidth: true\n\n                    contentHeight: contentLayout.implicitHeight\n                    contentWidth: width\n                    clip: true\n\n                    bottomMargin: 12\n\n                    NightLightOptions {\n                        id: contentLayout\n                        width: flickable.width\n                    }\n                }\n            }\n        }\n\n        WPanelSeparator {}\n\n        FooterRectangle {}\n    }\n\n    component NightLightOptions: ColumnLayout {\n        spacing: 10\n\n        SectionText {\n            text: Translation.tr(\"Night Light\")\n        }\n\n        ToggleItem {\n            name: Translation.tr(\"Automatic\")\n            description: Translation.tr(\"Turn on from sunset to sunrise\")\n            iconName: \"auto\"\n            checked: Config.options.light.night.automatic\n            onCheckedChanged: {\n                Config.options.light.night.automatic = checked;\n            }\n        }\n\n        ToggleItem {\n            name: Translation.tr(\"Enable now\")\n            description: Translation.tr(\"More comfortable viewing at night\")\n            iconName: WIcons.nightLightIcon\n            checked: Hyprsunset.temperatureActive\n            onCheckedChanged: {\n                Hyprsunset.toggleTemperature(checked);\n            }\n        }\n\n        IntensityEntry {\n            Layout.fillWidth: true\n        }\n\n        SectionText {\n            text: Translation.tr(\"Anti-flashbang (experimental)\")\n        }\n\n        ToggleItem {\n            name: Translation.tr(\"Enable\")\n            description: Translation.tr(\"Balance brightness based on content\")\n            iconName: \"flash-off\"\n            checked: Config.options.light.antiFlashbang.enable\n            onCheckedChanged: {\n                Config.options.light.antiFlashbang.enable = checked;\n            }\n        }\n    }\n\n    component IntensityEntry: RowLayout {\n        spacing: 10\n\n        FluentIcon {\n            id: iconWidget\n            Layout.leftMargin: 12\n            Layout.topMargin: 4\n            Layout.bottomMargin: 4\n            Layout.alignment: Qt.AlignTop\n            icon: \"temperature\"\n            implicitSize: 18\n        }\n        ColumnLayout {\n            Layout.fillWidth: true\n            // Layout.leftMargin: 40\n            Layout.rightMargin: 12\n            spacing: 4\n\n            ColumnLayout {\n                Layout.fillWidth: true\n                spacing: 0\n                WText {\n                    Layout.fillWidth: true\n                    text: Translation.tr(\"Intensity\")\n                    font.pixelSize: Looks.font.pixelSize.large\n                }\n                WText {\n                    Layout.fillWidth: true\n                    text: Translation.tr(\"Adjust the color temperature\")\n                    color: Looks.colors.subfg\n                }\n            }\n            WSlider {\n                Layout.fillWidth: true\n                from: 6500\n                to: 1200\n                value: Config.options.light.night.colorTemperature\n                onMoved: Config.options.light.night.colorTemperature = value\n                tooltipContent: Math.round((value - from) / (to - from) * 100)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterToggleButton.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\n// It should be perfectly fine to use just a Column here, but somehow\n// using ColumnLayout prevents weird opening anim stutter\nColumnLayout { \n    id: root\n\n    required property QuickToggleModel toggleModel\n    property string name: toggleModel?.name ?? \"\"\n    property string statusText: (toggleModel?.hasStatusText) ? (toggleModel?.statusText || (toggled ? Translation.tr(\"Active\") : Translation.tr(\"Inactive\"))) : \"\"\n    property string tooltipText: toggleModel?.tooltipText ?? \"\"\n    required property string icon\n    property bool available: toggleModel?.available ?? true\n    property bool toggled: toggleModel?.toggled ?? false\n    property var mainAction: toggleModel?.mainAction ?? null\n    property var altAction: toggleModel?.hasMenu ? (() => root.openMenu()) : (toggleModel?.altAction ?? null)\n    property bool hasMenu: toggleModel?.hasMenu ?? false\n    property Component menu\n\n    property color colBackground: toggled ? Looks.colors.accent : Looks.colors.bg2\n    property color colBackgroundHovered: toggled ? Looks.colors.accentHover : Looks.colors.bg2Hover\n    property color colBackgroundActive: toggled ? Looks.colors.accentActive : Looks.colors.bg2Active\n    property color colBorder: toggled ? Looks.colors.accentHover : Looks.colors.bg2Border\n    property color colForeground: toggled ? Looks.colors.accentFg : Looks.colors.fg1\n\n    spacing: 0\n    property real wholeToggleWidth: 96\n\n    AcrylicRectangle {\n        Layout.fillWidth: true\n        implicitWidth: root.wholeToggleWidth\n        implicitHeight: 48\n        color: root.colBackground\n        radius: Looks.radius.medium\n\n        RowLayout {\n            anchors.fill: parent\n            spacing: 0\n\n            ToggleFragment {\n                topLeftRadius: Looks.radius.medium\n                bottomLeftRadius: Looks.radius.medium\n                topRightRadius: root.hasMenu ? 0 : Looks.radius.medium\n                bottomRightRadius: root.hasMenu ? 0 : Looks.radius.medium\n                iconName: root.icon\n                onClicked: root.mainAction && root.mainAction()\n            }\n            FadeLoader {\n                Layout.fillHeight: true\n                Layout.fillWidth: true\n                shown: root.hasMenu\n                sourceComponent: ToggleFragment {\n                    topLeftRadius: 0\n                    bottomLeftRadius: 0\n                    topRightRadius: Looks.radius.medium\n                    bottomRightRadius: Looks.radius.medium\n                    iconName: \"chevron-right\"\n                    onClicked: {\n                        ActionCenterContext.stackView.push(root.menu)\n                    }\n                }\n            }\n        }\n    }\n\n    Item {\n        id: toggleNameWidget\n        implicitWidth: root.wholeToggleWidth\n        implicitHeight: 36\n        WText {\n            id: toggleNameText\n            anchors {\n                verticalCenter: parent.verticalCenter\n                left: parent.left\n                right: parent.right\n            }\n            horizontalAlignment: Text.AlignHCenter\n            elide: Text.ElideRight\n            text: root.name\n        }\n    }\n\n    component ToggleFragment: WButton {\n        id: toggleFragment\n        required property string iconName\n        Layout.fillHeight: true\n        Layout.fillWidth: true\n        inset: 0\n        backgroundOpacity: 0.8\n        checked: root.toggled\n        border.width: 1\n        border.color: root.colBorder\n\n        contentItem: Item {\n            anchors.centerIn: parent\n            FluentIcon {\n                anchors.centerIn: parent\n                icon: toggleFragment.iconName\n                implicitSize: 18\n                monochrome: true\n                filled: root.toggled\n                color: root.colForeground\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/ActionCenterTogglesDelegateChooser.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models.quickToggles\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter.bluetooth\nimport qs.modules.waffle.actionCenter.nightLight\nimport qs.modules.waffle.actionCenter.volumeControl\nimport qs.modules.waffle.actionCenter.wifi\n\nDelegateChooser {\n    id: root\n\n    // role: \"type\" is implied by usage\n\n    DelegateChoice {\n        roleValue: \"antiFlashbang\"\n        ActionCenterToggleButton {\n            toggleModel: AntiFlashbangToggle {}\n            icon: \"flash-off\"\n            menu: Component {\n                NightLightControl {}\n            }\n        }\n    }\n    DelegateChoice {\n        roleValue: \"bluetooth\"\n        ActionCenterToggleButton {\n            toggleModel: BluetoothToggle {}\n            name: toggleModel.statusText\n            icon: WIcons.bluetoothIcon\n            menu: Component {\n                BluetoothControl {}\n            }\n        }\n    }\n    DelegateChoice {\n        roleValue: \"cloudflareWarp\"\n        ActionCenterToggleButton {\n            toggleModel: CloudflareWarpToggle {}\n            icon: \"cloudflare\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"colorPicker\"\n        ActionCenterToggleButton {\n            toggleModel: ColorPickerToggle {}\n            icon: \"eyedropper\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"darkMode\"\n        ActionCenterToggleButton {\n            toggleModel: DarkModeToggle {}\n            icon: \"dark-theme\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"easyEffects\"\n        ActionCenterToggleButton {\n            toggleModel: EasyEffectsToggle {}\n            icon: \"device-eq\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"gameMode\"\n        ActionCenterToggleButton {\n            toggleModel: GameModeToggle {}\n            icon: \"games\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"idleInhibitor\"\n        ActionCenterToggleButton {\n            toggleModel: IdleInhibitorToggle {}\n            icon: \"drink-coffee\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"mic\"\n        ActionCenterToggleButton {\n            toggleModel: MicToggle {}\n            icon: WIcons.micIcon\n            menu: Component {\n                VolumeControl {\n                    output: false\n                }\n            }\n        }\n    }\n    DelegateChoice {\n        roleValue: \"musicRecognition\"\n        ActionCenterToggleButton {\n            toggleModel: MusicRecognitionToggle {}\n            icon: \"music-note-2\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"network\"\n        ActionCenterToggleButton {\n            toggleModel: NetworkToggle {}\n            name: toggleModel.statusText\n            icon: WIcons.internetIcon\n            menu: Component {\n                WifiControl {}\n            }\n        }\n    }\n    DelegateChoice {\n        roleValue: \"nightLight\"\n        ActionCenterToggleButton {\n            toggleModel: NightLightToggle {}\n            icon: WIcons.nightLightIcon\n            menu: Component {\n                NightLightControl {}\n            }\n        }\n    }\n    DelegateChoice {\n        roleValue: \"notifications\"\n        ActionCenterToggleButton {\n            toggleModel: NotificationToggle {}\n            icon: WIcons.notificationsIcon\n        }\n    }\n    DelegateChoice {\n        roleValue: \"onScreenKeyboard\"\n        ActionCenterToggleButton {\n            toggleModel: OnScreenKeyboardToggle {}\n            icon: GlobalStates.oskOpen ? \"keyboard-dock\" : \"keyboard\"\n        }\n    }\n    DelegateChoice {\n        roleValue: \"powerProfile\"\n        ActionCenterToggleButton {\n            toggleModel: PowerProfilesToggle {}\n            icon: WIcons.powerProfileIcon\n            name: toggleModel.statusText\n        }\n    }\n    DelegateChoice {\n        roleValue: \"screenSnip\"\n        ActionCenterToggleButton {\n            toggleModel: ScreenSnipToggle {}\n            icon: \"cut\"\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/toggles/WNetworkToggle.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nActionCenterToggle {\n    id: root\n\n    name: Network.ethernet ? Translation.tr(\"Network\") : Network.networkName\n\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeControl.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nItem {\n    id: root\n    property bool output: true\n\n    WPanelPageColumn {\n        anchors.fill: parent\n\n        BodyRectangle {\n            implicitHeight: 400\n            implicitWidth: 50\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 4\n                spacing: 4\n\n                HeaderRow {\n                    Layout.fillWidth: true\n                    title: root.output ? Translation.tr(\"Sound output\") : Translation.tr(\"Sound input\")\n                }\n\n                StyledFlickable {\n                    id: flickable\n                    Layout.fillHeight: true\n                    Layout.fillWidth: true\n\n                    contentHeight: contentLayout.implicitHeight\n                    contentWidth: width\n                    clip: true\n\n                    AudioChoices {\n                        id: contentLayout\n                        width: flickable.width\n                    }\n                }\n            }\n        }\n\n        WPanelSeparator {}\n\n        FooterRectangle {\n            WButton {\n                id: moreSettingsButton\n                anchors {\n                    verticalCenter: parent.verticalCenter\n                    left: parent.left\n                }\n                implicitHeight: 40\n                implicitWidth: contentItem.implicitWidth + 30\n                color: \"transparent\"\n\n                onClicked: {\n                    Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"sidebarLeft\", \"toggle\"]);\n                    Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.volumeMixer]);\n                }\n\n                contentItem: Item {\n                    anchors.centerIn: parent\n                    implicitWidth: buttonText.implicitWidth\n                    WText {\n                        id: buttonText\n                        anchors.centerIn: parent\n                        text: Translation.tr(\"More volume settings\")\n                        color: moreSettingsButton.pressed ? Looks.colors.fg : Looks.colors.fg1\n                    }\n                }\n            }\n        }\n    }\n\n    component AudioChoices: ColumnLayout {\n        spacing: 4\n\n        SectionText {\n            text: root.output ? Translation.tr(\"Output device\") : Translation.tr(\"Input device\")\n        }\n\n        Repeater {\n            model: ScriptModel {\n                values: root.output ? Audio.outputDevices : Audio.inputDevices\n            }\n            delegate: WChoiceButton {\n                required property var modelData\n                icon.name: WIcons.audioDeviceIcon(modelData)\n                text: Audio.friendlyDeviceName(modelData)\n                checked: (root.output ? Audio.sink : Audio.source) === modelData\n                onClicked: {\n                    if (root.output) Audio.setDefaultSink(modelData);\n                    else Audio.setDefaultSource(modelData);\n                }\n            }\n        }\n\n        WPanelSeparator {\n            visible: EasyEffects.available && root.output\n            color: Looks.colors.bg2Hover\n        }\n\n        ////////////////////////////////////////////////////////////\n\n        SectionText {\n            visible: EasyEffects.available && root.output\n            text: Translation.tr(\"Sound effects\")\n        }\n\n        WChoiceButton {\n            visible: EasyEffects.available && root.output\n            text: Translation.tr(\"Off\")\n            checked: !EasyEffects.active\n            onClicked: EasyEffects.disable()\n        }\n\n        WChoiceButton {\n            visible: EasyEffects.available && root.output\n            text: \"EasyEffects\"\n            checked: EasyEffects.active\n            onClicked: EasyEffects.enable()\n        }\n\n        WPanelSeparator {\n            color: Looks.colors.bg2Hover\n        }\n\n        ////////////////////////////////////////////////////////////\n\n        SectionText {\n            visible: EasyEffects.available\n            text: Translation.tr(\"Volume mixer\")\n        }\n\n        VolumeEntry {\n            node: root.output ? Audio.sink : Audio.source\n            icon: root.output ? \"speaker\" : \"mic-on\"\n            monochrome: true\n        }\n\n        Repeater {\n            model: ScriptModel {\n                values: root.output ? Audio.outputAppNodes : Audio.inputAppNodes\n            }\n            delegate: VolumeEntry {\n                required property var modelData\n                node: modelData\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/volumeControl/VolumeEntry.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Pipewire\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nRowLayout {\n    id: root\n    required property PwNode node\n    property alias icon: iconButton.iconName\n    property alias monochrome: iconButton.monochrome\n    monochrome: false\n\n    PwObjectTracker { // Necessary for useful info to be present in 'node'\n        objects: [root.node]\n    }\n\n    WPanelIconButton {\n        id: iconButton\n        iconName: WIcons.audioAppIcon(root.node)\n        onClicked: root.node.audio.muted = !root.node?.audio.muted\n\n        FluentIcon {\n            id: muteIcon\n            visible: root.node?.audio.muted ?? false\n            anchors {\n                bottom: parent.bottom\n                right: parent.right\n                margins: -1\n            }\n            implicitSize: 16\n            icon: \"speaker-mute\"\n        }\n\n        WToolTip {\n            extraVisibleCondition: iconButton.shouldShowTooltip\n            text: Audio.appNodeDisplayName(root.node)\n        }\n    }\n\n    WSlider {\n        Layout.fillWidth: true\n        Layout.rightMargin: 10\n        value: root.node?.audio.volume ?? 0\n        onMoved: root.node.audio.volume = value\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WWifiNetworkItem.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nExpandableChoiceButton {\n    id: root\n    required property WifiAccessPoint wifiNetwork\n\n    contentItem: RowLayout {\n        id: contentItem\n        spacing: 12\n\n        FluentIcon { // Duotone hack\n            Layout.bottomMargin: 2\n            Layout.alignment: Qt.AlignTop\n            property int strength: root.wifiNetwork?.strength ?? 0\n            icon: \"wifi-1\"\n            implicitSize: 30\n            color: Looks.colors.inactiveIcon\n\n            FluentIcon { // Signal\n                property int strength: root.wifiNetwork?.strength ?? 0\n                icon: WIcons.wifiIconForStrength(strength)\n                implicitSize: 30\n\n                FluentIcon { // Security\n                    anchors {\n                        right: parent.right\n                        bottom: parent.bottom\n                    }\n                    visible: root?.wifiNetwork?.isSecure ?? false\n                    icon: \"lock-closed\"\n                    filled: true\n                    implicitSize: 14           \n                }\n            }\n        }\n\n        ColumnLayout {\n            Layout.topMargin: statusText.visible ? 4 : 7\n            Layout.bottomMargin: 4\n            Layout.alignment: Qt.AlignTop\n            Layout.fillWidth: true\n            spacing: 1\n\n            Behavior on Layout.topMargin {\n                animation: Looks.transition.move.createObject(this)\n            }\n\n            WText { // Network name\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                font.pixelSize: Looks.font.pixelSize.large\n                text: root.wifiNetwork?.ssid ?? Translation.tr(\"Unknown\")\n                textFormat: Text.PlainText\n            }\n            WText { // Status\n                id: statusText\n                Layout.fillWidth: true\n                elide: Text.ElideRight\n                text: root.wifiNetwork?.active ? Translation.tr(\"Connected\") : root.wifiNetwork?.isSecure ? Translation.tr(\"Secured\") : Translation.tr(\"Not secured\")\n                font.pixelSize: Looks.font.pixelSize.large\n                color: Looks.colors.subfg\n                visible: root.wifiNetwork?.active || root.expanded\n                Behavior on opacity {\n                    animation: Looks.transition.opacity.createObject(this)\n                }\n            }\n\n            WButton {\n                Layout.alignment: Qt.AlignRight\n                horizontalAlignment: Text.AlignHCenter\n                visible: root.expanded\n                checked: !(root.wifiNetwork?.active ?? false)\n                colBackground: Looks.colors.bg2\n                colBackgroundHover: Looks.colors.bg2Hover\n                colBackgroundActive: Looks.colors.bg2Active\n                implicitHeight: 30\n                implicitWidth: 148\n                text: root.wifiNetwork?.active ? Translation.tr(\"Disconnect\") : Translation.tr(\"Connect\")\n\n                onClicked: {\n                    if (root.wifiNetwork?.active) {\n                        Network.disconnectWifiNetwork();\n                    } else {\n                        Network.connectToWifiNetwork(root.wifiNetwork);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/actionCenter/wifi/WifiControl.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.actionCenter\n\nItem {\n    id: root\n\n    Component.onCompleted: {\n        Network.rescanWifi();\n    }\n\n    WPanelPageColumn {\n        anchors.fill: parent\n\n        BodyRectangle {\n            implicitHeight: 400\n            implicitWidth: 50\n\n            ColumnLayout {\n                anchors.fill: parent\n                anchors.margins: 4\n                spacing: 4\n\n                ColumnLayout {\n                    implicitHeight: headerRow.implicitHeight\n                    Layout.fillWidth: true\n                    spacing: 0\n                    RowLayout {\n                        Layout.fillWidth: true\n                        spacing: 0\n                        HeaderRow {\n                            id: headerRow\n                            Layout.fillWidth: true\n                            title: Translation.tr(\"Wi-Fi\")\n                        }\n                        WSwitch {\n                            id: toggleSwitch\n                            Layout.rightMargin: 12\n                            checked: Network.wifiStatus !== \"disabled\"\n                            onCheckedChanged: {\n                                Network.enableWifi(checked);\n                                Network.rescanWifi();\n                            }\n                        }\n                    }\n                    FadeLoader {\n                        Layout.leftMargin: -4\n                        Layout.rightMargin: -4\n                        Layout.fillWidth: true\n                        shown: Network.wifiScanning\n                        visible: true\n                        sourceComponent: WIndeterminateProgressBar {}\n                    }\n                }\n\n                StyledListView {\n                    id: listView\n                    Layout.fillHeight: true\n                    Layout.fillWidth: true\n                    animateAppearance: false\n\n                    contentHeight: contentLayout.implicitHeight\n                    contentWidth: width\n                    clip: true\n                    spacing: 4\n\n                    model: ScriptModel {\n                        values: Network.friendlyWifiNetworks\n                    }\n                    delegate: WWifiNetworkItem {\n                        required property WifiAccessPoint modelData\n                        wifiNetwork: modelData\n                        width: ListView.view.width\n                    }\n                }\n            }\n        }\n\n        WPanelSeparator {}\n\n        FooterRectangle {\n            WTextButton {\n                anchors {\n                    verticalCenter: parent.verticalCenter\n                    left: parent.left\n                }\n                text: Translation.tr(\"More Internet settings\")\n                onClicked: {\n                    Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"sidebarLeft\", \"toggle\"]);\n                    Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.network]);\n                }\n            }\n            WBorderlessButton {\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.right: parent.right\n                anchors.rightMargin: 12\n                enabled: !Network.wifiScanning\n\n                onClicked: {\n                    Network.rescanWifi();\n                }\n\n                contentItem: FluentIcon {\n                    icon: \"arrow-counterclockwise\"\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/background/WaffleBackground.qml",
    "content": "pragma ComponentBehavior: Bound\n\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.widgets.widgetCanvas\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nimport qs.modules.ii.background.widgets\nimport qs.modules.ii.background.widgets.clock\nimport qs.modules.ii.background.widgets.weather\n\nVariants {\n    id: root\n    model: Quickshell.screens\n\n    PanelWindow {\n        id: panelRoot\n        required property var modelData\n\n        screen: modelData\n        exclusionMode: ExclusionMode.Ignore\n        WlrLayershell.layer: WlrLayer.Bottom\n        WlrLayershell.namespace: \"quickshell:background\"\n        anchors {\n            top: true\n            bottom: true\n            left: true\n            right: true\n        }\n        color: \"transparent\"\n\n        StyledImage {\n            anchors.fill: parent\n            source: Config.options.background.wallpaperPath\n            fillMode: Image.PreserveAspectCrop\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/AppButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport org.kde.kirigami as Kirigami\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nBarButton {\n    id: root\n\n    required property string iconName\n    property bool multiple: false\n    property bool separateLightDark: false\n    property alias tryCustomIcon: iconWidget.tryCustomIcon\n    leftInset: 2\n    rightInset: 2\n    implicitWidth: height - topInset - bottomInset + leftInset + rightInset\n\n    property real pressedScale: 5/6\n\n    onDownChanged: {\n        scaleAnim.duration = root.down ? 150 : 200\n        scaleAnim.easing.bezierCurve = root.down ? Looks.transition.easing.bezierCurve.easeIn : Looks.transition.easing.bezierCurve.easeOut\n        contentItem.scale = root.down ? root.pressedScale : 1 // If/When we do dragging, the scale is 1.25\n    }\n\n    background: Item {\n        id: background\n        BackgroundAcrylicRectangle {\n            id: mainBgRect\n            anchors.fill: parent\n            layer.enabled: root.multiple\n            layer.effect: OpacityMask {\n                invert: true\n                maskSource: Item {\n                    width: mainBgRect.width\n                    height: mainBgRect.height\n                    Rectangle {\n                        anchors.fill: parent\n                        anchors.rightMargin: 3\n                        radius: mainBgRect.radius\n                    }\n                }\n            }\n        }\n        Loader {\n            anchors.fill: parent\n            anchors.rightMargin: 5\n            active: root.multiple\n            sourceComponent: BackgroundAcrylicRectangle {}\n        }\n    }\n\n    contentItem: Item {\n        id: contentItem\n        anchors.centerIn: root.background\n\n        implicitHeight: iconWidget.implicitHeight\n        implicitWidth: iconWidget.implicitWidth\n\n        Behavior on scale {\n            NumberAnimation {\n                id: scaleAnim\n                easing.type: Easing.BezierSpline\n            }\n        }\n\n        WAppIcon {\n            id: iconWidget\n            anchors.centerIn: parent\n            iconName: root.iconName\n            separateLightDark: root.separateLightDark\n        }\n    }\n\n    component BackgroundAcrylicRectangle: AcrylicRectangle {\n        shiny: ((root.hovered && !root.down) || root.checked)\n        color: root.color\n        border.width: 1\n        border.color: root.colBackgroundBorder\n\n        Behavior on border.color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/BarButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nAcrylicButton {\n    id: root\n\n    property var altAction: () => {}\n    property var middleClickAction: () => {}\n\n    Layout.fillHeight: true\n    topInset: 4\n    bottomInset: 4\n    leftInset: 0\n    rightInset: 0\n    horizontalPadding: 8\n\n    colBackground: ColorUtils.transparentize(Looks.colors.bg1)\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton\n        onPressed: (event) => {\n            root.down = true;\n        }\n        onReleased: (event) => {\n            root.down = false;\n        }\n        onClicked: (event) => {\n            if (event.button === Qt.LeftButton) root.clicked();\n            if (event.button === Qt.RightButton) root.altAction();\n            if (event.button === Qt.MiddleButton) root.middleClickAction();\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/BarIconButton.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\n\nBarButton {\n    id: root\n\n    property alias iconName: iconContent.icon\n    property alias iconSource: iconContent.source\n    property alias iconSize: iconContent.implicitSize\n    property alias iconRotation: iconContent.rotation\n    property alias iconMonochrome: iconContent.monochrome\n    property alias iconScale: iconContent.scale\n    property alias tooltipText: tooltip.text\n    property alias overlayingItems: iconContent.data\n\n    implicitWidth: 32\n\n    contentItem: Item {\n        anchors.centerIn: parent\n        implicitWidth: iconContent.implicitWidth\n        implicitHeight: iconContent.implicitHeight\n\n        FluentIcon {\n            id: iconContent\n            anchors.centerIn: parent\n            implicitSize: 16\n            icon: root.iconName\n            monochrome: false\n        }\n    }\n\n    BarToolTip {\n        id: tooltip\n        extraVisibleCondition: root.shouldShowTooltip && text !== \"\"\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/BarMenu.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nBarPopup {\n    id: root\n    default property var menuData\n    property var model: [\n        { iconName: \"start-here\", text: \"Start\", action: () => {print(\"hello\")} },\n        { type : \"separator\" },\n    ]\n    readonly property bool hasIcons: model.some(item => item.iconName !== undefined && item.iconName !== \"\")\n    padding: 2\n\n    contentItem: ColumnLayout {\n        anchors.centerIn: parent\n        spacing: 0\n\n        Repeater {\n            model: root.model\n            delegate: DelegateChooser {\n                role: \"type\"\n                DelegateChoice {\n                    roleValue: \"separator\"\n                    Rectangle {\n                        Layout.topMargin: 2\n                        Layout.bottomMargin: 2\n                        Layout.fillWidth: true\n                        implicitHeight: 1\n                        color: Looks.colors.bg0Border\n                    }\n                }\n                DelegateChoice {\n                    roleValue: undefined\n                    WButton {\n                        id: btn\n                        Layout.fillWidth: true\n                        inset: 2\n\n                        required property var modelData\n                        forceShowIcon: root.hasIcons\n                        icon.name: modelData.iconName ? modelData.iconName : \"\"\n                        monochromeIcon: modelData.monochromeIcon ?? true\n                        text: modelData.text ? modelData.text : \"\"\n\n                        onClicked: {\n                            if (modelData.action) modelData.action();\n                            root.close();\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/BarPopup.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nLoader {\n    id: root\n\n    required property var contentItem\n    property real padding: Looks.radius.large - Looks.radius.medium\n    property bool noSmoothClosing: !Config.options.waffles.tweaks.smootherMenuAnimations\n    property bool closeOnFocusLost: true\n    signal focusCleared()\n    \n    property Item anchorItem: parent\n    property real visualMargin: 12\n    readonly property bool barAtBottom: Config.options.waffles.bar.bottom\n    property real ambientShadowWidth: 1\n\n    onFocusCleared: {\n        if (!root.closeOnFocusLost) return;\n        root.close()\n    }\n\n    function grabFocus() { // Doesn't work\n        item.grabFocus();\n    }\n\n    function close() {\n        item.close();\n    }\n\n    function updateAnchor() {\n        item?.anchor.updateAnchor();\n    }\n\n    active: false\n    visible: active\n    sourceComponent: PopupWindow {\n        id: popupWindow\n        visible: true\n        Component.onCompleted: {\n            openAnim.start();\n        }\n\n        anchor {\n            adjustment: PopupAdjustment.ResizeY | PopupAdjustment.SlideX\n            item: root.anchorItem\n            gravity: root.barAtBottom ? Edges.Top : Edges.Bottom\n            edges: root.barAtBottom ? Edges.Top : Edges.Bottom\n        }\n\n        HyprlandFocusGrab {\n            id: focusGrab\n            active: true\n            windows: [popupWindow]\n            onCleared: root.focusCleared();\n        }\n\n        function close() {\n            if (root.noSmoothClosing) root.active = false;\n            else closeAnim.start();\n        }\n\n        function grabFocus() {\n            focusGrab.active = true; // Doesn't work\n        }\n\n        implicitWidth: realContent.implicitWidth + (root.ambientShadowWidth * 2) + (root.visualMargin * 2)\n        implicitHeight: realContent.implicitHeight + (root.ambientShadowWidth * 2) + (root.visualMargin * 2)\n\n        property real sourceEdgeMargin: -implicitHeight\n        PropertyAnimation {\n            id: openAnim\n            target: popupWindow\n            property: \"sourceEdgeMargin\"\n            to: (root.ambientShadowWidth + root.visualMargin)\n            duration: 200\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n        }\n        SequentialAnimation {\n            id: closeAnim\n            PropertyAnimation {\n                target: popupWindow\n                property: \"sourceEdgeMargin\"\n                to: -implicitHeight\n                duration: 150\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut\n            }\n            ScriptAction {\n                script: {\n                    root.active = false;\n                }\n            }\n        }\n\n        color: \"transparent\"\n        WAmbientShadow {\n            target: realContent\n        }\n        \n        Rectangle {\n            id: realContent\n            z: 1\n            anchors {\n                left: parent.left\n                right: parent.right\n                top: root.barAtBottom ? undefined : parent.top\n                bottom: root.barAtBottom ? parent.bottom : undefined\n                margins: root.ambientShadowWidth + root.visualMargin\n                // Opening anim\n                bottomMargin: root.barAtBottom ? popupWindow.sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin)\n                topMargin: root.barAtBottom ? (root.ambientShadowWidth + root.visualMargin) : popupWindow.sourceEdgeMargin\n            }\n            color: Looks.colors.bg1Base\n            radius: Looks.radius.large\n\n            // test\n            implicitWidth: root.contentItem.implicitWidth + (root.padding * 2)\n            implicitHeight: root.contentItem.implicitHeight + (root.padding * 2)\n\n            children: [root.contentItem]\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/BarToolTip.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nWPopupToolTip {\n    anchorEdges: Config.options.waffles.bar.bottom ? Edges.Top : Edges.Bottom\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/SearchButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport org.kde.kirigami as Kirigami\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nAppButton {\n    id: root\n\n    iconName: checked ? \"system-search-checked\" : \"system-search\"\n    separateLightDark: true\n\n    checked: GlobalStates.searchOpen && LauncherSearch.query !== \"\"\n    onClicked: {\n        GlobalStates.searchOpen = !GlobalStates.searchOpen; // For now...\n    }\n\n    BarToolTip {\n        id: tooltip\n        text: Translation.tr(\"Search\")\n        extraVisibleCondition: root.shouldShowTooltip\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/StartButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\n// TODO: Replace the icon with QMLized svg (with /usr/lib/qt6/bin/svgtoqml) for proper micro-animation\nAppButton {\n    id: root\n\n    leftInset: Config.options.waffles.bar.leftAlignApps ? 12 : 0\n    iconName: down ? \"start-here-pressed\" : \"start-here\"\n\n    checked: GlobalStates.searchOpen && LauncherSearch.query === \"\"\n    onClicked: {\n        GlobalStates.searchOpen = !GlobalStates.searchOpen;\n    }\n\n    BarToolTip {\n        id: tooltip\n        text: Translation.tr(\"Start\")\n        extraVisibleCondition: root.shouldShowTooltip\n    }\n\n    altAction: () => {\n        contextMenu.active = true;\n    }\n\n    BarMenu {\n        id: contextMenu\n\n        model: [\n            {\n                text: Translation.tr(\"Terminal\"),\n                action: () => {\n                    Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.terminal]);\n                }\n            },\n            {\n                text: Translation.tr(\"Task Manager\"),\n                action: () => {\n                    Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.taskManager]);\n                }\n            },\n            {\n                text: Translation.tr(\"Settings\"),\n                action: () => {\n                    Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"settings.qml\")]);\n                }\n            },\n            {\n                text: Translation.tr(\"File Explorer\"),\n                action: () => {\n                    Qt.openUrlExternally(Directories.home);\n                }\n            },\n            {\n                text: Translation.tr(\"Search\"),\n                action: () => {\n                    Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"overview\", \"toggle\"]);\n                }\n            },\n        ]\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/SystemButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nBarButton {\n    id: root\n\n    checked: GlobalStates.sidebarLeftOpen\n    onClicked: {\n        GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen;\n    }\n\n    contentItem: Item {\n        anchors.fill: parent\n        implicitHeight: column.implicitHeight\n        implicitWidth: column.implicitWidth\n        Row {\n            id: column\n            anchors {\n                top: parent.top\n                bottom: parent.bottom\n                horizontalCenter: parent.horizontalCenter\n            }\n            spacing: 4\n\n            IconHoverArea {\n                id: internetHoverArea\n                iconItem: FluentIcon {\n                    anchors.verticalCenter: parent.verticalCenter\n                    icon: \"wifi-1\"\n                    color: Looks.colors.inactiveIcon\n\n                    FluentIcon {\n                        anchors.fill: parent\n                        icon: WIcons.internetIcon\n                    }\n                }\n            }\n\n            IconHoverArea {\n                id: volumeHoverArea\n                iconItem: FluentIcon {\n                    anchors.verticalCenter: parent.verticalCenter\n                    icon: \"speaker\"\n                    color: Looks.colors.inactiveIcon\n                    \n                    FluentIcon {\n                        anchors.fill: parent\n                        icon: WIcons.volumeIcon\n                    }\n                }\n                onScrollDown: Audio.decrementVolume();\n                onScrollUp: Audio.incrementVolume();\n            }\n\n            IconHoverArea {\n                id: batteryHoverArea\n                visible: Battery?.available ?? false\n                iconItem: FluentIcon {\n                    anchors.verticalCenter: parent.verticalCenter\n                    icon: WIcons.batteryLevelIcon\n                    FluentIcon {\n                        anchors.fill: parent\n                        icon: WIcons.batteryIcon\n                    }\n                }\n            }\n        }\n    }\n\n    component IconHoverArea: FocusedScrollMouseArea {\n        id: hoverArea\n        required property var iconItem\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n        }\n        hoverEnabled: true\n        implicitHeight: hoverArea.iconItem.implicitHeight\n        implicitWidth: hoverArea.iconItem.implicitWidth\n\n        onPressed: (event) => event.accepted = false; // Don't consume clicks\n\n        children: [iconItem]\n    }\n\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip && internetHoverArea.containsMouse\n        text: Translation.tr(\"%1\\nInternet access\").arg(Network.ethernet ? Translation.tr(\"Network\") : Network.networkName)\n    }\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip && volumeHoverArea.containsMouse\n        text: Translation.tr(\"Speakers (%1): %2\") //\n            .arg(Audio.sink?.nickname || Audio.sink?.description || Translation.tr(\"Unknown\")) //\n            .arg(Audio.sink?.audio.muted ? Translation.tr(\"Muted\") : `${Math.round(Audio.sink?.audio.volume * 100) || 0}%`) //\n    }\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip && batteryHoverArea.containsMouse\n        text: Translation.tr(\"Battery: %1%2\") //\n            .arg(`${Math.round(Battery.percentage * 100) || 0}%`) //\n            .arg(Battery.isPluggedIn ? (\" \" + Translation.tr(\"(Plugged in)\")) : \"\")\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/TaskViewButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport org.kde.kirigami as Kirigami\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nAppButton {\n    id: root\n\n    iconName: (down && !checked) ? \"task-view-pressed\" : \"task-view\"\n    pressedScale: checked ? 5/6 : 1\n    separateLightDark: true\n\n    checked: GlobalStates.overviewOpen\n    onClicked: {\n        GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n    }\n\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip\n        text: Translation.tr(\"Task View\") // Should be a preview of workspaces, but we'll have this for now...\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/TimeButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nBarButton {\n    id: root\n\n    rightInset: 12 // For now this is the rightmost button. Desktop peek is useless. (for now)\n    leftPadding: 12\n    rightPadding: 22\n\n    checked: GlobalStates.sidebarRightOpen\n    onClicked: {\n        GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n    }\n\n    contentItem: Item {\n        // anchors.centerIn: parent\n        implicitHeight: contentLayout.implicitHeight\n        implicitWidth: contentLayout.implicitWidth\n        Row {\n            id: contentLayout\n            anchors.centerIn: parent\n            spacing: 7\n            \n            Column {\n                anchors.verticalCenter: parent.verticalCenter\n                WText {\n                    anchors.right: parent.right\n                    text: DateTime.time\n                }\n                WText {\n                    anchors.right: parent.right\n                    text: DateTime.date\n                }\n            }\n            FluentIcon {\n                visible: Notifications.silent\n                anchors.verticalCenter: parent.verticalCenter\n                icon: \"alert-snooze\"\n                implicitSize: 18\n                filled: true\n            }\n        }\n    }\n\n    BarToolTip {\n        id: tooltip\n        extraVisibleCondition: root.shouldShowTooltip\n        text: `${Qt.locale().toString(DateTime.clock.date, \"dddd, MMMM d, yyyy\")}\\n\\n${Qt.locale().toString(DateTime.clock.date, \"ddd hh:mm AP\")}`\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/UpdatesButton.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar.tray\n\nBarIconButton {\n    id: root\n\n    visible: Updates.updateAdvised || Updates.updateStronglyAdvised\n    padding: 4\n    iconName: \"arrow-sync\"\n    iconSize: 20 // Needed because the icon appears to have some padding\n    iconMonochrome: true\n    tooltipText: Translation.tr(\"Get the latest features and security improvements with\\nthe newest feature update.\\n\\n%1 packages\").arg(Updates.count)\n\n    onClicked: {\n        Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.update]);\n    }\n\n    overlayingItems: Rectangle {\n        anchors {\n            right: parent.right\n            bottom: parent.bottom\n            margins: 1\n        }\n        implicitWidth: 8\n        implicitHeight: implicitWidth\n        radius: height / 2\n        color: Updates.updateStronglyAdvised ? Looks.colors.warning : Looks.colors.accent\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/WaffleBar.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nScope {\n    id: root\n    \n    LazyLoader {\n        id: barLoader\n        active: GlobalStates.barOpen\n        component: Variants {\n            model: Quickshell.screens\n            delegate: PanelWindow { // Bar window\n                id: barRoot\n                required property var modelData\n                screen: modelData\n                exclusionMode: ExclusionMode.Ignore\n                exclusiveZone: implicitHeight\n                WlrLayershell.namespace: \"quickshell:bar\"\n\n                anchors {\n                    left: true\n                    right: true\n                    bottom: Config.options.waffles.bar.bottom\n                    top: !Config.options.waffles.bar.bottom\n                }\n\n                color: \"transparent\"\n                implicitHeight: content.implicitHeight\n                implicitWidth: content.implicitWidth\n\n                WaffleBarContent {\n                    id: content\n                    anchors.fill: parent\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"bar\"\n\n        function toggle(): void {\n            GlobalStates.barOpen = !GlobalStates.barOpen\n        }\n\n        function close(): void {\n            GlobalStates.barOpen = false\n        }\n\n        function open(): void {\n            GlobalStates.barOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barToggle\"\n        description: \"Toggles bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = !GlobalStates.barOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barOpen\"\n        description: \"Opens bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = true;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"barClose\"\n        description: \"Closes bar on press\"\n\n        onPressed: {\n            GlobalStates.barOpen = false;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/WaffleBarContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar.tasks\nimport qs.modules.waffle.bar.tray\n\nRectangle {\n    id: root\n\n    color: Looks.colors.bg0\n    implicitHeight: 48\n    \n    Rectangle {\n        id: border\n        anchors {\n            left: parent.left\n            right: parent.right\n            top: Config.options.waffles.bar.bottom ? parent.top : undefined\n            bottom: Config.options.waffles.bar.bottom ? undefined : parent.bottom\n        }\n        color: Looks.colors.bg0Border\n        implicitHeight: 1\n    }\n\n    BarGroupRow {\n        id: bloatRow\n        anchors.left: parent.left\n        opacity: Config.options.waffles.bar.leftAlignApps ? 0 : 1\n        visible: opacity > 0\n        Behavior on opacity {\n            animation: Looks.transition.opacity.createObject(this)\n        }\n\n        WidgetsButton {}\n    }\n\n    BarGroupRow {\n        id: appsRow\n        anchors.left: undefined\n        anchors.horizontalCenter: parent.horizontalCenter\n\n        states: State {\n            name: \"left\"\n            when: Config.options.waffles.bar.leftAlignApps\n            AnchorChanges {\n                target: appsRow\n                anchors.left: parent.left\n                anchors.horizontalCenter: undefined\n            }\n        }\n\n        transitions: Transition {\n            animations: Looks.transition.anchor.createObject(this)\n        }\n\n        StartButton {}\n        SearchButton {}\n        TaskViewButton {}\n        Tasks {}\n    }\n\n    BarGroupRow {\n        id: systemRow\n        anchors.right: parent.right\n        FadeLoader {\n            Layout.fillHeight: true\n            shown: Config.options.waffles.bar.leftAlignApps\n            sourceComponent: WidgetsButton {}\n        }\n        Tray {}\n        UpdatesButton {}\n        SystemButton {}\n        TimeButton {}\n    }\n\n    component BarGroupRow: RowLayout {\n        anchors.top: parent.top\n        anchors.bottom: parent.bottom\n        spacing: 0\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/WidgetsButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport org.kde.kirigami as Kirigami\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nAppButton {\n    id: root\n\n    readonly property bool expandedForm: Config.options.waffles.bar.leftAlignApps\n    leftInset: Config.options.waffles.bar.leftAlignApps ? 0 : 12\n    implicitWidth: expandedForm ? 148 : (height - topInset - bottomInset + leftInset + rightInset)\n    iconName: \"widgets\"\n\n    checked: GlobalStates.sidebarLeftOpen\n    onClicked: {\n        GlobalStates.sidebarLeftOpen = !GlobalStates.sidebarLeftOpen\n    }\n    onDownChanged: {\n        scaleAnim.duration = root.down ? 150 : 200\n        scaleAnim.easing.bezierCurve = root.down ? Looks.transition.easing.bezierCurve.easeIn : Looks.transition.easing.bezierCurve.easeOut\n        iconWidget.scale = root.down ? 5/6 : 1 // If/When we do dragging, the scale is 1.25\n    }\n\n    contentItem: Item {\n        anchors {\n            verticalCenter: parent.verticalCenter\n            left: root.expandedForm ? parent.left : undefined\n            horizontalCenter: root.expandedForm ? undefined : background.horizontalCenter\n        }\n        implicitHeight: row.implicitHeight\n        implicitWidth: row.implicitWidth\n        Row {\n            id: row\n            anchors {\n                verticalCenter: parent.verticalCenter\n                left: root.expandedForm ? parent.left : undefined\n                horizontalCenter: root.expandedForm ? undefined : parent.horizontalCenter\n                margins: 8\n            }\n            spacing: 6\n\n            WAppIcon {\n                id: iconWidget\n                anchors.verticalCenter: parent.verticalCenter\n                iconName: root.iconName\n\n                Behavior on scale {\n                    NumberAnimation {\n                        id: scaleAnim\n                        easing.type: Easing.BezierSpline\n                    }\n                }\n            }\n\n            Column {\n                visible: root.expandedForm\n                anchors.verticalCenter: parent.verticalCenter\n                WText {\n                    text: Translation.tr(\"Widgets\")\n                }\n            }\n        }\n    }\n\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip\n        text: Translation.tr(\"Widgets\")\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskAppButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\nimport Quickshell\n\nAppButton {\n    id: root\n\n    required property var appEntry\n    readonly property bool isSeparator: appEntry.appId === \"SEPARATOR\"\n    property var desktopEntry: DesktopEntries.heuristicLookup(appEntry.appId)\n\n    Timer {\n        // Retry looking up the desktop entry if it failed (e.g. database not loaded yet)\n        property int retryCount: 5\n        interval: 1000\n        running: !root.isSeparator && root.desktopEntry === null && retryCount > 0\n        repeat: true\n        onTriggered: {\n            retryCount--;\n            root.desktopEntry = DesktopEntries.heuristicLookup(root.appEntry.appId);\n        }\n    }\n\n    property bool active: root.appEntry.toplevels.some(t => t.activated)\n    property bool hasWindows: appEntry.toplevels.length > 0\n\n    signal hoverPreviewRequested()\n    signal hoverPreviewDismissed()\n\n    multiple: appEntry.toplevels.length > 1\n    checked: active\n    iconName: AppSearch.guessIcon(appEntry.appId)\n    tryCustomIcon: false\n    \n    onHoverTimedOut: {\n        root.hoverPreviewRequested()\n    }\n\n    onClicked: {\n        root.hoverTimer.stop() // Prevents preview showing up when clicking to focus\n        if (root.multiple) {\n            root.hoverPreviewRequested()\n        } else if (root.appEntry.toplevels.length === 1) {\n            root.appEntry.toplevels[0].activate()\n        } else {\n            root.desktopEntry.execute()\n        }\n    }\n\n    middleClickAction: () => {\n        if (root.desktopEntry) {\n            desktopEntry.execute()\n        }\n    }\n\n    altAction: () => {\n        root.hoverPreviewDismissed()\n        root.hoverTimer.stop()\n        contextMenu.active = true;\n    }\n\n    // Active indicator\n    Rectangle {\n        id: activeIndicator\n        opacity: root.hasWindows ? 1 : 0\n        anchors {\n            horizontalCenter: root.background.horizontalCenter\n            bottom: root.background.bottom\n            bottomMargin: 1\n        }\n\n        implicitWidth: root.active ? 16 : 6\n        implicitHeight: 3\n        radius: height / 2\n\n        color: root.active ? Looks.colors.accent : Looks.colors.accentUnfocused\n\n        Behavior on implicitWidth {\n            animation: Looks.transition.enter.createObject(this)\n        }\n        Behavior on color {\n            animation: Looks.transition.color.createObject(this)\n        }\n        Behavior on opacity {\n            animation: Looks.transition.opacity.createObject(this)\n        }\n    }\n\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip && !root.hasWindows\n        text: desktopEntry ? desktopEntry.name : appEntry.appId\n    }\n\n    BarMenu {\n        id: contextMenu\n        noSmoothClosing: false // On the real thing this is always smooth\n\n        model: [\n            ...((root.desktopEntry?.actions.length > 0) ? root.desktopEntry.actions.map(action =>({\n                iconName: action.icon,\n                text: action.name,\n                action: () => {\n                    action.execute()\n                }\n            })).concat({ type: \"separator\" }) : []),\n            {\n                iconName: root.iconName,\n                text: root.desktopEntry ? root.desktopEntry.name : StringUtils.toTitleCase(appEntry.appId),\n                monochromeIcon: false,\n                action: () => {\n                    if (root.desktopEntry) {\n                        root.desktopEntry.execute()\n                    }\n                }\n            },\n            {\n                iconName: root.appEntry.pinned ? \"pin-off\" : \"pin\",\n                text: root.appEntry.pinned ? Translation.tr(\"Unpin from taskbar\") : Translation.tr(\"Pin to taskbar\"),\n                action: () => {\n                    TaskbarApps.togglePin(root.appEntry.appId);\n                }\n            },\n            ...(root.appEntry.toplevels.length > 0 ? [{\n                iconName: \"dismiss\",\n                text: root.multiple ? Translation.tr(\"Close all windows\") : Translation.tr(\"Close window\"),\n                action: () => {\n                    for (let toplevel of root.appEntry.toplevels) {\n                        toplevel.close();\n                    }\n                }\n            }] : []),\n        ]\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tasks/TaskPreview.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport Quickshell\n\nPopupWindow {\n    id: root\n\n    ///////////////////// Properties ////////////////////\n    required property bool tasksHovered\n    property var appEntry\n    property Item anchorItem\n\n    //////////////////// Functions ////////////////////\n    function close() { // Closing doesn't animate, not sure if they're just lazy or it's intentional\n        marginBehavior.enabled = false;\n        root.visible = false;\n    }\n\n    function open() {\n        marginBehavior.enabled = true;\n        root.visible = true;\n    }\n\n    function show(appEntry: var, button: Item) {\n        root.appEntry = appEntry;\n        root.anchorItem = button;\n        root.anchor.updateAnchor();\n        root.open();\n    }\n\n    ///////////////////// Internals /////////////////////\n    readonly property bool bottom: Config.options.waffles.bar.bottom\n    property real visualMargin: 12\n    property real ambientShadowWidth: 1\n\n    visible: false\n    color: \"transparent\"\n    implicitWidth: contentItem.implicitWidth + ambientShadowWidth + (visualMargin * 2)\n    implicitHeight: contentItem.implicitHeight + ambientShadowWidth + (visualMargin * 2)\n    anchor {\n        adjustment: PopupAdjustment.Slide\n        item: root.anchorItem\n        gravity: bottom ? Edges.Top : Edges.Bottom\n        edges: bottom ? Edges.Top : Edges.Bottom\n    }\n\n    Timer {\n        interval: 250\n        running: root.visible && !hoverChecker.containsMouse && !root.tasksHovered\n        onTriggered: {\n            root.close();\n        }\n    }\n\n    // Content\n    MouseArea {\n        id: hoverChecker\n        anchors.fill: parent\n        hoverEnabled: true\n\n        // Shadow\n        WAmbientShadow {\n            target: contentItem\n        }\n\n        Rectangle {\n            id: contentItem\n            property real sourceEdgeMargin: root.visible ? (root.ambientShadowWidth + root.visualMargin) : -root.implicitHeight\n            Behavior on sourceEdgeMargin {\n                id: marginBehavior\n                animation: Looks.transition.enter.createObject(this)\n            }\n            anchors {\n                left: parent.left\n                right: parent.right\n                top: root.bottom ? undefined : parent.top\n                bottom: root.bottom ? parent.bottom : undefined\n                margins: root.ambientShadowWidth + root.visualMargin\n                // Opening anim\n                bottomMargin: root.bottom ? sourceEdgeMargin : (root.ambientShadowWidth + root.visualMargin)\n                topMargin: root.bottom ? (root.ambientShadowWidth + root.visualMargin) : sourceEdgeMargin\n            }\n            color: Looks.colors.bg1Base\n            radius: Looks.radius.large\n\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: contentItem.width\n                    height: contentItem.height\n                    radius: contentItem.radius\n                }\n            }\n\n            // Testing\n            implicitHeight: Math.min(158, windowsRow.implicitHeight)\n            implicitWidth: windowsRow.implicitWidth\n\n            RowLayout {\n                id: windowsRow\n                anchors.fill: parent\n\n                Repeater {\n                    model: ScriptModel {\n                        values: root.appEntry?.toplevels ?? []\n                    }\n                    delegate: WindowPreview {\n                        required property var modelData\n                        toplevel: modelData\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tasks/Tasks.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nMouseArea {\n    id: root\n\n    Layout.fillHeight: true\n    implicitHeight: appRow.implicitHeight\n    implicitWidth: appRow.implicitWidth\n    hoverEnabled: true\n\n    function showPreviewPopup(appEntry, button) {\n        previewPopup.show(appEntry, button);\n    }\n\n    Behavior on implicitWidth {\n        animation: Looks.transition.move.createObject(this)\n    }\n\n    WListView {\n        id: appRow\n        anchors {\n            top: parent.top\n            bottom: parent.bottom\n        }\n        orientation: Qt.Horizontal\n        spacing: 0\n        implicitWidth: contentWidth\n        clip: true\n        interactive: false\n        // TODO: Include only apps (and windows) in current workspace only | wait, does that even make sense in a Hyprland workflow?\n        model: ScriptModel {\n            objectProp: \"appId\"\n            values: TaskbarApps.apps.filter(app => app.appId !== \"SEPARATOR\")\n        }\n        delegate: TaskAppButton {\n            required property var modelData\n            appEntry: modelData\n\n            onHoverPreviewRequested: {\n                root.showPreviewPopup(appEntry, this);\n            }\n            onHoverPreviewDismissed: {\n                previewPopup.close();\n            }\n        }\n    }\n\n    // Previews popup\n    TaskPreview {\n        id: previewPopup\n        tasksHovered: root.containsMouse\n        anchor.window: root.QsWindow.window\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tasks/WindowPreview.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\nimport Quickshell\nimport Quickshell.Wayland\n\nButton {\n    id: root\n\n    required property var toplevel\n    property real previewWidthConstraint: 200\n    property real previewHeightConstraint: 110\n    padding: 5\n    Layout.fillHeight: true\n\n    onClicked: {\n        root.toplevel.activate(); // TODO: make this work with those who disable focus on activate because telegram is abusive\n    }\n\n    background: Rectangle {\n        id: background\n        radius: Looks.radius.medium\n        color: root.down ? Looks.colors.bg2Active : (root.hovered ? Looks.colors.bg2Hover : ColorUtils.transparentize(Looks.colors.bg2))\n        Behavior on color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n\n    contentItem: ColumnLayout {\n        id: contentItem\n        anchors.fill: parent\n        anchors.margins: root.padding\n        spacing: 5\n\n        RowLayout {\n            Layout.fillWidth: true\n            Layout.fillHeight: false\n            spacing: 8\n\n            WAppIcon {\n                id: appIcon\n                Layout.leftMargin: Looks.radius.large - root.padding + 2\n                Layout.alignment: Qt.AlignVCenter\n                iconName: AppSearch.guessIcon(root.toplevel.appId)\n                implicitSize: 16\n            }\n\n            Item {\n                id: appTitleContainer\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                implicitHeight: closeButton.implicitHeight // Enforce height, because closeButton doesn't contribute when it's invisible\n                WText {\n                    id: appTitleText\n                    anchors.fill: parent\n                    text: root.toplevel.title\n                    elide: Text.ElideRight\n                    font.pixelSize: Looks.font.pixelSize.large\n                    font.weight: Looks.font.weight.thin\n                    color: Looks.colors.fg1\n                }\n            }\n\n            WindowCloseButton {\n                id: closeButton\n            }\n        }\n\n        Item {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            Layout.margins: Looks.radius.large - root.padding\n            Layout.topMargin: 0\n            implicitWidth: Math.max(screencopyView.implicitWidth, 80)\n            implicitHeight: screencopyView.implicitHeight\n\n            ScreencopyView {\n                id: screencopyView\n                anchors.centerIn: parent\n                captureSource: root.toplevel\n                live: true\n                paintCursor: true\n                constraintSize: Qt.size(root.previewWidthConstraint, root.previewHeightConstraint)\n            }\n        }\n    }\n\n    component WindowCloseButton: CloseButton {\n        visible: root.hovered\n        Layout.leftMargin: 4\n        implicitHeight: 30\n        implicitWidth: 30\n        radius: Looks.radius.large - root.padding\n        onClicked: {\n            root.toplevel.close();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tray/Tray.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt.labs.synchronizer\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\n\nRowLayout {\n    id: root\n\n    property bool overflowOpen: false\n    property bool dragging: false\n\n    Layout.fillHeight: true\n    spacing: 0\n\n    BarIconButton {\n        id: overflowButton\n\n        visible: (TrayService.unpinnedItems.length > 0 || root.dragging)\n        checked: root.overflowOpen\n\n        iconName: \"chevron-down\"\n        iconMonochrome: true\n        iconRotation: (Config.options.waffles.bar.bottom ? 180 : 0) + (root.overflowOpen ? 180 : 0)\n        Behavior on iconRotation {\n            animation: Looks.transition.rotate.createObject(this)\n        }\n\n        onClicked: {\n            root.overflowOpen = !root.overflowOpen;\n        }\n\n        TrayOverflowMenu {\n            id: trayOverflowLayout\n            Synchronizer on active {\n                property alias source: root.overflowOpen\n            }\n        }\n\n        BarToolTip {\n            extraVisibleCondition: overflowButton.shouldShowTooltip\n            text: Translation.tr(\"Show hidden icons\")\n        }\n\n        DropArea {\n            id: pinDropArea\n            anchors.fill: parent\n            property bool willPin: false\n            onEntered: willPin = true\n            onExited: willPin = false\n        }\n    }\n\n    Repeater {\n        model: ScriptModel {\n            values: TrayService.pinnedItems\n        }\n        delegate: TrayButton {\n            id: trayButton\n            required property var modelData\n            item: modelData\n\n            property real initialX\n            property real initialY\n\n            MouseArea {\n                id: dragArea\n                anchors.fill: parent\n                drag.target: parent\n                drag.axis: Drag.XAxis\n                drag.threshold: 2\n\n                onPressed: event => {\n                    trayButton.Drag.hotSpot.x = event.x;\n                    trayButton.initialX = trayButton.x;\n                    root.dragging = true;\n                    trayButton.Drag.active = true;\n                }\n                onPositionChanged: {\n                    pinTooltip.updateAnchor();\n                }\n                onReleased: {\n                    if (!dragArea.drag.active) {\n                        trayButton.click();\n                    } else {\n                        if (pinDropArea.containsDrag && pinDropArea.willPin) {\n                            // Quickshell would crash if we don't hide this item first. Took me fucking 3 hours to figure out...\n                            trayButton.visible = false;\n                            TrayService.togglePin(trayButton.item.id);\n                            pinDropArea.willPin = false;\n                        } else {\n                            trayButton.x = trayButton.initialX;\n                        }\n                    }\n                    trayButton.Drag.active = false;\n                    root.dragging = false;\n                }\n            }\n\n            BarToolTip {\n                id: pinTooltip\n                extraVisibleCondition: trayButton.Drag.active && pinDropArea.containsDrag && pinDropArea.willPin\n                horizontalPadding: 6\n                verticalPadding: 6\n                realContentItem: FluentIcon {\n                    anchors.centerIn: parent\n                    icon: \"pin-off\"\n                    implicitSize: 18\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayButton.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.SystemTray\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\n\nBarIconButton {\n    id: root\n\n    required property SystemTrayItem item\n    property alias menuOpen: menu.visible\n    readonly property bool barAtBottom: Config.options.waffles.bar.bottom\n    iconSource: item.icon\n    iconScale: 0\n    Component.onCompleted: {\n        root.iconScale = 1\n    }\n    Behavior on iconScale {\n        animation: Looks.transition.enter.createObject(this)\n    }\n\n    onClicked: {\n        item.activate();\n    }\n\n    altAction: () => {\n        if (item.hasMenu) menu.open()\n    }\n\n    // This is lazy, but it's not like tray menus on Windoes are consistent...\n    // TODO: Figure out how to do cascading menus then use a custom menu\n    QsMenuAnchor {\n        id: menu\n        menu: root.item.menu\n        anchor {\n            adjustment: PopupAdjustment.ResizeY | PopupAdjustment.SlideX\n            item: root\n            gravity: root.barAtBottom ? Edges.Top : Edges.Bottom\n            edges: root.barAtBottom ? Edges.Top : Edges.Bottom\n        }\n    }\n\n    BarToolTip {\n        extraVisibleCondition: root.shouldShowTooltip && !root.Drag.active\n        text: TrayService.getTooltipForItem(root.item)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/bar/tray/TrayOverflowMenu.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\n\nBarPopup {\n    id: root\n\n    closeOnFocusLost: false\n    onFocusCleared: {\n        const hasMenuOpen = contentItem.children.some(c => (c.menuOpen));\n        if (!hasMenuOpen)\n            root.close();\n        else\n            root.grabFocus();\n    }\n\n    contentItem: Item {\n        id: contentItem\n        anchors.centerIn: parent\n        implicitWidth: contentGrid.implicitWidth\n        implicitHeight: contentGrid.implicitHeight\n        GridLayout {\n            id: contentGrid\n            anchors.centerIn: parent\n            rows: Math.floor(Math.sqrt(TrayService.unpinnedItems.length))\n            columns: Math.ceil(TrayService.unpinnedItems.length / rows)\n            columnSpacing: 0\n            rowSpacing: 0\n\n            Repeater {\n                model: ScriptModel {\n                    values: TrayService.unpinnedItems\n                    onValuesChanged: {\n                        root.updateAnchor();\n                        if (values.length === 0) {\n                            root.close();\n                        }\n                    }\n                }\n                delegate: TrayButton {\n                    id: trayButton\n                    required property var modelData\n                    item: modelData\n\n                    topInset: 0\n                    bottomInset: 0\n                    implicitWidth: 40\n                    implicitHeight: 40\n\n                    colBackground: ColorUtils.transparentize(Looks.colors.bg2)\n                    colBackgroundHover: Looks.colors.bg2Hover\n                    colBackgroundActive: Looks.colors.bg2Active\n\n                    onMenuOpenChanged: {\n                        // The overflow menu should only be closed when the user clicks outside\n                        // However the focus grab refuses to reactivate, so we can't have that\n                        // But most of the time the user dismisses the menu by clicking outside anyway,\n                        // so this is acceptable.\n                        if (!menuOpen) {\n                            root.close();\n                        }\n                    }\n\n                    property real initialX\n                    property real initialY\n\n                    Behavior on x {\n                        animation: Looks.transition.move.createObject(this)\n                    }\n                    Behavior on y {\n                        animation: Looks.transition.move.createObject(this)\n                    }\n\n                    MouseArea {\n                        id: dragArea\n                        anchors.fill: parent\n                        drag.target: parent\n                        drag.threshold: 2\n\n                        onPressed: event => {\n                            trayButton.Drag.hotSpot.x = event.x;\n                            trayButton.Drag.hotSpot.y = event.y;\n                            trayButton.initialX = trayButton.x;\n                            trayButton.initialY = trayButton.y;\n                            trayButton.Drag.active = true;\n                        }\n                        onReleased: {\n                            if (!dragArea.drag.active) {\n                                trayButton.click();\n                            } else {\n                                if (!unpinDropArea.containsDrag && unpinDropArea.willUnpin) {\n                                    // Quickshell would crash if we don't hide this item first. Took me fucking 3 hours to figure out...\n                                    trayButton.visible = false;\n                                    TrayService.togglePin(trayButton.item.id);\n                                    unpinDropArea.willUnpin = false;\n                                } else {\n                                    trayButton.x = trayButton.initialX;\n                                    trayButton.y = trayButton.initialY;\n                                }\n                            }\n                            trayButton.Drag.active = false;\n                        }\n                    }\n                }\n            }\n        }\n\n        DropArea {\n            id: unpinDropArea\n            anchors.fill: parent\n            property bool willUnpin: false\n            onEntered: willUnpin = false\n            onExited: willUnpin = true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/lock/WaffleLock.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.common.panels.lock\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.sessionScreen as SessionScreen\n\nLockScreen {\n    id: root\n\n    property bool passwordView: false\n\n    lockSurface: Item {\n        id: lockSurfaceItem\n\n        Component.onCompleted: {\n            root.passwordView = false;\n            lockSurfaceItem.forceActiveFocus();\n        }\n\n        Keys.onPressed: {\n            interactables.switchToFocusedView();\n        }\n\n        StyledImage {\n            id: bg\n            z: 0\n            width: parent.width\n            height: parent.height\n            onStatusChanged: {\n                if (status === Image.Ready) {\n                    print(\"Lock wallpaper loaded\");\n                    print(lockSurfaceItem.height);\n                    y = -lockSurfaceItem.height;\n                    openAnim.restart();\n                }\n            }\n            source: Config.options.background.wallpaperPath\n            fillMode: Image.PreserveAspectCrop\n\n            PropertyAnimation {\n                id: openAnim\n                target: bg\n                property: \"y\"\n                to: 0\n                duration: 350\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        GaussianBlur {\n            z: 1\n            anchors.fill: bg\n            source: bg\n            radius: 100\n            samples: radius * 2 + 1\n            scale: root.passwordView ? 1.1 : 1\n            opacity: root.passwordView ? 1 : 0\n\n            Behavior on opacity {\n                animation: Looks.transition.opacity.createObject(this)\n            }\n\n            Behavior on scale {\n                NumberAnimation {\n                    duration: 400\n                    easing.type: Easing.BezierSpline\n                    easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n                }\n            }\n        }\n\n        Interactables {\n            id: interactables\n            z: 2\n            anchors.fill: bg\n        }\n    }\n\n    component Interactables: Rectangle {\n        id: interactablesComponent\n        color: ColorUtils.transparentize(\"#000000\", 0.8)\n        // Button {\n        //     onClicked: {\n        //         root.context.unlocked(LockContext.ActionEnum.Unlock);\n        //         GlobalStates.screenLocked = false;\n        //     }\n        //     text: \"woah it doesnt work let me out pls uwu colon three\"\n        // }\n\n        function switchToFocusedView() {\n            switchToPasswordViewAnim.restart();\n        }\n\n        SequentialAnimation {\n            id: switchToPasswordViewAnim\n            PropertyAnimation {\n                target: unfocusedContent\n                property: \"y\"\n                from: 0\n                to: -height * 1.1\n                duration: 250\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n            }\n            ScriptAction {\n                script: {\n                    root.passwordView = true;\n                }\n            }\n        }\n\n        Item {\n            id: unfocusedContent\n            width: parent.width\n            height: parent.height\n            visible: !root.passwordView\n            ClockTextGroup {\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    top: parent.top\n                    topMargin: interactablesComponent.height * 0.1\n                }\n            }\n            RowLayout {\n                anchors {\n                    bottom: parent.bottom\n                    right: parent.right\n                    bottomMargin: 21\n                    rightMargin: 31\n                }\n                IconIndicator {\n                    baseIcon: \"wifi-1\"\n                    icon: WIcons.internetIcon\n                }\n                IconIndicator {\n                    baseIcon: WIcons.batteryIcon\n                    icon: WIcons.batteryLevelIcon\n                }\n            }\n        }\n\n        Item {\n            id: focusedContent\n            anchors.fill: parent\n            visible: root.passwordView\n\n            PasswordGroup {\n                visible: root.passwordView\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    verticalCenter: parent.verticalCenter\n                }\n            }\n\n            RowLayout {\n                visible: root.passwordView\n                anchors {\n                    bottom: parent.bottom\n                    right: parent.right\n                    bottomMargin: 21\n                    rightMargin: 31\n                }\n                SessionScreen.PowerButton {\n                    id: powerButton\n                }\n            }\n        }\n    }\n\n    component IconIndicator: Item {\n        id: iconIndicator\n        required property string baseIcon\n        required property string icon\n        default property alias indicatorData: iconWidget.data\n        implicitWidth: 40\n        implicitHeight: 40\n        FluentIcon {\n            id: iconWidget\n            anchors.centerIn: parent\n            icon: iconIndicator.baseIcon\n            color: Looks.darkColors.inactiveIcon\n            implicitSize: 20\n            FluentIcon {\n                anchors.fill: parent\n                icon: iconIndicator.icon\n            }\n        }\n    }\n\n    component ClockTextGroup: Column {\n        id: clockTextGroup\n        spacing: -3\n\n        WText {\n            anchors.horizontalCenter: parent.horizontalCenter\n            color: Looks.darkColors.fg\n            font.pixelSize: 133\n            font.weight: Looks.font.weight.strong\n            text: {\n                // Don't take am/pm\n                // Match groups of digits separated by non-digit chars (e.g., \"12:34\", \"12.34\", \"12-34\")\n                let match = DateTime.time.match(/(\\d{1,2})\\D+(\\d{2})/);\n                return match ? `${match[1]}${DateTime.time.match(/\\D+/)[0]}${match[2]}` : DateTime.time;\n            }\n        }\n\n        WText {\n            id: dateLabel\n            color: Looks.darkColors.fg\n            anchors.horizontalCenter: parent.horizontalCenter\n            font.pixelSize: 28\n            font.weight: Looks.font.weight.strong\n            text: DateTime.collapsedCalendarFormat\n        }\n    }\n\n    component PasswordGroup: ColumnLayout {\n        id: passwordGroup\n        spacing: 15\n\n        WUserAvatar {\n            Layout.alignment: Qt.AlignHCenter\n            sourceSize: Qt.size(192, 192)\n        }\n\n        WText {\n            Layout.alignment: Qt.AlignHCenter\n            text: SystemInfo.username\n            color: Looks.darkColors.fg\n            font.pixelSize: 26\n            font.weight: Looks.font.weight.strong\n        }\n\n        Rectangle {\n            id: passwordInputWrapper\n            Layout.topMargin: 10\n            Layout.alignment: Qt.AlignHCenter\n            Layout.bottomMargin: 132\n            color: \"transparent\"\n            implicitWidth: 296\n            implicitHeight: 36\n            border.width: 2\n            border.color: Looks.applyContentTransparency(Looks.darkColors.bg1Border)\n            radius: Looks.radius.medium\n\n            Rectangle {\n                id: passwordInputBackground\n                anchors.fill: parent\n                anchors.margins: 2\n                radius: Looks.radius.small + 1\n                color: passwordInput.focus ? Looks.applyBackgroundTransparency(Looks.darkColors.bg1Base) : Looks.applyContentTransparency(Looks.darkColors.bg1)\n\n                RowLayout {\n                    anchors.fill: parent\n                    anchors.margins: 6\n                    spacing: 3\n\n                    WTextInput {\n                        id: passwordInput\n                        Layout.fillHeight: true\n                        Layout.fillWidth: true\n                        verticalAlignment: TextInput.AlignVCenter\n                        inputMethodHints: Qt.ImhSensitiveData\n                        echoMode: passwordVisibilityButton.pressed ? TextInput.Normal : TextInput.Password\n                        color: Looks.darkColors.fg\n\n                        font.pixelSize: 12\n                        WText {\n                            anchors.left: parent.left\n                            anchors.verticalCenter: parent.verticalCenter\n                            visible: passwordInput.text.length === 0\n                            text: Translation.tr(\"Password\")\n                            font.pixelSize: Looks.font.pixelSize.large\n                            color: Looks.darkColors.fg\n                            opacity: 0.8\n                        }\n\n                        onTextChanged: root.context.currentText = this.text\n                        onAccepted: {\n                            root.context.tryUnlock();\n                        }\n                        Connections {\n                            target: root.context\n                            function onCurrentTextChanged() {\n                                passwordInput.text = root.context.currentText;\n                            }\n                        }\n                        Connections {\n                            target: root\n                            function onPasswordViewChanged() {\n                                passwordInput.forceActiveFocus();\n                            }\n                        }\n\n                        Keys.onPressed: event => {\n                            root.context.resetClearTimer();\n                        }\n\n                        MouseArea {\n                            anchors.fill: parent\n                            acceptedButtons: Qt.NoButton\n                            cursorShape: Qt.IBeamCursor\n                        }\n                    }\n\n                    PasswordBoxButton {\n                        id: passwordVisibilityButton\n                        property bool passwordVisible: false\n                        visible: passwordInput.text.length > 0\n                        onPressed: passwordVisible = true\n                        onReleased: passwordVisible = false\n                        icon.name: passwordVisible ? \"eye-off\" : \"eye\"\n                    }\n\n                    PasswordBoxButton {\n                        onClicked: {\n                            root.context.tryUnlock();\n                        }\n                        icon.name: \"arrow-right\"\n                    }\n                }\n            }\n            Rectangle {\n                id: activeIndicatorLine\n                anchors {\n                    left: parent.left\n                    right: parent.right\n                    bottom: parent.bottom\n                }\n                implicitHeight: 2\n                color: passwordInput.focus ? Looks.colors.accent : Looks.applyContentTransparency(Looks.darkColors.bg2Border)\n            }\n\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: passwordInputWrapper.width\n                    height: passwordInputWrapper.height\n                    radius: passwordInputWrapper.radius\n                }\n            }\n        }\n\n        Item {}\n    }\n\n    component PasswordBoxButton: WButton {\n        id: pwBoxBtn\n        implicitWidth: 28\n        implicitHeight: 22\n\n        property color colBackground: ColorUtils.transparentize(Looks.darkColors.bg1)\n        property color colBackgroundHover: ColorUtils.transparentize(Looks.darkColors.bg2Hover)\n        property color colBackgroundActive: ColorUtils.transparentize(Looks.darkColors.bg2Active)\n        fgColor: checked ? Looks.colors.accentFg : Looks.darkColors.fg\n\n        checked: hovered\n\n        contentItem: Item {\n            FluentIcon {\n                color: pwBoxBtn.fgColor\n                anchors.centerIn: parent\n                icon: pwBoxBtn.icon.name\n                implicitSize: 16\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/AcrylicButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nWButton {\n    id: root\n\n    colBackground: Looks.colors.bg1\n    colBackgroundHover: Looks.colors.bg1Hover\n    colBackgroundActive: Looks.colors.bg1Active\n    property color colBackgroundBorder\n    property color color\n    property alias border: background.border\n    property alias shinyColor: background.borderColor\n\n    colBackgroundBorder: ColorUtils.transparentize(color, (root.checked || root.hovered) ? Looks.backgroundTransparency : 0)\n    color: {\n        if (root.down) {\n            return root.colBackgroundActive\n        } else if ((root.hovered && !root.down) || root.checked) {\n            return root.colBackgroundHover\n        } else {\n            return root.colBackground\n        }\n    }\n\n    background: AcrylicRectangle {\n        id: background\n        shiny: ((root.hovered && !root.down) || root.checked)\n        color: root.color\n        radius: Looks.radius.medium\n        border.width: 1\n        border.color: root.colBackgroundBorder\n\n        Behavior on border.color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/AcrylicRectangle.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nRectangle {\n    id: root\n\n    property bool shiny: true // Top border\n    property color borderColor: ColorUtils.transparentize(Looks.colors.bg1Hover, 0.7)\n    property color internalBorderColor: ColorUtils.transparentize(borderColor, shiny ? 0.0 : 1)\n    color: Looks.colors.bg1Hover\n    radius: Looks.radius.medium\n    Behavior on color {\n        animation: Looks.transition.color.createObject(this)\n    }\n    Behavior on internalBorderColor {\n        animation: Looks.transition.color.createObject(this)\n    }\n    onInternalBorderColorChanged: {\n        borderCanvas.requestPaint();\n    }\n    \n    // 1px border at the top or bottom\n    Canvas {\n        id: borderCanvas\n        anchors.fill: parent\n        // For dark mode we have a shiny top border, and for light mode we have sort of a shadow\n        rotation: Looks.dark ? 0 : 180\n        onPaint: {\n            var ctx = getContext(\"2d\");\n            ctx.clearRect(0, 0, width, height);\n\n            var borderColor = root.internalBorderColor;\n\n            var r = root.radius;\n            var fadeLength = Math.max(1, r);\n            var fadeLengthPercent = fadeLength / width;\n\n            // Compute normalized stops\n            var leftFadeStop = fadeLengthPercent;\n            var rightFadeStop = 1 - fadeLengthPercent;\n\n            var grad = ctx.createLinearGradient(0, 0, width, 0);\n            grad.addColorStop(0, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0));\n            grad.addColorStop(leftFadeStop, borderColor);\n            grad.addColorStop(rightFadeStop, borderColor);\n            grad.addColorStop(1, Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0));\n\n            ctx.strokeStyle = grad;\n            ctx.lineWidth = 1;\n\n            ctx.beginPath();\n            ctx.moveTo(r, 0.5);\n            ctx.lineTo(width - r, 0.5);\n            // Top-right curve\n            ctx.arcTo(width, 0.5, width, r + 0.5, r);\n            // Top-left curve\n            ctx.moveTo(width - r, 0.5);\n            ctx.arcTo(0, 0.5, 0, r + 0.5, r);\n            ctx.stroke();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/BodyRectangle.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nRectangle {\n    Layout.fillHeight: true\n    Layout.fillWidth: true\n    color: Looks.colors.bgPanelBody\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/CloseButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\nimport Quickshell\n\nButton {\n    id: reusableCloseButton\n    implicitHeight: 30\n    implicitWidth: 30\n    property alias radius: closeButtonBg.radius\n\n    Rectangle {\n        z: 0\n        color: \"transparent\"\n        anchors.fill: closeButtonBg\n        anchors.margins: -1\n        opacity: closeButtonBg.opacity\n        border.width: 1\n        radius: closeButtonBg.radius + 1\n        border.color: Looks.colors.bg2Border\n    }\n\n    background: Rectangle {\n        id: closeButtonBg\n        z: 1\n        opacity: reusableCloseButton.hovered ? 1 : 0\n        color: reusableCloseButton.pressed ? Looks.colors.dangerActive : Looks.colors.danger\n        Behavior on opacity {\n            animation: Looks.transition.opacity.createObject(this)\n        }\n        Behavior on color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n    \n    contentItem: FluentIcon {\n        z: 2\n        anchors.centerIn: parent\n        icon: \"dismiss\"\n        implicitSize: 10\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/FluentIcon.qml",
    "content": "import QtQuick\nimport org.kde.kirigami as Kirigami\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nKirigami.Icon {\n    id: root\n    required property string icon\n    property bool filled: false\n    property alias monochrome: root.isMask\n    // Should be 16, but it appears the icons have some padding, \n    // Unlike the Windows-only Segoe UI icons, the open source FluentUI ones are hella small\n    property int implicitSize: 20\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    source: icon === \"\" ? \"\" : `${Looks.iconsPath}/${root.icon}${filled ? \"-filled\" : \"\"}.svg`\n    fallback: root.icon\n    roundToIconSize: false\n    color: Looks.colors.fg\n    isMask: true\n    animated: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/FooterRectangle.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nRectangle {\n    Layout.fillHeight: false\n    Layout.fillWidth: true\n    color: \"transparent\"\n\n    implicitWidth: 358\n    implicitHeight: 47\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/Looks.qml",
    "content": "pragma ComponentBehavior: Bound\npragma Singleton\n\nimport QtQuick\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\n\nSingleton {\n    id: root\n    property QtObject darkColors\n    property QtObject lightColors\n    property QtObject colors\n    property QtObject radius\n    property QtObject font\n    property QtObject transition\n    property string iconsPath: `${Directories.assetsPath}/icons/fluent`\n    property bool dark: Appearance.m3colors.darkmode\n\n    readonly property bool transparencyEnabled: Config.options.appearance.transparency.enable\n    property real backgroundTransparency: transparencyEnabled ? 0.16 : 0\n    property real panelBackgroundTransparency: transparencyEnabled ? 0.14 : 0\n    property real panelLayerTransparency: root.dark ? 0.9 : 0.7\n    property real contentTransparency: root.dark ? 0.87 : 0.5\n    function applyBackgroundTransparency(col) {\n        return ColorUtils.applyAlpha(col, 1 - root.backgroundTransparency)\n    }\n    function applyContentTransparency(col) {\n        return ColorUtils.applyAlpha(col, 1 - root.contentTransparency)\n    }\n    lightColors: QtObject {\n        id: lightColors\n        property color bgPanelBody: \"#F2F2F2\"\n        property color bgPanelSeparator: \"#E0E0E0\"\n        property color bg0: \"#EEEEEE\"\n        property color bg0Border: '#BEBEBE'\n        property color bg1Base: \"#F7F7F7\"\n        property color bg1: \"#F7F7F7\"\n        property color bg1Hover: \"#F7F7F7\"\n        property color bg1Active: '#EFEFEF'\n        property color bg1Border: '#E9E9E9'\n        property color bg2: \"#FBFBFB\"\n        property color bg2Base: \"#FBFBFB\"\n        property color bg2Hover: '#ffffff'\n        property color bg2Active: '#eeeeee'\n        property color bg2Border: '#E0E0E0'\n        property color subfg: \"#5C5C5C\"\n        property color fg: \"#000000\"\n        property color fg1: \"#626262\"\n        property color inactiveIcon: \"#C4C4C4\"\n        property color controlBgInactive: '#555458'\n        property color controlBg: '#807F85'\n        property color controlBgHover: '#57575B'\n        property color controlFg: \"#FFFFFF\"\n        property color accentUnfocused: \"#848484\"\n        property color link: \"#235CCF\"\n        property color inputBg: ColorUtils.transparentize(bg0, 0.4)\n    }\n    darkColors: QtObject {\n        id: darkColors\n        property color bgPanelBody: '#242424'\n        property color bgPanelSeparator: \"#191919\"\n        property color bg0: \"#1C1C1C\"\n        property color bg0Border: \"#404040\"\n        property color bg1Base: '#2C2C2C'\n        property color bg1: '#2C2C2C'\n        property color bg1Hover: \"#292929\"\n        property color bg1Active: '#252525'\n        property color bg1Border: '#bebebe'\n        property color bg2Base: \"#313131\"\n        property color bg2: '#313131'\n        property color bg2Hover: '#363636'\n        property color bg2Active: '#2B2B2B'\n        property color bg2Border: '#404040'\n        property color subfg: \"#CED1D7\"\n        property color fg: \"#FFFFFF\"\n        property color fg1: \"#D1D1D1\"\n        property color inactiveIcon: \"#494949\"\n        property color controlBgInactive: \"#CDCECF\"\n        property color controlBg: \"#9B9B9B\"\n        property color controlBgHover: \"#CFCED1\"\n        property color controlFg: \"#454545\"\n        property color accentUnfocused: \"#989898\"\n        property color link: \"#A7C9FC\"\n        property color inputBg: ColorUtils.transparentize(darkColors.bg0, 0.5)\n    }\n    colors: QtObject {\n        id: colors\n        // Special\n        property color shadow: ColorUtils.transparentize('#161616', 0.62)\n        property color ambientShadow: ColorUtils.transparentize(\"#000000\", 0.75)\n        property color bgPanelFooterBase: root.dark ? root.darkColors.bg0 : root.lightColors.bg0\n        property color bgPanelFooterBackground: ColorUtils.transparentize(root.dark ? root.darkColors.bg0 : root.lightColors.bg0, root.panelBackgroundTransparency)\n        property color bgPanelFooter: ColorUtils.transparentize(bgPanelFooterBackground, root.panelLayerTransparency)\n        property color bgPanelBodyBase: root.dark ? root.darkColors.bgPanelBody : root.lightColors.bgPanelBody\n        property color bgPanelBody: ColorUtils.solveOverlayColor(bgPanelFooterBackground,bgPanelBodyBase, 1 - root.panelLayerTransparency)\n        property color bgPanelSeparator: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bgPanelSeparator : root.lightColors.bgPanelSeparator, 1 - root.panelBackgroundTransparency)\n        // Layer 0\n        property color bg0Base: root.dark ? root.darkColors.bg0 : root.lightColors.bg0\n        property color bg0: ColorUtils.transparentize(bg0Base, root.backgroundTransparency)\n        property color bg0Border: ColorUtils.transparentize(root.dark ? root.darkColors.bg0Border : root.lightColors.bg0Border, root.backgroundTransparency)\n        // Layer 1\n        property color bg1Base: root.dark ? root.darkColors.bg1 : root.lightColors.bg1\n        property color bg1: ColorUtils.solveOverlayColor(bg0Base, bg1Base, 1 - root.contentTransparency)\n        property color bg1Hover: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Hover : root.lightColors.bg1Hover, 1 - root.contentTransparency)\n        property color bg1Active: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Active : root.lightColors.bg1Active, 1 - root.contentTransparency)\n        property color bg1Border: ColorUtils.solveOverlayColor(bg0Base, root.dark ? root.darkColors.bg1Border : root.lightColors.bg1Border, 1 - root.contentTransparency)\n        // Layer 2\n        property color bg2Base: root.dark ? root.darkColors.bg2 : root.lightColors.bg2\n        property color bg2: ColorUtils.solveOverlayColor(bgPanelBodyBase, bg2Base, 1 - root.contentTransparency)\n        property color bg2Hover: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Hover : root.lightColors.bg2Hover, 1 - root.contentTransparency)\n        property color bg2Active: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Active : root.lightColors.bg2Active, 1 - root.contentTransparency)\n        property color bg2Border: ColorUtils.solveOverlayColor(bgPanelBodyBase, root.dark ? root.darkColors.bg2Border : root.lightColors.bg2Border, 1 - root.contentTransparency)\n        // Foreground / Text\n        property color subfg: root.dark ? root.darkColors.subfg : root.lightColors.subfg\n        property color fg: root.dark ? root.darkColors.fg : root.lightColors.fg\n        property color fg1: root.dark ? root.darkColors.fg1 : root.lightColors.fg1\n        property color inactiveIcon: root.dark ? root.darkColors.inactiveIcon : root.lightColors.inactiveIcon\n        property color link: root.dark ? root.darkColors.link : root.lightColors.link\n        // Controls\n        property color controlBgInactive: root.dark ? root.darkColors.controlBgInactive : root.lightColors.controlBgInactive\n        property color controlBg: root.dark ? root.darkColors.controlBg : root.lightColors.controlBg\n        property color controlBgHover: root.dark ? root.darkColors.controlBgHover : root.lightColors.controlBgHover\n        property color controlFg: root.dark ? root.darkColors.controlFg : root.lightColors.controlFg\n        property color inputBg: root.dark ? root.darkColors.inputBg : root.lightColors.inputBg\n        property color danger: \"#C42B1C\"\n        property color dangerActive: \"#B62D1F\"\n        property color warning: \"#FF9900\"\n        // Accent\n        property color accent: Appearance.colors.colPrimary\n        property color accentHover: Appearance.colors.colPrimaryHover\n        property color accentActive: Appearance.colors.colPrimaryActive\n        property color accentUnfocused: root.dark ? root.darkColors.accentUnfocused : root.lightColors.accentUnfocused\n        property color accentFg: ColorUtils.isDark(accent) ? \"#FFFFFF\" : \"#000000\"\n        property color selection: Appearance.colors.colPrimaryContainer\n        property color selectionFg: Appearance.colors.colOnPrimaryContainer\n    }\n\n    radius: QtObject {\n        id: radius\n        property int none: 0\n        property int small: 2\n        property int medium: 4\n        property int large: 8\n        property int xLarge: 12\n    }\n\n    font: QtObject {\n        id: font\n        property QtObject family: QtObject {\n            property string ui: \"Noto Sans\"\n        }\n        property QtObject weight: QtObject { // Noto is not Segoe, so we might use slightly different weights\n            property int thin: Font.Normal\n            property int regular: Font.Medium\n            property int strong: Font.DemiBold\n            property int stronger: (Font.DemiBold + 2*Font.Bold) / 3\n            property int strongest: Font.Bold\n        }\n        property QtObject pixelSize: QtObject {\n            property real normal: 11\n            property real large: 13\n            property real larger: 15\n            property real xlarger: 17\n        }\n        property QtObject variableAxes: QtObject {\n            property var ui: ({\n                \"wdth\": 25\n            })\n        }\n    }\n\n    transition: QtObject {\n        id: transition\n\n        property int velocity: 850\n\n        property QtObject easing: QtObject {\n            property QtObject bezierCurve: QtObject {\n                readonly property list<real> easeInOut: [0.42,0.00,0.58,1.00,1,1]\n                readonly property list<real> easeIn: [0,1,1,1,1,1]\n                readonly property list<real> easeOut: [1,0,1,1,1,1]\n            }\n        }\n\n        property Component color: Component {\n            ColorAnimation {\n                duration: 80\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        property Component opacity: Component {\n            NumberAnimation {\n                duration: 120\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        property Component resize: Component { // TODO: better curve needed\n            NumberAnimation {\n                duration: 200\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        property Component enter: Component {\n            NumberAnimation {\n                duration: 250\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        property Component exit: Component {\n            NumberAnimation {\n                duration: 250\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeOut\n            }\n        }\n\n        property Component move: Component {\n            NumberAnimation {\n                duration: 170\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeInOut\n            }\n        }\n\n        property Component rotate: Component {\n            NumberAnimation {\n                duration: 170\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeInOut\n            }\n        }\n\n        property Component anchor: Component {\n            AnchorAnimation {\n                duration: 160\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        property Component longMovement: Component {\n            NumberAnimation {\n                duration: 1000\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        property Component scroll: Component {\n            NumberAnimation {\n                duration: 250\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: [0.0, 0.0, 0.25, 1.0, 1, 1]\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/VerticalPageIndicator.qml",
    "content": "pragma ComponentBehavior: Bound\nimport Qt.labs.synchronizer\nimport QtQuick\nimport QtQuick.Layouts\nimport QtQuick.Controls\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nColumn {\n    id: root\n\n    property bool showArrows: true\n    property int currentIndex: 0\n    property int count: 1\n    signal clicked(int index)\n    signal increasePage()\n    signal decreasePage()\n\n    visible: count > 1\n    spacing: 6\n\n    NavigationArrow {\n        visible: root.showArrows\n        down: false\n    }\n\n    Repeater {\n        model: root.count\n        delegate: MouseArea {\n            id: pageIndicator\n            required property int index\n            hoverEnabled: true\n            onClicked: root.clicked(index);\n            anchors.horizontalCenter: parent.horizontalCenter\n            implicitWidth: 6\n            implicitHeight: 6\n\n            Circle {\n                anchors.centerIn: parent\n                diameter: (index === root.currentIndex || pageIndicator.containsMouse) && !pageIndicator.pressed ? 6 : 4\n                color: pageIndicator.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg\n            }\n        }\n    }\n\n    NavigationArrow {\n        visible: root.showArrows\n        down: true\n    }\n\n    component NavigationArrow: FluentIcon {\n        id: navArrow\n        required property bool down\n        anchors.horizontalCenter: parent.horizontalCenter\n        implicitHeight: 12\n        implicitWidth: 12 - (2 * upArea.containsPress)\n        icon: down ? \"caret-down\" : \"caret-up\"\n        color: upArea.containsMouse ? Looks.colors.controlBgHover : Looks.colors.controlBg\n        filled: true\n        opacity: ((down && root.currentIndex < root.count - 1) || (!down && root.currentIndex > 0)) ? 1 : 0\n        MouseArea {\n            id: upArea\n            anchors.fill: parent\n            hoverEnabled: true\n            onClicked: navArrow.down ? root.increasePage() : root.decreasePage();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WAmbientShadow.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nRectangle {\n    id: root\n\n    required property var target\n    z: 0\n\n    anchors {\n        fill: target\n        margins: -border.width\n    }\n\n    border.color: Looks.colors.ambientShadow\n    border.width: 1\n    color: \"transparent\"\n    radius: target.radius + border.width\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WAppIcon.qml",
    "content": "import QtQuick\nimport org.kde.kirigami as Kirigami\nimport qs.services\nimport qs.modules.common\n\nKirigami.Icon {\n    id: root\n    required property string iconName\n    property bool separateLightDark: false\n    property bool tryCustomIcon: true\n    \n    property real implicitSize: 26\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    animated: true\n    roundToIconSize: false\n    fallback: root.iconName\n    source: tryCustomIcon ? `${Looks.iconsPath}/${root.iconName}${!root.separateLightDark ? \"\" : Looks.dark ? \"-dark\" : \"-light\"}.svg` : fallback\n\n    color: Looks.colors.fg\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WBarAttachedPanelContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n\n    signal closed\n\n    required property Item contentItem\n    property real visualMargin: 12\n    property int closeAnimDuration: 150\n    property bool revealFromSides: false\n    property bool revealFromLeft: true\n\n    function close() {\n        closeAnim.start();\n    }\n\n    readonly property bool barAtBottom: Config.options.waffles.bar.bottom\n\n    implicitHeight: contentItem.implicitHeight + visualMargin * 2\n    implicitWidth: contentItem.implicitWidth + visualMargin * 2\n\n    focus: true\n    Keys.onPressed: event => { // Esc to close\n        if (event.key === Qt.Key_Escape) {\n            content.close();\n        }\n    }\n\n    Item {\n        id: panelContent\n        anchors {\n            left: (root.revealFromSides && !root.revealFromLeft) ? undefined : parent.left\n            right: (root.revealFromSides && root.revealFromLeft) ? undefined : parent.right\n            top: (!root.revealFromSides && root.barAtBottom) ? undefined : parent.top\n            bottom: (!root.revealFromSides && !root.barAtBottom) ? undefined : parent.bottom\n            // Opening anim\n            bottomMargin: (!root.revealFromSides && root.barAtBottom) ? sourceEdgeMargin : root.visualMargin\n            topMargin: (!root.revealFromSides && !root.barAtBottom) ? sourceEdgeMargin : root.visualMargin\n            leftMargin: (root.revealFromSides && root.revealFromLeft) ? sideEdgeMargin : root.visualMargin\n            rightMargin: (root.revealFromSides && !root.revealFromLeft) ? sideEdgeMargin : root.visualMargin\n        }\n\n        Component.onCompleted: {\n            openAnim.start();\n        }\n\n        property real sourceEdgeMargin: -(implicitHeight + root.visualMargin)\n        property real sideEdgeMargin: -(implicitWidth + root.visualMargin)\n        OpenAnim {\n            id: openAnim\n            properties: \"sourceEdgeMargin, sideEdgeMargin\"\n        }\n        SequentialAnimation {\n            id: closeAnim\n            ParallelAnimation {\n                CloseAnim {\n                    property: \"sourceEdgeMargin\"\n                    to: -(implicitHeight + root.visualMargin)\n                }\n                CloseAnim {\n                    property: \"sideEdgeMargin\"\n                    to: -(implicitWidth + root.visualMargin)\n                }\n            }\n            ScriptAction {\n                script: {\n                    root.closed();\n                }\n            }\n        }\n        implicitWidth: root.contentItem.implicitWidth\n        implicitHeight: root.contentItem.implicitHeight\n        children: [root.contentItem]\n    }\n\n    component OpenAnim: PropertyAnimation {\n        target: panelContent\n        to: root.visualMargin\n        duration: 200\n        easing.type: Easing.BezierSpline\n        easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n    }\n    component CloseAnim: PropertyAnimation {\n        target: panelContent\n        duration: root.closeAnimDuration\n        easing.type: Easing.BezierSpline\n        easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WBorderedButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nWButton {\n    id: root\n\n    colBackground: Looks.colors.bg2\n    colBackgroundHover: Looks.colors.bg2Hover\n    colBackgroundActive: Looks.colors.bg2Active\n    property color colBorder: Looks.colors.bg2Border\n    property color colBorderToggled: Looks.colors.accent\n    border.color: checked ? colBorderToggled : colBorder\n    border.width: root.pressed ? 2 : 1\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WBorderlessButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nButton {\n    id: root\n\n    implicitHeight: 36\n\n    property color colBackground: ColorUtils.transparentize(Looks.colors.bg1)\n    property color colBackgroundHover: Looks.colors.bg1Hover\n    property color colBackgroundActive: Looks.colors.bg1Active\n    property color color\n    property color colForeground: Looks.colors.fg\n    color: {\n        if (!root.enabled) return colBackground;\n        if (root.down) {\n            return root.colBackgroundActive\n        } else if ((root.hovered && !root.down) || root.checked) {\n            return root.colBackgroundHover\n        } else {\n            return root.colBackground\n        }\n    }\n    property alias radius: background.radius\n\n    background: Rectangle {\n        id: background\n        radius: Looks.radius.medium\n        color: root.color\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\n// Generic button with background\nButton {\n    id: root\n\n    property color colBackground: ColorUtils.transparentize(Looks.colors.bg1)\n    property color colBackgroundHover: Looks.colors.bg2Hover\n    property color colBackgroundActive: Looks.colors.bg2Active\n    property color colBackgroundToggled: Looks.colors.accent\n    property color colBackgroundToggledHover: Looks.colors.accentHover\n    property color colBackgroundToggledActive: Looks.colors.accentActive\n    property color colForeground: Looks.colors.fg\n    property color colForegroundToggled: Looks.colors.accentFg\n    property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4)\n    property alias backgroundOpacity: backgroundRect.opacity\n    property color color: {\n        if (!root.enabled) return colBackground;\n        if (root.checked) {\n            if (root.down) {\n                return root.colBackgroundToggledActive;\n            } else if (root.hovered) {\n                return root.colBackgroundToggledHover;\n            } else {\n                return root.colBackgroundToggled;\n            }\n        }\n        if (root.down) {\n            return root.colBackgroundActive;\n        } else if (root.hovered) {\n            return root.colBackgroundHover;\n        } else {\n            return root.colBackground;\n        }\n    }\n    property color fgColor: {\n        if (!root.enabled) return root.colForegroundDisabled\n        if (root.checked) return root.colForegroundToggled\n        if (root.enabled) return root.colForeground\n        return root.colForeground\n    }\n    property alias horizontalAlignment: buttonText.horizontalAlignment\n    font {\n        family: Looks.font.family.ui\n        pixelSize: Looks.font.pixelSize.large\n        weight: Looks.font.weight.regular\n    }\n\n    // Hover stuff\n    signal hoverTimedOut\n    property bool shouldShowTooltip: false\n    ToolTip.delay: 400\n    property Timer hoverTimer: Timer {\n        id: hoverTimer\n        running: root.hovered\n        interval: root.ToolTip.delay\n        onTriggered: {\n            root.hoverTimedOut();\n        }\n    }\n    onHoverTimedOut: {\n        root.shouldShowTooltip = true;\n    }\n    onHoveredChanged: {\n        if (!root.hovered) {\n            root.shouldShowTooltip = false;\n            root.hoverTimer.stop();\n        }\n    }\n\n    property alias monochromeIcon: buttonIcon.monochrome\n    property alias buttonSpacing: contentLayout.spacing\n    property bool forceShowIcon: false\n\n    property var altAction: () => {}\n    property var middleClickAction: () => {}\n\n    property real inset: 0\n    topInset: inset\n    bottomInset: inset\n    leftInset: inset\n    rightInset: inset\n    property alias radius: backgroundRect.radius\n    property alias topLeftRadius: backgroundRect.topLeftRadius\n    property alias topRightRadius: backgroundRect.topRightRadius\n    property alias bottomLeftRadius: backgroundRect.bottomLeftRadius\n    property alias bottomRightRadius: backgroundRect.bottomRightRadius\n    property alias border: backgroundRect.border\n    horizontalPadding: 10\n    verticalPadding: 6\n    implicitHeight: contentItem.implicitHeight + verticalPadding * 2 + topInset + bottomInset\n    implicitWidth: contentItem.implicitWidth + horizontalPadding * 2 + leftInset + rightInset\n\n    background: Rectangle {\n        id: backgroundRect\n        radius: Looks.radius.medium\n        color: root.color\n        Behavior on color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.RightButton | Qt.MiddleButton\n        onClicked: event => {\n            if (event.button === Qt.LeftButton)\n                root.clicked();\n            if (event.button === Qt.RightButton)\n                root.altAction();\n            if (event.button === Qt.MiddleButton)\n                root.middleClickAction();\n        }\n    }\n\n    contentItem: Item {\n        anchors {\n            fill: parent\n            margins: root.inset\n        }\n        implicitWidth: contentLayout.implicitWidth\n        implicitHeight: contentLayout.implicitHeight\n        RowLayout {\n            id: contentLayout\n            anchors {\n                fill: parent\n                leftMargin: root.horizontalPadding\n                rightMargin: root.horizontalPadding\n            }\n            spacing: 12\n            FluentIcon {\n                id: buttonIcon\n                monochrome: true\n                implicitSize: 18\n                Layout.leftMargin: root.iconLeftMargin\n                Layout.fillWidth: false\n                Layout.alignment: Qt.AlignVCenter\n                icon: root.icon.name\n                color: root.fgColor\n                visible: root.icon.name !== \"\"\n            }\n            WText {\n                id: buttonText\n                Layout.fillWidth: true\n                Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft\n                text: root.text\n                horizontalAlignment: Text.AlignLeft\n                font: root.font\n                color: root.fgColor\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WChoiceButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nWButton {\n    id: root\n\n    property bool animateChoiceHighlight: true\n\n    Layout.fillWidth: true\n    implicitWidth: contentItem.implicitWidth\n    horizontalPadding: 10\n    verticalPadding: 11\n    buttonSpacing: 8\n\n    color: {\n        if (root.checked) {\n            if (root.down) {\n                return root.colBackgroundHover;\n            } else if (root.hovered && !root.down) {\n                return root.colBackgroundActive;\n            } else {\n                return root.colBackgroundHover;\n            }\n        }\n        if (root.down) {\n            return root.colBackgroundActive;\n        } else if (root.hovered && !root.down) {\n            return root.colBackgroundHover;\n        } else {\n            return root.colBackground;\n        }\n    }\n    fgColor: colForeground\n\n    background: Rectangle {\n        id: backgroundRect\n        radius: Looks.radius.medium\n        color: root.color\n        Behavior on color {\n            enabled: root.animateChoiceHighlight\n            animation: Looks.transition.color.createObject(this)\n        }\n\n        WFadeLoader {\n            anchors.left: parent.left\n            anchors.verticalCenter: parent.verticalCenter\n            shown: root.checked\n            sourceComponent: Rectangle {\n                implicitWidth: 3\n                implicitHeight: 3\n                radius: width / 2\n                color: Looks.colors.accent\n                property bool forceZeroHeight: true\n                height: forceZeroHeight ? 0 : Math.max(16, root.background.height - 18 * 2)\n                Component.onCompleted: {\n                    forceZeroHeight = false;\n                }\n\n                Behavior on height {\n                    enabled: root.animateChoiceHighlight\n                    animation: Looks.transition.opacity.createObject(this)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WFadeLoader.qml",
    "content": "import QtQuick\nimport qs.modules.common\n\n// Yes, this is (mostly) a copy of FadeLoader.\n// The animation of a Behavior cannot be changed... I'd love to be proven wrong.\nLoader {\n    id: root\n    property bool shown: true\n    property alias fade: opacityBehavior.enabled\n    property alias animation: opacityBehavior.animation\n    opacity: shown ? 1 : 0\n    visible: opacity > 0\n    active: opacity > 0\n\n    Behavior on opacity {\n        id: opacityBehavior\n        animation: Looks.transition.opacity.createObject(null)\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WIcons.qml",
    "content": "pragma Singleton\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.UPower\nimport qs.services\n\nSingleton {\n    id: root\n\n    function pathForName(iconName) {\n        return Quickshell.shellPath(`assets/icons/fluent/${iconName}.svg`);\n    }\n\n    function wifiIconForStrength(strength) {\n        if (strength > 75)\n            return \"wifi-1\";\n        if (strength > 50)\n            return \"wifi-2\";\n        if (strength > 25)\n            return \"wifi-3\";\n        return \"wifi-4\";\n    }\n\n    property string internetIcon: {\n        if (Network.ethernet)\n            return \"ethernet\";\n        if (Network.wifiEnabled) {\n            const strength = Network.networkStrength;\n            return wifiIconForStrength(strength);\n        }\n        if (Network.wifiStatus === \"connecting\")\n            return \"wifi-4\";\n        if (Network.wifiStatus === \"disconnected\")\n            return \"wifi-off\";\n        if (Network.wifiStatus === \"disabled\")\n            return \"wifi-off\";\n        return \"wifi-warning\";\n    }\n\n    property string batteryIcon: {\n        if (Battery.isCharging)\n            return \"battery-charge\";\n        if (Battery.isCriticalAndNotCharging)\n            return \"battery-warning\";\n        if (Battery.percentage >= 0.9)\n            return \"battery-full\";\n        return `battery-0`;\n    }\n\n    property string batteryLevelIcon: {\n        const discreteLevel = Math.ceil(Battery.percentage * 10);\n        return `battery-${discreteLevel > 9 ? \"full\" : discreteLevel}`;\n    }\n\n    property string volumeIcon: {\n        const muted = Audio.sink?.audio.muted ?? false;\n        const volume = Audio.sink?.audio.volume ?? 0;\n        if (muted)\n            return \"speaker-mute\";\n        if (volume == 0)\n            return \"speaker-none\";\n        if (volume < 0.5)\n            return \"speaker-1\";\n        return \"speaker\";\n    }\n\n    property string micIcon: {\n        const muted = Audio.source?.audio.muted ?? false;\n        return muted ? \"mic-off\" : \"mic\";\n    }\n\n    property string bluetoothIcon: BluetoothStatus.connected ? \"bluetooth-connected\" : BluetoothStatus.enabled ? \"bluetooth\" : \"bluetooth-disabled\"\n\n    property string nightLightIcon: Hyprsunset.temperatureActive ? \"weather-moon\" : \"weather-moon-off\"\n\n    property string notificationsIcon: Notifications.silent ? \"alert-snooze\" : \"alert\"\n\n    property string powerProfileIcon: {\n        switch (PowerProfiles.profile) {\n        case PowerProfile.PowerSaver:\n            return \"leaf-two\";\n        case PowerProfile.Balanced:\n            return \"flash-on\";\n        case PowerProfile.Performance:\n            return \"fire\";\n        }\n    }\n\n    function audioDeviceIcon(node) {\n        if (!node.isSink)\n            return \"mic-on\";\n        const monitor = /monitor|hdmi/i;\n        const headphones = /headset|headphone|bluez|wireless/i;\n        const speakers = /speaker|output/i;\n        if (monitor.test(node.nickname) || monitor.test(node.description) || monitor.test(node.name)) {\n            return \"desktop-speaker\";\n        }\n        if (headphones.test(node.nickname) || headphones.test(node.description) || headphones.test(node.name)) {\n            return \"headphones\";\n        }\n        if (speakers.test(node.nickname) || speakers.test(node.description) || speakers.test(node.name)) {\n            return \"speaker\";\n        }\n        return \"speaker\";\n    }\n\n    function audioAppIcon(node) {\n        let icon;\n        icon = AppSearch.guessIcon(node?.properties[\"application.icon-name\"] ?? \"\");\n        if (AppSearch.iconExists(icon))\n            return icon;\n        icon = AppSearch.guessIcon(node?.properties[\"node.name\"] ?? \"\");\n        return icon;\n    }\n\n    function bluetoothDeviceIcon(device) {\n        const systemIconName = device?.icon || \"\";\n        if (systemIconName.includes(\"headset\") || systemIconName.includes(\"headphones\"))\n            return \"headphones\";\n        if (systemIconName.includes(\"audio\"))\n            return \"speaker\";\n        if (systemIconName.includes(\"phone\"))\n            return \"phone\";\n        if (systemIconName.includes(\"mouse\"))\n            return \"bluetooth\";\n        if (systemIconName.includes(\"keyboard\"))\n            return \"keyboard\";\n        return \"bluetooth\";\n    }\n\n    function fluentFromMaterial(icon) {\n        switch (icon) {\n        case \"calculate\":\n            return \"calculator\";\n        case \"keyboard_return\":\n            return \"arrow-enter-left\";\n        case \"open_in_new\":\n            return \"open\";\n        case \"settings_suggest\":\n            return \"wand\";\n        case \"terminal\":\n            return \"app-generic\";\n        case \"travel_explore\":\n            return \"globe-search\";\n        case \"keep\":\n            return \"pin\";\n        case \"keep_off\":\n            return \"pin-off\";\n        default:\n            return \"apps\";\n        }\n    }\n\n    function guessIconForName(name) {\n        const lowerName = name.toLowerCase();\n        if (lowerName.includes(\"app\") || lowerName.includes(\"desktop\"))\n            return \"apps\";\n        if (lowerName.includes(\"news\"))\n            return \"news\";\n        if (lowerName.includes(\"new\") || lowerName.includes(\"create\") || lowerName.includes(\"add\"))\n            return \"add\";\n        if (lowerName.includes(\"open\"))\n            return \"open\";\n        if (lowerName.includes(\"friends\") || lowerName.includes(\"contact\") || lowerName.includes(\"family\"))\n            return \"people\";\n        if (lowerName.includes(\"community\"))\n            return \"people-team\";\n        if (lowerName.includes(\"library\"))\n            return \"library\";\n        if (lowerName.includes(\"setting\"))\n            return \"settings\";\n        if (lowerName.includes(\"gallery\"))\n            return \"image-copy\";\n        if (lowerName.includes(\"server\"))\n            return \"server\";\n        if (lowerName.includes(\"picture\") || lowerName.includes(\"photo\") || lowerName.includes(\"image\"))\n            return \"image\";\n        if (lowerName.includes(\"store\") || lowerName.includes(\"shop\"))\n            return \"store-microsoft\";\n        if (lowerName.includes(\"record\") || lowerName.includes(\"capture\"))\n            return \"record\";\n        if (lowerName.includes(\"screen\") || lowerName.includes(\"display\") || lowerName.includes(\"monitor\") || lowerName.includes(\"desktop\"))\n            return \"desktop\";\n\n        return \"apps\";\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WIndeterminateProgressBar.qml",
    "content": "import QtQuick\nimport Qt5Compat.GraphicalEffects\nimport qs\nimport qs.services\nimport qs.services.network\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nStyledIndeterminateProgressBar {\n    id: progressBar\n    implicitHeight: 3\n    background: null\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Rectangle {\n            width: progressBar.width\n            height: progressBar.height\n            radius: progressBar.height / 2\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WListView.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.widgets\nimport QtQuick\nimport QtQuick.Controls\n\nListView {\n    id: root\n\n    boundsBehavior: Flickable.DragOverBounds\n\n    ScrollBar.vertical: WScrollBar {}\n\n    displaced: Transition {\n        animations: [Looks.transition.enter.createObject(this, {\n                property: \"y\"\n            })]\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WMenu.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nMenu {\n    id: root\n\n    property bool downDirection: false\n    property bool hasIcons: false // TODO: implement\n\n    property color color: Looks.colors.bg1Base\n    property alias backgroundPane: bgPane\n\n    implicitWidth: background.implicitWidth + margins * 2\n    implicitHeight: background.implicitHeight + margins * 2\n    margins: 10\n    padding: 3\n    property real sourceEdgeMargin: -implicitHeight\n    clip: true\n\n    enter: Transition {\n        NumberAnimation {\n            property: \"sourceEdgeMargin\"\n            from: -root.implicitHeight\n            to: root.margins\n            duration: 200\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n        }\n    }\n    exit: Transition {\n        NumberAnimation {\n            property: \"sourceEdgeMargin\"\n            from: root.margins\n            to: -root.implicitHeight\n            duration: 150\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut\n        }\n    }\n\n    background: Item {\n        id: bgItem\n        implicitWidth: bgPane.implicitWidth\n        implicitHeight: bgPane.implicitHeight\n        WPane {\n            id: bgPane\n            anchors {\n                left: parent.left\n                right: parent.right\n                top: root.downDirection ? parent.top : undefined\n                bottom: root.downDirection ? undefined : parent.bottom\n                margins: root.margins\n                topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins\n                bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin\n            }\n            contentItem: Rectangle {\n                color: root.color\n                implicitWidth: menuListView.implicitWidth + root.padding * 2\n                implicitHeight: root.contentItem.implicitHeight + root.padding * 2\n            }\n\n        }\n    }\n\n    Component.onCompleted: {\n        menuListView.itemAtIndex(0)?.forceActiveFocus();\n    }\n\n    contentItem: Item {\n        implicitWidth: menuListView.implicitWidth\n        implicitHeight: menuListView.implicitHeight\n        WListView {\n            id: menuListView\n            interactive: contentHeight > height\n            anchors {\n                left: parent.left\n                right: parent.right\n                top: root.downDirection ? parent.top : undefined\n                bottom: root.downDirection ? undefined : parent.bottom\n                margins: root.margins // ????\n                topMargin: root.downDirection ? root.sourceEdgeMargin : root.margins\n                bottomMargin: root.downDirection ? root.margins : root.sourceEdgeMargin\n            }\n            clip: true\n            implicitHeight: contentHeight\n            implicitWidth: Array.from({\n                length: count\n            }, (_, i) => itemAtIndex(i)?.implicitWidth ?? 0).reduce((a, b) => a > b ? a : b)\n\n            model: root.contentModel\n        }\n    }\n\n    delegate: WMenuItem {\n        id: menuItemDelegate\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WMenuItem.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nMenuItem {\n    id: root\n\n    property color colBackground: ColorUtils.transparentize(Looks.colors.bg1)\n    property color colBackgroundHover: Looks.colors.bg2Hover\n    property color colBackgroundActive: Looks.colors.bg2Active\n    property color colBackgroundToggled: Looks.colors.bg2Hover\n    property color colBackgroundToggledHover: Looks.colors.bg2Active\n    property color colBackgroundToggledActive: Looks.colors.bg2Hover\n    property color colForeground: Looks.colors.fg\n    property color colForegroundToggled: Looks.colors.fg\n    property color colForegroundDisabled: ColorUtils.transparentize(Looks.colors.subfg, 0.4)\n    property color color: {\n        if (!root.enabled)\n            return colBackground;\n        if (root.checked) {\n            if (root.down) {\n                return root.colBackgroundToggledActive;\n            } else if (root.hovered) {\n                return root.colBackgroundToggledHover;\n            } else {\n                return root.colBackgroundToggled;\n            }\n        }\n        if (root.down) {\n            return root.colBackgroundActive;\n        } else if (root.hovered) {\n            return root.colBackgroundHover;\n        } else {\n            return root.colBackground;\n        }\n    }\n    property color fgColor: {\n        if (root.checked)\n            return root.colForegroundToggled;\n        if (root.enabled)\n            return root.colForeground;\n        return root.colForegroundDisabled;\n    }\n\n    property real inset: 2\n    topInset: inset\n    bottomInset: inset\n    leftInset: inset\n    rightInset: inset\n    horizontalPadding: 11\n\n    width: ListView.view?.width\n    height: visible ? implicitHeight : 0\n\n    background: Rectangle {\n        id: backgroundRect\n        radius: Looks.radius.medium\n        color: root.color\n        Behavior on color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n\n    implicitHeight: Math.max(28, contentItem.implicitHeight) + topInset + bottomInset\n    implicitWidth: contentItem.implicitWidth + leftInset + rightInset + leftPadding + rightPadding\n\n    contentItem: Item {\n        implicitWidth: contentLayout.implicitWidth\n        implicitHeight: contentLayout.implicitHeight\n\n        RowLayout {\n            id: contentLayout\n            anchors.fill: parent\n            spacing: 12\n            FluentIcon {\n                id: buttonIcon\n                monochrome: true\n                implicitSize: 20\n                Layout.fillWidth: false\n                Layout.alignment: Qt.AlignVCenter\n                color: root.fgColor\n                visible: root.icon.name !== \"\"\n                icon: root.icon.name\n            }\n            WText {\n                id: buttonText\n                Layout.fillWidth: true\n                Layout.alignment: Qt.AlignVCenter | Qt.AlignLeft\n                text: root.text\n                horizontalAlignment: Text.AlignLeft\n                font.pixelSize: Looks.font.pixelSize.large\n                color: root.fgColor\n            }\n        }\n\n        WFadeLoader {\n            anchors {\n                verticalCenter: parent.verticalCenter\n                left: parent.left\n                leftMargin: -root.leftPadding + width\n            }\n            shown: root.checked\n            sourceComponent: Rectangle {\n                implicitWidth: 3\n                implicitHeight: 3\n                radius: width / 2\n                color: Looks.colors.accent\n                property bool forceZeroHeight: true\n                height: forceZeroHeight ? 0 : Math.max(root.down ? 10 : 16, root.background.height - 18 * 2)\n                Component.onCompleted: {\n                    forceZeroHeight = false;\n                }\n\n                Behavior on height {\n                    animation: Looks.transition.resize.createObject(this)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WMouseAreaButton.qml",
    "content": "import QtQuick\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nMouseArea {\n    id: root\n\n    property real radius: Looks.radius.medium\n    hoverEnabled: true\n\n    property color colBackground: ColorUtils.transparentize(Looks.colors.bg2)\n    property color colBackgroundHover: Looks.colors.bg2Hover\n    property color colBackgroundActive: Looks.colors.bg2Active\n    property color colBorder: ColorUtils.transparentize(Looks.colors.bg2Border)\n    property color colBorderHover: Looks.colors.bg2Border\n    \n    property color color: {\n        if (containsMouse) {\n            return pressed ? colBackgroundActive : colBackgroundHover;\n        } else {\n            return colBackground;\n        }\n    }\n\n    property color borderColor: {\n        if (containsMouse) {\n            return colBorderHover;\n        } else {\n            return colBorder;\n        }\n    }\n\n    property Item background: Rectangle {\n        id: bgRect\n        parent: root\n        anchors.fill: parent\n        color: root.color\n        radius: root.radius\n\n        border.color: root.borderColor\n        border.width: 1\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WPane.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n    property Item contentItem\n    property real radius: Looks.radius.large\n    property alias color: contentRect.color\n    property alias border: borderRect\n    property alias borderColor: borderRect.border.color\n    property alias borderWidth: borderRect.border.width\n\n    implicitWidth: borderRect.implicitWidth\n    implicitHeight: borderRect.implicitHeight\n\n    WRectangularShadow {\n        target: borderRect\n    }\n\n    Rectangle {\n        id: borderRect\n        z: 1\n\n        color: \"transparent\"\n        radius: root.radius\n        border.color: Looks.colors.bg2Border\n        border.width: 1\n        implicitWidth: contentItem.implicitWidth + border.width * 2\n        implicitHeight: contentItem.implicitHeight + border.width * 2\n        anchors.fill: contentRect\n        anchors.margins: -border.width\n    }\n\n    Rectangle {\n        id: contentRect\n        anchors.centerIn: parent\n        z: 0\n        \n        color: Looks.colors.bgPanelFooterBackground\n        implicitWidth: contentItem.implicitWidth\n        implicitHeight: contentItem.implicitHeight\n        layer.enabled: true\n        layer.effect: OpacityMask {\n            maskSource: Rectangle {\n                id: contentAreaMask\n                width: contentRect.width\n                height: contentRect.height\n                radius: root.radius - borderRect.border.width\n            }\n        }\n        children: [root.contentItem]\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WPanelIconButton.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.bar\n\nWButton {\n    id: root\n\n    property alias iconName: iconContent.icon\n    property alias iconSize: iconContent.implicitSize\n    property alias monochrome: iconContent.monochrome\n    implicitWidth: 40\n    implicitHeight: 40\n\n    contentItem: Item {\n        FluentIcon {\n            id: iconContent\n            anchors.centerIn: parent\n            implicitSize: 18\n            icon: root.iconName\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WPanelPageColumn.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\n\nColumnLayout {\n    spacing: 0\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WPanelSeparator.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.waffle.looks\n\nRectangle {\n    Layout.fillHeight: false\n    Layout.fillWidth: true\n    color: Looks.colors.bgPanelSeparator\n    implicitHeight: 1\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WPopupToolTip.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nPopupToolTip {\n    id: root\n\n    required property Item realContentItem\n    realContentItem: WText {\n        text: root.text\n        anchors.centerIn: parent\n    }\n\n    property real visualMargin: 11\n    verticalPadding: 8\n    horizontalPadding: 10\n    verticalMargin: visualMargin\n    horizontalMargin: visualMargin\n\n    contentItem: WToolTipContent {\n        id: tooltipContent\n        realContentItem: root.realContentItem\n        horizontalPadding: root.horizontalPadding\n        verticalPadding: root.verticalPadding\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WProgressBar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Widgets\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nProgressBar {\n    id: root\n\n    Behavior on value {\n        SmoothedAnimation {\n            velocity: Looks.transition.velocity\n        }\n    }\n\n    implicitHeight: 4\n    background: null\n    \n    contentItem: Item {\n        id: background\n\n        Rectangle {\n            id: trackTrough\n            anchors {\n                left: parent.left\n                right: parent.right\n                verticalCenter: parent.verticalCenter\n            }\n            radius: root.implicitHeight / 2\n            color: Looks.colors.controlBg\n            implicitHeight: root.implicitHeight\n        }\n\n        Rectangle {\n            id: trackHighlight\n            anchors {\n                left: parent.left\n                verticalCenter: parent.verticalCenter\n            }\n            radius: root.implicitHeight / 2\n            color: Looks.colors.accent\n            implicitHeight: root.implicitHeight\n            width: background.width * root.value\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadow.qml",
    "content": "import QtQuick\nimport QtQuick.Effects\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nStyledRectangularShadow {\n    blur: 10\n    spread: 2\n    offset: Qt.vector2d(0.0, 4)\n    color: Looks.colors.shadow\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WRectangularShadowThis.qml",
    "content": "import QtQuick\nimport QtQuick.Effects\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nItem {\n    default property Item contentItem\n    property Item shadow: WRectangularShadow {\n        target: contentItem\n    }\n    implicitWidth: contentItem.implicitWidth\n    implicitHeight: contentItem.implicitHeight\n\n    children: [shadow, contentItem]\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WScrollBar.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nScrollBar {\n    id: root\n\n    policy: ScrollBar.AsNeeded\n    active: hovered || pressed\n    property color color: Looks.colors.controlBg\n\n    contentItem: Rectangle {\n        implicitWidth: root.active ? 4 : 2\n        implicitHeight: root.visualSize\n        radius: 9999\n        color: root.color\n        \n        opacity: root.policy === ScrollBar.AlwaysOn || (root.active && root.size < 1.0) ? 0.5 : 0\n        Behavior on opacity {\n            animation: Looks.transition.opacity.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WSlider.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell.Widgets\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nSlider {\n    id: root\n\n    property real trackWidth: 4\n    property string tooltipContent: `${Math.round(value * 100)}`\n    property bool scrollable: false\n    stepSize: 0.02\n    leftPadding: 0\n    rightPadding: 0\n\n    implicitHeight: handle.implicitHeight\n\n    Behavior on value { // This makes the adjusted value (like volume) shift smoothly\n        SmoothedAnimation {\n            velocity: Looks.transition.velocity\n        }\n    }\n\n    background: MouseArea {\n        id: background\n        anchors.fill: parent\n\n        onWheel: (event) => {\n            if (!root.scrollable) {\n                event.accepted = false;\n                return;\n            }\n            if (event.angleDelta.y > 0) {\n                root.value = Math.min(root.value + root.stepSize, 1)\n                root.moved()\n            } else {\n                root.value = Math.max(root.value - root.stepSize, 0)\n                root.moved()\n            }\n        }\n\n        Rectangle {\n            id: trackHighlight\n            anchors {\n                left: parent.left\n                verticalCenter: parent.verticalCenter\n            }\n            topLeftRadius: root.trackWidth / 2\n            bottomLeftRadius: root.trackWidth / 2\n            color: Looks.colors.accent\n            implicitHeight: root.trackWidth\n            width: background.width * root.visualPosition\n        }\n\n        Rectangle {\n            id: trackTrough\n            anchors {\n                right: parent.right\n                verticalCenter: parent.verticalCenter\n            }\n            topRightRadius: root.trackWidth / 2\n            bottomRightRadius: root.trackWidth / 2\n            color: Looks.colors.controlBg\n            implicitHeight: root.trackWidth\n            width: background.width * (1 - root.visualPosition)\n        }\n    }\n\n    handle: Circle {\n        id: handle\n        anchors.verticalCenter: parent.verticalCenter\n        x: (diameter / 2) + root.visualPosition * (root.width - diameter) - (diameter / 2)\n        diameter: 20\n        color: Looks.colors.controlFg\n\n        MouseArea {\n            id: handleMouseArea\n            anchors.fill: parent\n            hoverEnabled: true\n            acceptedButtons: Qt.NoButton\n        }\n\n        Circle {\n            anchors.centerIn: parent\n            diameter: root.pressed ? 10 : handleMouseArea.containsMouse ? 14 : 12\n            color: Looks.colors.accent\n\n            Behavior on diameter {\n                animation: Looks.transition.enter.createObject(this)\n            }\n        }\n\n        WToolTip {\n            id: tooltip\n            extraVisibleCondition: root.pressed\n            text: root.tooltipContent\n            font.pixelSize: Looks.font.pixelSize.larger\n            verticalPadding: 3\n            horizontalPadding: 8\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WStackView.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport qs.modules.waffle.looks\n\nStackView {\n    id: root\n    property real moveDistance: 30\n    property int pushDuration: 200\n    property int fadeDuration: 80\n    property list<real> bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n    property list<real> fadeBezierCurve: Looks.transition.easing.bezierCurve.easeInOut\n    clip: true\n\n    background: null\n\n    pushEnter: Transition {\n        XAnimator {\n            from: -root.moveDistance\n            to: 0\n            duration: root.pushDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.bezierCurve\n        }\n        NumberAnimation {\n            properties: \"opacity\"\n            from: 0\n            to: 1\n            duration: root.fadeDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.fadeBezierCurve\n        }\n    }\n    pushExit: Transition {\n        XAnimator {\n            from: 0\n            to: root.moveDistance\n            duration: root.pushDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.bezierCurve\n        }\n        NumberAnimation {\n            properties: \"opacity\"\n            from: 1\n            to: 0\n            duration: root.fadeDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.fadeBezierCurve\n        }\n    }\n    popEnter: Transition {\n        XAnimator {\n            from: root.moveDistance\n            to: 0\n            duration: root.pushDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.bezierCurve\n        }\n        NumberAnimation {\n            properties: \"opacity\"\n            from: 0\n            to: 1\n            duration: root.fadeDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.fadeBezierCurve\n        }\n    }\n    popExit: Transition {\n        XAnimator {\n            from: 0\n            to: -root.moveDistance\n            duration: root.pushDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.bezierCurve\n        }\n        NumberAnimation {\n            properties: \"opacity\"\n            from: 1\n            to: 0\n            duration: root.fadeDuration\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: root.fadeBezierCurve\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WSwitch.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport QtQuick.Controls\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nSwitch {\n    id: root\n\n    implicitWidth: 40\n    implicitHeight: 20\n    property real indicatorHeight: 12\n    property real indicatorPressedHeight: 14\n    property real indicatorPressedWidth: 17\n    property color checkedColor: Looks.colors.accent\n    property color uncheckedColor: Looks.colors.bg1\n    property color borderColor: Looks.colors.controlBgInactive\n\n    readonly property real indicatorPressedWidthDiff: indicatorPressedWidth - indicatorHeight\n    \n    background: Rectangle {\n        width: parent.width\n        height: parent.height\n        radius: height / 2\n        color: root.checked ? root.checkedColor : root.uncheckedColor\n        border.width: 1\n        border.color: root.checked ? root.checkedColor : root.borderColor\n\n        Behavior on color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n        Behavior on border.color {\n            animation: Appearance.animation.elementMoveFast.colorAnimation.createObject(this)\n        }\n    }\n\n    // Custom thumb styling\n    indicator: Rectangle {\n        implicitWidth: (root.pressed || root.down) ? root.indicatorPressedWidth : root.indicatorHeight\n        implicitHeight: (root.pressed || root.down) ? root.indicatorPressedHeight : root.indicatorHeight\n        radius: height / 2\n        color: root.checked ? Looks.colors.accentFg : root.borderColor\n        anchors.verticalCenter: parent.verticalCenter\n        anchors.left: parent.left\n        anchors.leftMargin: {\n            if (root.checked) {\n                return 24 - (root.pressed || root.down ? root.indicatorPressedWidthDiff : 0);\n            } else {\n                return (root.pressed || root.down) ? 3 : (Config.options.waffles.tweaks.switchHandlePositionFix ? 4 : 3);\n            }\n        }\n\n        Behavior on anchors.leftMargin {\n            animation: Looks.transition.enter.createObject(this)\n        }\n        Behavior on implicitWidth {\n            animation: Looks.transition.resize.createObject(this)\n        }\n        Behavior on implicitHeight {\n            animation: Looks.transition.resize.createObject(this)\n        }\n        Behavior on color {\n            animation: Looks.transition.color.createObject(this)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WText.qml",
    "content": "import QtQuick\n\nText {\n    id: root\n\n    renderType: Text.NativeRendering\n    verticalAlignment: Text.AlignVCenter\n    color: Looks.colors.fg\n\n    font {\n        hintingPreference: Font.PreferDefaultHinting\n        family: Looks.font.family.ui\n        pixelSize: Looks.font.pixelSize.normal\n        weight: Looks.font.weight.regular\n        variableAxes: Looks.font.variableAxes.ui\n    }\n\n    linkColor: Looks.colors.link\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WTextButton.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs.modules.waffle.looks\n\nWButton {\n    id: root\n    implicitHeight: 40\n    implicitWidth: contentItem.implicitWidth + 30\n    color: \"transparent\"\n\n    contentItem: Item {\n        id: contentItem\n        anchors.centerIn: parent\n        implicitWidth: buttonText.implicitWidth\n\n        WText {\n            id: buttonText\n            anchors.centerIn: parent\n            color: root.pressed ? Looks.colors.fg : Looks.colors.fg1\n            text: root.text\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WTextField.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport QtQuick.Controls.FluentWinUI3\nimport QtQuick.Controls\n\nTextField {\n    id: root\n    \n    clip: true\n    renderType: Text.NativeRendering\n    verticalAlignment: Text.AlignVCenter\n    color: Looks.colors.fg\n\n    palette {\n        active: Looks.colors.accent\n    }\n\n    font {\n        hintingPreference: Font.PreferDefaultHinting\n        family: Looks.font.family.ui\n        pixelSize: Looks.font.pixelSize.normal\n        weight: Looks.font.weight.regular\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        acceptedButtons: Qt.NoButton\n        hoverEnabled: true\n        cursorShape: Qt.IBeamCursor\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WTextInput.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\n\nTextInput {\n    id: root\n    renderType: Text.NativeRendering\n    verticalAlignment: Text.AlignVCenter\n    color: Looks.colors.fg\n\n    font {\n        hintingPreference: Font.PreferFullHinting\n        family: Looks.font.family.ui\n        pixelSize: Looks.font.pixelSize.large\n        weight: Looks.font.weight.regular\n    }\n\n    selectionColor: Looks.colors.selection\n    selectedTextColor: Looks.colors.selectionFg\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WTextWithFixedWidth.qml",
    "content": "import QtQuick\n\nItem {\n    id: root\n\n    property string longestText\n    property alias text: textItem.text\n    property alias font: textItem.font\n    property alias horizontalAlignment: textItem.horizontalAlignment\n    property alias verticalAlignment: textItem.verticalAlignment\n    property alias color: textItem.color\n\n    implicitWidth: longestTextMetrics.width\n    implicitHeight: longestTextMetrics.height\n\n    TextMetrics {\n        id: longestTextMetrics\n        text: root.longestText\n        font {\n            family: Looks.font.family.ui\n            pixelSize: Looks.font.pixelSize.large\n            weight: Looks.font.weight.regular\n        }\n    }\n\n    WText {\n        id: textItem\n        anchors.fill: parent\n        font.pixelSize: Looks.font.pixelSize.large\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolTip.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nStyledToolTip {\n    id: root\n\n    required property Item realContentItem\n    font {\n        family: Looks.font.family.ui\n        pixelSize: Looks.font.pixelSize.normal\n        weight: Looks.font.weight.regular\n    }\n    realContentItem: WText {\n        text: root.text\n        font: root.font\n        anchors.centerIn: parent\n    }\n\n    verticalPadding: 8\n    horizontalPadding: 10\n\n    delay: 400\n\n    contentItem: WToolTipContent {\n        id: tooltipContent\n        realContentItem: root.realContentItem\n        horizontalPadding: root.horizontalPadding\n        verticalPadding: root.verticalPadding\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolTipContent.qml",
    "content": "import QtQuick\nimport Quickshell\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n    anchors.centerIn: parent\n    required property Item realContentItem\n    property alias radius: realContent.radius\n    property real verticalPadding: 8\n    property real horizontalPadding: 10\n    implicitWidth: realContent.implicitWidth + 2 * 2\n    implicitHeight: realContent.implicitHeight + 2 * 2\n\n    WAmbientShadow {\n        target: realContent\n    }\n    \n    Rectangle {\n        id: realContent\n        z: 1\n        anchors.centerIn: parent\n        implicitWidth: root.realContentItem.implicitWidth + root.horizontalPadding * 2\n        implicitHeight: root.realContentItem.implicitHeight + root.verticalPadding * 2\n        color: Looks.colors.bg1Base\n        radius: Looks.radius.medium\n\n        children: [root.realContentItem]\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolbar.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nItem {\n    id: root\n\n    property real padding: 9\n    property alias colBackground: background.color\n    property alias spacing: toolbarLayout.spacing\n    property alias radius: background.radius\n    default property alias toolbarData: toolbarLayout.data\n    \n    implicitWidth: background.implicitWidth\n    implicitHeight: background.implicitHeight\n\n    Rectangle {\n        id: background\n        anchors.fill: parent\n        implicitHeight: 50\n        implicitWidth: toolbarLayout.implicitWidth + root.padding * 2\n        radius: Looks.radius.large\n        color: Looks.colors.bg0Base\n\n        border.width: 1\n        border.color: Looks.colors.bg1Border\n\n        RowLayout {\n            id: toolbarLayout\n            spacing: 4\n            anchors {\n                fill: parent\n                margins: root.padding\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolbarButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\n\nWButton {\n    implicitHeight: 32\n    radius: Looks.radius.medium\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\n\nWToolbarButton {\n    id: root\n    implicitWidth: height\n    contentItem: Item {\n        FluentIcon {\n            anchors.centerIn: parent\n            icon: root.icon.name\n            implicitSize: 18\n            color: root.fgColor\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolbarIconTabButton.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport qs.modules.common\n\nTabButton {\n    id: root\n\n    implicitWidth: 38\n    implicitHeight: 32\n    padding: 0\n\n    background: null\n    contentItem: Item {\n        FluentIcon {\n            anchors.centerIn: parent\n            icon: root.icon.name\n            color: root.icon.color\n            implicitSize: 18\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolbarSeparator.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport qs.modules.common\n\nRectangle {\n    Layout.leftMargin: 4\n    Layout.rightMargin: 4\n    implicitHeight: 24\n    implicitWidth: 1\n    color: Looks.colors.bg0Border\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WToolbarTabBar.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport qs.modules.common\nimport qs.modules.common.functions\n\nTabBar {\n    id: root\n    implicitHeight: 32\n\n    background: Rectangle {\n        radius: Looks.radius.medium\n        color: Looks.colors.bgPanelFooter\n        border.color: ColorUtils.transparentize(Looks.colors.bg0Border, 0.7)\n        border.width: 1\n\n        // Indicator\n        Rectangle {\n            anchors {\n                top: parent.top\n                bottom: parent.bottom\n                left: parent.left\n                leftMargin: root.currentIndex * (root.width / root.count)\n                Behavior on leftMargin {\n                    animation: Looks.transition.resize.createObject(this)\n                }\n            }\n            radius: Looks.radius.medium\n            color: Looks.colors.bg2Base\n            border.color: Looks.colors.bg0Border\n            border.width: 1\n            width: root.width / root.count\n\n            Rectangle {\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    bottom: parent.bottom\n                    bottomMargin: 1\n                }\n                implicitWidth: pressDetector.containsPress ? 16 : 12\n                implicitHeight: 3\n                radius: height / 2\n                color: Looks.colors.accent\n            }\n        }\n    }\n\n    MouseArea {\n        id: pressDetector\n        z: 9999\n        anchors.fill: parent\n        acceptedButtons: Qt.LeftButton\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/looks/WUserAvatar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nStyledImage {\n    id: avatar\n    Layout.alignment: Qt.AlignTop\n    sourceSize: Qt.size(32, 32)\n    source: Directories.userAvatarPathAccountsService\n    fallbacks: [Directories.userAvatarPathRicersAndWeirdSystems, Directories.userAvatarPathRicersAndWeirdSystems2]\n\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Circle {\n            diameter: avatar.height\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/CalendarWidget.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQml\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nBodyRectangle {\n    id: root\n\n    // State\n    property bool collapsed\n\n    // Locale\n    property var locale: Qt.locale(Config.options.calendar.locale)\n\n    implicitHeight: collapsed ? 0 : contentColumn.implicitHeight\n    implicitWidth: contentColumn.implicitWidth\n\n    Behavior on implicitHeight {\n        animation: Looks.transition.enter.createObject(this)\n    }\n\n    clip: true\n    ColumnLayout {\n        id: contentColumn\n        spacing: 12\n        CalendarHeader {\n            Layout.topMargin: 10\n            Layout.fillWidth: true\n        }\n        ColumnLayout {\n            Layout.fillWidth: true\n            Layout.leftMargin: 5\n            Layout.rightMargin: 5\n            spacing: 1\n            DayOfWeekRow {\n                Layout.fillWidth: true\n                locale: root.locale\n                spacing: calendarView.buttonSpacing\n                implicitHeight: calendarView.buttonSize\n                delegate: Item {\n                    id: dayOfWeekItem\n                    required property var model\n                    implicitHeight: calendarView.buttonSize\n                    implicitWidth: calendarView.buttonSize\n                    WText {\n                        anchors.centerIn: parent\n                        text: {\n                            var result = dayOfWeekItem.model.shortName;\n                            if (Config.options.waffles.calendar.force2CharDayOfWeek) result = result.substring(0,2);\n                            return result;\n                        }\n                        color: Looks.colors.fg\n                        font.pixelSize: Looks.font.pixelSize.large\n                    }\n                }\n            }\n            CalendarView {\n                id: calendarView\n                locale: root.locale\n                verticalPadding: 2\n                buttonSize: 41 // ???\n                buttonSpacing: 6\n                buttonVerticalSpacing: 1\n                Layout.fillWidth: true\n                delegate: DayButton {}\n            }\n        }\n    }\n\n    component DayButton: WButton {\n        id: dayButton\n        required property var model\n        checked: model.today\n        enabled: hovered || checked || model.month === calendarView.focusedMonth\n        implicitWidth: calendarView.buttonSize\n        implicitHeight: calendarView.buttonSize\n        radius: height / 2\n\n        required property int index\n\n        contentItem: Item {\n            WText {\n                anchors.centerIn: parent\n                text: dayButton.model.day\n                color: dayButton.fgColor\n                font.pixelSize: Looks.font.pixelSize.larger\n            }\n        }\n    }\n\n    component CalendarHeader: RowLayout {\n        Layout.leftMargin: 8\n        Layout.rightMargin: 8\n        spacing: 8\n\n        WBorderlessButton {\n            Layout.fillWidth: true\n            implicitHeight: 34\n            contentItem: Item {\n                WText {\n                    anchors.fill: parent\n                    horizontalAlignment: Text.AlignLeft\n                    text: Qt.locale().toString(calendarView.focusedDate, \"MMMM yyyy\")\n                    font.pixelSize: Looks.font.pixelSize.large\n                    font.weight: Looks.font.weight.strong\n                }\n            }\n        }\n        ScrollMonthButton {\n            scrollDown: false\n        }\n        ScrollMonthButton {\n            scrollDown: true\n        }\n    }\n\n    component ScrollMonthButton: WBorderlessButton {\n        id: scrollMonthButton\n        required property bool scrollDown\n        Layout.alignment: Qt.AlignVCenter\n\n        onClicked: {\n            calendarView.scrollMonthsAndSnap(scrollDown ? 1 : -1);\n        }\n        implicitWidth: 32\n        implicitHeight: 34\n\n        contentItem: FluentIcon {\n            filled: true\n            implicitSize: 12\n            icon: scrollMonthButton.scrollDown ? \"caret-down\" : \"caret-up\"\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/DateHeader.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nFooterRectangle {\n    id: root\n\n    implicitWidth: 0\n    property bool collapsed\n    color: ColorUtils.transparentize(Looks.colors.bgPanelBody, collapsed ? 0 : 1)\n    Behavior on color {\n        animation: Looks.transition.color.createObject(this)\n    }\n\n    RowLayout {\n        anchors {\n            fill: parent\n            leftMargin: 16\n            rightMargin: 16\n            topMargin: 12\n            bottomMargin: 12\n        }\n\n        WText {\n            Layout.fillWidth: true\n            font.pixelSize: Looks.font.pixelSize.large\n            text: DateTime.collapsedCalendarFormat\n        }\n\n        WBorderedButton {\n            implicitWidth: 24\n            implicitHeight: 24\n            padding: 0\n            onClicked: root.collapsed = !root.collapsed\n            contentItem: Item {\n                FluentIcon {\n                    anchors.centerIn: parent\n                    implicitSize: 12\n                    icon: \"chevron-down\"\n                    rotation: root.collapsed ? 180 : 0\n                    Behavior on rotation {\n                        animation: Looks.transition.rotate.createObject(this)\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/FocusFooter.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nFooterRectangle {\n    Layout.fillWidth: true\n    implicitWidth: 0\n    color: Looks.colors.bgPanelBody\n\n    RowLayout {\n        anchors {\n            fill: parent\n            leftMargin: 16\n            rightMargin: 16\n            topMargin: 12\n            bottomMargin: 12\n        }\n        spacing: 0\n\n        SmallBorderedIconButton {\n            visible: !TimerService.pomodoroRunning\n            icon.name: \"subtract\"\n            onClicked: Config.options.time.pomodoro.focus -= 300 // 5 mins\n        }\n\n        WTextWithFixedWidth {\n            visible: !TimerService.pomodoroRunning\n            implicitWidth: 81\n            horizontalAlignment: Text.AlignHCenter\n            color: Looks.colors.subfg\n            text: Translation.tr(\"%1 mins\").arg(`<font color=\"${Looks.colors.fg.toString()}\">${TimerService.focusTime / 60}</font>`)\n        }\n\n        SmallBorderedIconButton {\n            visible: !TimerService.pomodoroRunning\n            icon.name: \"add\"\n            onClicked: Config.options.time.pomodoro.focus += 300 // 5 mins\n        }\n\n        WText {\n            visible: TimerService.pomodoroRunning\n            font.pixelSize: Looks.font.pixelSize.large\n            text: Translation.tr(\"Focusing\")\n        }\n\n        Item {\n            Layout.fillWidth: true\n        }\n\n        SmallBorderedIconAndTextButton {\n            iconName: TimerService.pomodoroRunning ? \"stop\" : \"play\"\n            text: TimerService.pomodoroRunning ? Translation.tr(\"End session\") : Translation.tr(\"Focus\")\n\n            onClicked: {\n                if (TimerService.pomodoroRunning) {\n                    TimerService.togglePomodoro();\n                    TimerService.resetPomodoro();\n                } else {\n                    TimerService.togglePomodoro();\n                    Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"sidebarRight\", \"toggle\"]);\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/NotificationCenterContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Qt.labs.synchronizer\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nWBarAttachedPanelContent {\n    id: root\n\n    readonly property bool barAtBottom: Config.options.waffles.bar.bottom\n    revealFromSides: true\n    revealFromLeft: false\n\n    property bool collapsed: false\n\n    contentItem: ColumnLayout {\n        id: contentLayout\n        anchors {\n            horizontalCenter: parent.horizontalCenter\n            top: parent.top\n            bottom: parent.bottom\n        }\n        spacing: 12\n\n        Item {\n            id: notificationArea\n            Layout.fillHeight: true\n            implicitWidth: notificationPane.implicitWidth\n\n            WPane {\n                id: notificationPane\n                anchors {\n                    bottom: parent.bottom\n                    left: parent.left\n                    right: parent.right\n                }\n                contentItem: NotificationPaneContent {\n                    implicitWidth: calendarColumnLayout.implicitWidth\n                    implicitHeight: {\n                        if (Notifications.list.length > 0) {\n                            return ((contentLayout.height - calendarPane.height - contentLayout.spacing) - notificationPane.borderWidth * 2)\n                        }\n                        return 230;\n                    }\n                    \n                    Timer {\n                        id: enableTimer\n                        interval: Config.options.hacks.arbitraryRaceConditionDelay\n                        onTriggered: heightBehavior.enabled = true;\n                    }\n                    Behavior on implicitHeight {\n                        id: heightBehavior\n                        enabled: false\n                        Component.onCompleted: {\n                            enableTimer.restart();\n                        }\n                        animation: Looks.transition.enter.createObject(this)\n                    }\n                }\n            }\n        }\n\n        WPane {\n            id: calendarPane\n            contentItem: WPanelPageColumn {\n                id: calendarColumnLayout\n                DateHeader {\n                    Layout.fillWidth: true\n                    Synchronizer on collapsed {\n                        property alias source: root.collapsed\n                    }\n                }\n\n                WPanelSeparator {\n                    visible: !root.collapsed\n                }\n\n                CalendarWidget {\n                    Layout.fillWidth: true\n                    Synchronizer on collapsed {\n                        property alias source: root.collapsed\n                    }\n                }\n\n                WPanelSeparator {}\n\n                FocusFooter {\n                    Layout.fillWidth: true\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/NotificationHeaderButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nWBorderlessButton {\n    id: root\n    Layout.fillWidth: false\n    property real implicitSize: 16\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n    color: \"transparent\"\n    colForeground: root.hovered && !root.pressed ? Looks.colors.fg : Looks.colors.fg1\n\n    Behavior on colForeground {\n        animation: Looks.transition.color.createObject(this)\n    }\n\n    contentItem: Item {\n        FluentIcon {\n            anchors.centerIn: parent\n            implicitSize: root.implicitSize\n            icon: root.icon.name\n            color: root.colForeground\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/NotificationPaneContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nFooterRectangle {\n    id: root\n    anchors.fill: parent\n    implicitHeight: 230\n\n    ColumnLayout {\n        id: contentLayout\n        anchors.fill: parent\n        anchors.margins: 4\n\n        spacing: 12\n\n        RowLayout {\n            Layout.fillWidth: true\n            Layout.leftMargin: 12\n            Layout.rightMargin: 12\n            Layout.topMargin: 8\n\n            spacing: 8\n\n            WText {\n                Layout.fillWidth: true\n                horizontalAlignment: Text.AlignLeft\n                elide: Text.ElideRight\n                text: Translation.tr(\"Notifications\")\n                font.pixelSize: Looks.font.pixelSize.large\n            }\n\n            SmallBorderedIconButton {\n                icon.name: \"alert-snooze\"\n                checked: Notifications.silent\n                onClicked: {\n                    Notifications.silent = !Notifications.silent;\n                }\n            }\n\n            SmallBorderedIconAndTextButton {\n                visible: Notifications.list.length > 0\n                iconVisible: false\n                text: Translation.tr(\"Clear all\")\n                onClicked: {\n                    Notifications.discardAllNotifications();\n                }\n            }\n        }\n\n        WListView {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            clip: true\n\n            model: Notifications.appNameList\n            delegate: WNotificationGroup {\n                required property int index\n                required property var modelData\n                width: ListView.view.width\n                notificationGroup: Notifications.groupsByAppName[modelData]\n            }\n\n            EmptyPlaceholder {\n                visible: Notifications.list.length === 0\n                anchors.centerIn: parent\n            }\n        }\n    }\n\n    component EmptyPlaceholder: WText {\n        horizontalAlignment: Text.AlignHCenter\n        text: Translation.tr(\"No new notifications\")\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/SmallBorderedIconAndTextButton.qml",
    "content": "import QtQuick\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nAcrylicButton {\n    id: root\n\n    property bool iconVisible: true\n    property string iconName: \"\"\n    property bool iconFilled: true\n\n    colBackground: Looks.colors.bg2\n    colBackgroundHover: Looks.colors.bg2Hover\n    colBackgroundActive: Looks.colors.bg2Active\n    property color colBorder: Looks.colors.bg2Border\n    property color colBorderToggled: Looks.colors.accent\n    border.color: checked ? colBorderToggled : colBorder\n\n    leftPadding: 12\n    rightPadding: 12\n    implicitWidth: focusButtonContent.implicitWidth + leftPadding + rightPadding\n    implicitHeight: 24\n\n    contentItem: Row {\n        id: focusButtonContent\n        spacing: 4\n\n        FluentIcon {\n            visible: root.iconVisible\n            icon: root.iconName\n            filled: root.iconFilled\n            implicitSize: 14\n            anchors.verticalCenter: parent.verticalCenter\n        }\n        WText {\n            anchors.verticalCenter: parent.verticalCenter\n            text: root.text\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/SmallBorderedIconButton.qml",
    "content": "import QtQuick\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\n\nWBorderedButton {\n    id: root\n    implicitWidth: 24\n    implicitHeight: 24\n    contentItem: Item {\n        FluentIcon {\n            anchors.centerIn: parent\n            implicitSize: 12\n            icon: root.icon.name\n            color: root.fgColor\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/WNotificationAppIcon.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport org.kde.kirigami as Kirigami\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n\n    property string icon: \"\"\n    property real implicitSize: 16\n    implicitWidth: implicitSize\n    implicitHeight: implicitSize\n\n    Kirigami.Icon {\n        anchors.fill: parent\n        implicitWidth: root.implicitSize\n        implicitHeight: root.implicitSize\n\n        source: root.icon || fallback\n        fallback: `${Looks.iconsPath}/apps.svg`\n        roundToIconSize: false\n        isMask: !root.icon\n        color: Looks.colors.fg\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/WNotificationDismissAnim.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nSequentialAnimation {\n    id: root\n\n    required property var target\n\n    PropertyAction {\n        target: root.target\n        property: \"ListView.delayRemove\"\n        value: true\n    }\n    NumberAnimation {\n        target: root.target\n        property: \"x\"\n        to: root.target.width\n        duration: 250\n        easing.type: Easing.BezierSpline\n        easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n    }\n    PropertyAction {\n        target: root.target\n        property: \"ListView.delayRemove\"\n        value: false\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/WNotificationGroup.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\n// TODO: Swipe to dismiss\nMouseArea {\n    id: root\n\n    required property var notificationGroup\n    readonly property var notifications: notificationGroup?.notifications ?? []\n    property bool expanded: false\n\n    implicitWidth: contentLayout.implicitWidth\n    implicitHeight: contentLayout.implicitHeight\n\n    function dismissAll() {\n        root.notifications.forEach(notif => {\n            Qt.callLater(() => {\n                Notifications.discardNotification(notif.notificationId);\n            });\n        });\n        removeAnimation.start();\n    }\n\n    WNotificationDismissAnim {\n        id: removeAnimation\n        target: root\n    }\n\n    property real dragDismissThreshold: 100\n    drag {\n        axis: Drag.XAxis\n        target: contentLayout\n        minimumX: 0\n        onActiveChanged: {\n            if (drag.active)\n                return;\n            if (contentLayout.x > root.dragDismissThreshold) {\n                root.dismissAll();\n            } else {\n                contentLayout.x = 0;\n            }\n        }\n    }\n\n    ColumnLayout {\n        id: contentLayout\n        spacing: 4\n        width: root.width\n\n        Behavior on x {\n            animation: Looks.transition.enter.createObject(this)\n        }\n\n        GroupHeader {\n            id: notifHeader\n            Layout.fillWidth: true\n            Layout.margins: 11\n        }\n\n        WListView {\n            Layout.leftMargin: -Math.min(35, contentLayout.x)\n            Layout.rightMargin: -Layout.leftMargin\n            Layout.fillWidth: true\n            implicitWidth: notifHeader.implicitWidth\n            implicitHeight: contentHeight\n            interactive: false\n            spacing: 4\n            model: ScriptModel {\n                values: root.expanded ? root.notifications.slice().reverse() : root.notifications.slice(-1)\n                objectProp: \"notificationId\"\n            }\n            delegate: WSingleNotification {\n                id: singleNotif\n                required property int index\n                required property var modelData\n\n                width: ListView.view.width\n                notification: modelData\n\n                groupExpandControlMessage: {\n                    if (root.notifications.length <= 1)\n                        return \"\";\n                    if (!root.expanded)\n                        return Translation.tr(\"+%1 notifications\").arg(root.notifications.length - 1);\n                    if (index === root.notifications.length - 1)\n                        return Translation.tr(\"See fewer\");\n                    return \"\";\n                }\n                onGroupExpandToggle: {\n                    root.expanded = !root.expanded;\n                }\n            }\n        }\n    }\n\n    component GroupHeader: MouseArea {\n        id: headerMouseArea\n        hoverEnabled: true\n        acceptedButtons: Qt.NoButton\n\n        implicitWidth: appHeader.implicitWidth\n        implicitHeight: appHeader.implicitHeight\n\n        RowLayout {\n            id: appHeader\n            anchors.fill: parent\n            spacing: 7\n\n            WNotificationAppIcon {\n                Layout.alignment: Qt.AlignVCenter\n                icon: root.notificationGroup?.appIcon ?? \"\"\n            }\n\n            WText {\n                Layout.fillWidth: true\n                horizontalAlignment: Text.AlignLeft\n                elide: Text.ElideRight\n                text: root.notificationGroup?.appName ?? \"\"\n            }\n\n            // NotificationHeaderButton { // TODO: More notification functionality needed so we can have this button\n            //     visible: headerMouseArea.containsMouse\n            //     Layout.leftMargin: 25\n            //     Layout.rightMargin: 25\n            //     icon.name: \"more-horizontal\"\n            // }\n\n            NotificationHeaderButton {\n                visible: headerMouseArea.containsMouse\n                Layout.rightMargin: 3\n                icon.name: \"dismiss\"\n                onClicked: {\n                    root.dismissAll();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/WSingleNotification.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Services.Notifications\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nMouseArea {\n    id: root\n\n    required property var notification\n    property bool expanded: notification.actions.length > 0\n    property string groupExpandControlMessage: \"\"\n\n    readonly property bool isPopup: notification?.popup ?? false\n\n    signal groupExpandToggle\n    hoverEnabled: true\n\n    function dismiss() {\n        Qt.callLater(() => {\n            Notifications.discardNotification(root.notification?.notificationId);\n        });\n        removeAnimation.start();\n    }\n\n    WNotificationDismissAnim {\n        id: removeAnimation\n        target: root\n    }\n\n    implicitHeight: contentItem.implicitHeight\n    implicitWidth: contentItem.implicitWidth\n\n    Behavior on implicitHeight {\n        animation: Looks.transition.enter.createObject(this)\n    }\n\n    property real dragDismissThreshold: 100\n    drag {\n        axis: Drag.XAxis\n        target: contentItem\n        minimumX: 0\n        onActiveChanged: {\n            if (drag.active)\n                return;\n            if (contentItem.x > root.dragDismissThreshold) {\n                root.dismiss();\n            } else {\n                contentItem.x = 0;\n            }\n        }\n    }\n\n    Rectangle {\n        id: contentItem\n        width: parent.width\n        color: root.isPopup ? Looks.colors.bg0 : Looks.colors.bgPanelBody\n        radius: root.isPopup ? Looks.radius.large : Looks.radius.medium\n        property real padding: 12\n        implicitHeight: notificationContent.implicitHeight + padding * 2\n        implicitWidth: notificationContent.implicitWidth + padding * 2\n        border.width: 1\n        border.color: root.isPopup ? Looks.colors.bg2Border : Looks.colors.bgPanelSeparator\n\n        Behavior on x {\n            animation: Looks.transition.enter.createObject(this)\n        }\n\n        ColumnLayout {\n            id: notificationContent\n            anchors.fill: parent\n            anchors.margins: contentItem.padding\n            spacing: 19\n\n            // Header\n            SingleNotificationHeader {\n                Layout.fillWidth: true\n            }\n\n            // Content\n            Item {\n                id: actualContent\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                property real spacing: 16\n                implicitHeight: Math.max(contentColumn.implicitHeight, imageLoader.height)\n                implicitWidth: contentColumn.implicitWidth\n\n                Loader {\n                    id: imageLoader\n                    anchors {\n                        top: parent.top\n                        left: parent.left\n                    }\n                    active: root.notification.image != \"\"\n                    sourceComponent: StyledImage {\n                        readonly property int size: 48\n                        width: size\n                        height: size\n                        source: root.notification.image\n                        fillMode: Image.PreserveAspectFit\n                    }\n                }\n\n                ColumnLayout {\n                    id: contentColumn\n                    anchors {\n                        top: parent.top\n                        left: parent.left\n                        right: parent.right\n                    }\n                    spacing: 3\n\n                    SummaryText {\n                        id: summaryText\n                        Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0\n                    }\n                    BodyText {\n                        Layout.leftMargin: imageLoader.active ? imageLoader.width + actualContent.spacing : 0\n                        // onLineLaidOut: (line) => {\n                        //     if (!imageLoader.active) return;\n                        //     const dodgeDistance = imageLoader.width + actualContent.spacing;\n                        //     // print(line.y, dodgeDistance)\n                        //     if (summaryText.height + line.y > dodgeDistance) {\n                        //         line.x -= dodgeDistance;\n                        //         line.width += dodgeDistance;\n                        //     }\n                        // }\n                    }\n                }\n            }\n\n            // Actions\n            ActionsRow {\n                Layout.fillWidth: true\n            }\n\n            // \"+1 notifications\" button\n            GroupExpandButton {\n                Layout.bottomMargin: 2\n            }\n        }\n    }\n\n    component SingleNotificationHeader: RowLayout {\n        ExpandButton {\n            Layout.topMargin: -2\n        }\n\n        Item {\n            Layout.fillWidth: true\n        }\n\n        NotificationHeaderButton {\n            Layout.rightMargin: 4\n            opacity: (root.containsMouse || root.isPopup) ? 1 : 0\n            icon.name: \"dismiss\"\n            implicitSize: 14\n            onClicked: root.dismiss()\n        }\n    }\n\n    component ActionsRow: RowLayout {\n        visible: root.expanded && root.notification.actions.length > 0\n        uniformCellSizes: true\n        Repeater {\n            id: actionRepeater\n            model: root.notification.actions\n            delegate: WBorderedButton {\n                id: actionButton\n                Layout.fillHeight: true\n                required property var modelData\n                Layout.fillWidth: true\n                verticalPadding: 16\n                horizontalPadding: 12\n                text: modelData.text\n                implicitHeight: actionButtonText.implicitHeight + verticalPadding * 2\n                contentItem: WText {\n                    id: actionButtonText\n                    text: actionButton.text\n                    font.pixelSize: Looks.font.pixelSize.large\n                    horizontalAlignment: Text.AlignHCenter\n                    wrapMode: Text.Wrap\n                }\n            }\n        }\n    }\n\n    component SummaryText: WText {\n        Layout.fillWidth: true\n        elide: Text.ElideRight\n        text: root.notification?.summary\n        font.pixelSize: Looks.font.pixelSize.large\n    }\n\n    component BodyText: WText {\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        elide: Text.ElideRight\n        verticalAlignment: Text.AlignTop\n        wrapMode: Text.Wrap\n        maximumLineCount: root.expanded ? 100 : 1\n        text: {\n            if (root.expanded)\n                return `<style>img{max-width:${summaryText.width}px; align: right}</style>` + `${NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\\n/g, \"<br/>\")}`;\n            return NotificationUtils.processNotificationBody(root.notification.body, root.notification.appName || root.notification.summary).replace(/\\n/g, \"<br/>\");\n        }\n        color: Looks.colors.subfg\n        textFormat: root.expanded ? Text.RichText : Text.StyledText\n        onLinkActivated: link => {\n            Qt.openUrlExternally(link);\n            GlobalStates.sidebarRightOpen = false;\n        }\n    }\n\n    component ExpandButton: NotificationHeaderButton {\n        id: expandButton\n        implicitWidth: expandButtonContent.implicitWidth\n        onClicked: root.expanded = !root.expanded\n\n        contentItem: Item {\n            id: expandButtonContent\n            implicitWidth: expandButtonRow.implicitWidth\n            implicitHeight: expandButtonRow.implicitHeight\n            RowLayout {\n                id: expandButtonRow\n                anchors.centerIn: parent\n                spacing: 8\n                WText {\n                    color: expandButton.colForeground\n                    text: NotificationUtils.getFriendlyNotifTimeString(root.notification?.time)\n                }\n                FluentIcon {\n                    Layout.rightMargin: 12\n                    icon: \"chevron-down\"\n                    implicitSize: 18\n                    rotation: root.expanded ? -180 : 0\n                    color: expandButton.colForeground\n                    Behavior on rotation {\n                        animation: Looks.transition.rotate.createObject(this)\n                    }\n                }\n            }\n        }\n    }\n\n    component GroupExpandButton: AcrylicButton {\n        id: groupExpandButton\n        visible: root.groupExpandControlMessage !== \"\"\n        horizontalPadding: 10\n        implicitHeight: 24\n        implicitWidth: expandButtonText.implicitWidth + horizontalPadding * 2\n        onClicked: root.groupExpandToggle()\n        contentItem: Item {\n            WText {\n                id: expandButtonText\n                anchors.centerIn: parent\n                text: root.groupExpandControlMessage\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationCenter/WaffleNotificationCenter.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nScope {\n    id: root\n\n    Connections {\n        target: GlobalStates\n\n        function onSidebarRightOpenChanged() {\n            if (GlobalStates.sidebarRightOpen) panelLoader.active = true;\n        }\n    }\n\n    Loader {\n        id: panelLoader\n        active: GlobalStates.sidebarRightOpen\n        sourceComponent: PanelWindow {\n            id: panelWindow\n            exclusiveZone: 0\n            WlrLayershell.namespace: \"quickshell:wNotificationCenter\"\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n            color: \"transparent\"\n\n            anchors {\n                bottom: true\n                top: true\n                right: true\n            }\n\n            implicitWidth: content.implicitWidth\n            implicitHeight: content.implicitHeight\n\n            HyprlandFocusGrab {\n                id: focusGrab\n                active: true\n                windows: [panelWindow]\n                onCleared: content.close();\n            }\n\n            Connections {\n                target: GlobalStates\n                function onSidebarRightOpenChanged() {\n                    if (!GlobalStates.sidebarRightOpen) content.close();\n                }\n            }\n\n            NotificationCenterContent {\n                id: content\n                anchors.fill: parent\n\n                onClosed: {\n                    GlobalStates.sidebarRightOpen = false;\n                    panelLoader.active = false;\n                }\n            }\n        }\n    }\n\n    function toggleOpen() {\n        GlobalStates.sidebarRightOpen = !GlobalStates.sidebarRightOpen;\n    }\n\n    IpcHandler {\n        target: \"sidebarRight\"\n\n        function toggle() {\n            root.toggleOpen();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sidebarRightToggle\"\n        description: \"Toggles notification center on press\"\n\n        onPressed: root.toggleOpen();\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/notificationPopup/WaffleNotificationPopup.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.notificationCenter\n\nScope {\n    id: notificationPopup\n\n    PanelWindow {\n        id: root\n        visible: (Notifications.popupList.length > 0) && !GlobalStates.screenLocked\n        screen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name) ?? null\n\n        WlrLayershell.namespace: \"quickshell:notificationPopup\"\n        WlrLayershell.layer: WlrLayer.Overlay\n        exclusiveZone: 0\n\n        anchors {\n            top: true\n            right: true\n            bottom: true\n        }\n\n        mask: Region {\n            item: listview.contentItem\n        }\n\n        color: \"transparent\"\n        implicitWidth: listview.implicitWidth\n\n        WListView {\n            id: listview\n            anchors {\n                bottom: parent.bottom\n                right: parent.right\n                left: parent.left\n            }\n            leftMargin: 16\n            rightMargin: 16\n            topMargin: 16\n            bottomMargin: 16\n\n            height: Math.min(contentItem.height + topMargin + bottomMargin, parent.height)\n            width: parent.width - Appearance.sizes.elevationMargin * 2\n            \n            implicitWidth: 396\n            spacing:12\n\n            model: ScriptModel {\n                values: Notifications.popupList\n            }\n            delegate: WSingleNotification {\n                required property var modelData\n                notification: modelData\n                width: ListView.view.width - ListView.view.leftMargin - ListView.view.rightMargin\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/BrightnessOSD.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\nimport qs.services\nimport qs.modules.waffle.looks\n\nOSDValue {\n    id: root\n    property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)\n    property var brightnessMonitor: Brightness.getMonitorForScreen(focusedScreen)\n    iconName: \"weather-sunny\"\n    value: brightnessMonitor?.brightness ?? 0\n    showNumber: false\n\n    Connections {\n        target: Brightness\n        function onBrightnessChanged() {\n            root.timer.restart();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/OSDValue.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nWBarAttachedPanelContent {\n    id: root\n    required property string iconName\n    property real value\n    property bool showNumber: true\n\n    property Timer timer: Timer {\n        id: autoCloseTimer\n        running: true\n        interval: Config.options.osd.timeout\n        repeat: false\n        onTriggered: {\n            root.close();\n        }\n    }\n\n    contentItem: WPane {\n        anchors.centerIn: parent\n        borderColor: Looks.colors.ambientShadow\n\n        contentItem: Item {\n            // color: Looks.colors.bg1Base\n            // radius: Looks.radius.medium\n            implicitWidth: root.showNumber ? 192 : 170\n            implicitHeight: 46\n\n            RowLayout {\n                id: contentRow\n                anchors.fill: parent\n                anchors.margins: 12\n\n                spacing: 12\n\n                FluentIcon {\n                    Layout.alignment: Qt.AlignVCenter\n                    icon: root.iconName\n                    implicitSize: 18\n                }\n\n                WProgressBar {\n                    id: progressBar\n                    value: root.value\n                    Layout.fillWidth: true\n                    Layout.alignment: Qt.AlignVCenter\n                    Layout.rightMargin: root.showNumber ? 0 : 3\n                }\n\n                WTextWithFixedWidth {\n                    visible: root.showNumber\n                    text: Math.round(root.value * 100)\n                    // longestText: \"100\"\n                    implicitWidth: 16\n                    horizontalAlignment: Text.AlignHCenter\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/VolumeOSD.qml",
    "content": "import QtQuick\nimport qs.services\nimport qs.modules.waffle.looks\n\nOSDValue {\n    id: root\n    iconName: WIcons.volumeIcon\n    value: Audio.sink?.audio.volume ?? 0\n\n    Connections {\n        // Listen to volume changes\n        target: Audio.sink?.audio ?? null\n        function onVolumeChanged() {\n            if (Audio.ready)\n                root.timer.restart();\n        }\n        function onMutedChanged() {\n            if (Audio.ready)\n                root.timer.restart();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/onScreenDisplay/WaffleOSD.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nScope {\n    id: root\n\n    property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)\n    property string currentIndicator: \"volume\"\n    property var indicators: [\n        {\n            id: \"volume\",\n            sourceUrl: \"VolumeOSD.qml\",\n            globalStateValue: \"osdVolumeOpen\"\n        },\n        {\n            id: \"brightness\",\n            sourceUrl: \"BrightnessOSD.qml\",\n            globalStateValue: \"osdBrightnessOpen\"\n        },\n    ]\n\n    function triggerBrightnessOsd() {\n        root.currentIndicator = \"brightness\";\n        GlobalStates.osdBrightnessOpen = true;\n    }\n\n    function triggerVolumeOSD() {\n        root.currentIndicator = \"volume\";\n        GlobalStates.osdVolumeOpen = true;\n    }\n\n    // Listen to brightness changes\n    Connections {\n        target: Brightness\n        function onBrightnessChanged() {\n            root.triggerBrightnessOsd();\n        }\n    }\n\n    // Listen to volume changes\n    Connections {\n        target: Audio.sink?.audio ?? null\n        function onVolumeChanged() {\n            if (Audio.ready)\n                root.triggerVolumeOSD();\n        }\n        function onMutedChanged() {\n            if (Audio.ready)\n                root.triggerVolumeOSD();\n        }\n    }\n\n    // Open when global state changes\n    Connections {\n        target: GlobalStates\n\n        function onOsdBrightnessOpenChanged() {\n            if (GlobalStates.osdBrightnessOpen)\n                panelLoader.active = true;\n        }\n        function onOsdVolumeOpenChanged() {\n            if (GlobalStates.osdVolumeOpen)\n                panelLoader.active = true;\n        }\n    }\n\n    // The actual thing\n    Loader {\n        id: panelLoader\n        active: false\n        onActiveChanged: {\n            if (active) return;\n            root.indicators.forEach(i => {\n                GlobalStates[i.globalStateValue] = false;\n            });\n        }\n        sourceComponent: PanelWindow {\n            id: panelWindow\n\n            Connections {\n                target: root\n                function onFocusedScreenChanged() {\n                    osdRoot.screen = root.focusedScreen;\n                }\n            }\n\n            color: \"transparent\"\n            exclusiveZone: 0\n            WlrLayershell.namespace: \"quickshell:wOnScreenDisplay\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            anchors {\n                top: !Config.options.waffles.bar.bottom\n                bottom: Config.options.waffles.bar.bottom\n            }\n            mask: Region {\n                item: osdIndicatorLoader\n            }\n\n            implicitWidth: osdIndicatorLoader.implicitWidth\n            implicitHeight: osdIndicatorLoader.implicitHeight\n\n            Loader {\n                id: osdIndicatorLoader\n                anchors.fill: parent\n                source: root.indicators.find(i => i.id === root.currentIndicator)?.sourceUrl\n\n                Connections {\n                    target: osdIndicatorLoader.item\n                    function onClosed() {\n                        panelLoader.active = false;\n                        GlobalStates[root.indicators.find(i => i.id === root.currentIndicator)?.globalStateValue] = false;\n                    }\n                }\n\n                Behavior on source {\n                    id: switchBehavior\n\n                    SequentialAnimation {\n                        id: switchAnim\n                        // Animate close of current indicator\n                        ScriptAction {\n                            script: {\n                                osdIndicatorLoader.item.close();\n                            }\n                        }\n                        // Wait for close anim\n                        PauseAnimation {\n                            duration: osdIndicatorLoader.item.closeAnimDuration\n                        }\n                        PropertyAction {} // The source change happens here\n                    }\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"osd\"\n\n        function trigger() {\n            root.trigger();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"osdTrigger\"\n        description: \"Triggers OSD display\"\n\n        onPressed: root.trigger()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/polkit/WPolkitContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nRectangle {\n    id: root\n\n    color: \"#000000\"\n    readonly property bool usePasswordChars: !PolkitService.flow?.responseVisible ?? true\n\n    Keys.onPressed: event => { // Esc to close\n        if (event.key === Qt.Key_Escape) {\n            PolkitService.cancel();\n        }\n    }\n\n    StyledImage {\n        anchors.fill: parent\n        source: Config.options.background.wallpaperPath\n        fillMode: Image.PreserveAspectCrop\n\n        Rectangle {\n            anchors.fill: parent\n            color: ColorUtils.transparentize(\"#000000\", 0.31)\n\n            PolkitDialog {\n                id: dialog\n                DragHandler {\n                    target: null\n                    property real startX: dialog.x\n                    property real startY: dialog.y\n                    onActiveChanged: {\n                        if (!active) return;\n                        startX = dialog.x;\n                        startY = dialog.y;\n                    }\n                    xAxis.onActiveValueChanged: {\n                        dialog.x = Math.round(startX + xAxis.activeValue);\n                    }\n                    yAxis.onActiveValueChanged: {\n                        dialog.y = Math.round(startY + yAxis.activeValue);\n                    }\n                }\n                x: Math.round((parent.width - width) / 2)\n                y: Math.round((parent.height - height) / 2)\n            }\n        }\n    }\n\n    component PolkitDialog: WPane {\n        borderColor: Looks.colors.ambientShadow\n\n        contentItem: WPanelPageColumn {\n            PolkitDialogHeader {\n                Layout.fillWidth: true\n            }\n            BodyRectangle {\n                id: dialogBody\n                implicitHeight: bodyContent.implicitHeight + 48\n                implicitWidth: 434\n                color: Looks.colors.bg1Base\n\n                ColumnLayout {\n                    id: bodyContent\n                    anchors.fill: parent\n                    anchors.margins: 24\n                    spacing: 20\n\n                    RowLayout {\n                        Layout.fillWidth: true\n                        spacing: 15\n\n                        WAppIcon {\n                            iconName: PolkitService.flow?.iconName ?? \"window-shield\"\n                            fallback: PolkitService.flow?.iconName == \"\" ? `${Looks.iconsPath}/window-shield` : PolkitService.flow.iconName\n                            isMask: PolkitService.flow?.iconName === \"\"\n                            tryCustomIcon: false\n                        }\n                        WText {\n                            Layout.fillWidth: true\n                            horizontalAlignment: Text.AlignLeft\n                            font.pixelSize: Looks.font.pixelSize.larger\n                            font.weight: Looks.font.weight.strongest\n                            text: {\n                                const iconName = PolkitService.flow?.iconName ?? \"\";\n                                if (iconName === \"\")\n                                    return Translation.tr(\"Command-line-invoked Action\");\n                                const desktopEntry = DesktopEntries.applications.values.find(entry => {\n                                    return entry.icon == iconName;\n                                });\n                                return desktopEntry ? desktopEntry.name : Translation.tr(\"Unknown Application\");\n                            }\n                        }\n                    }\n\n                    WText {\n                        Layout.fillWidth: true\n                        wrapMode: Text.Wrap\n                        horizontalAlignment: Text.AlignLeft\n                        text: PolkitService.cleanMessage\n                    }\n\n                    WTextField {\n                        id: inputField\n                        Layout.fillWidth: true\n                        focus: true\n                        enabled: PolkitService.interactionAvailable\n                        placeholderText: PolkitService.cleanPrompt\n                        echoMode: root.usePasswordChars ? TextInput.Password : TextInput.Normal\n                        onAccepted: PolkitService.submit(inputField.text)\n\n                        Keys.onPressed: event => { // Esc to close\n                            if (event.key === Qt.Key_Escape) {\n                                PolkitService.cancel();\n                            }\n                        }\n\n                        Component.onCompleted: forceActiveFocus()\n                        Connections {\n                            target: PolkitService\n                            function onInteractionAvailableChanged() {\n                                if (!PolkitService.interactionAvailable)\n                                    return;\n                                inputField.text = \"\";\n                                inputField.forceActiveFocus();\n                            }\n                        }\n                    }\n                }\n            }\n            BodyRectangle {\n                implicitHeight: 80\n                color: Looks.colors.bgPanelFooterBackground\n                RowLayout {\n                    anchors.fill: parent\n                    anchors.margins: 24\n                    spacing: 8\n                    uniformCellSizes: true\n\n                    WButton {\n                        Layout.fillWidth: true\n                        implicitHeight: 32\n                        colBackground: Looks.colors.bg1\n                        horizontalAlignment: Text.AlignHCenter\n                        text: Translation.tr(\"Yes\")\n                        onClicked: PolkitService.submit(inputField.text)\n                    }\n                    WButton {\n                        Layout.fillWidth: true\n                        implicitHeight: 32\n                        horizontalAlignment: Text.AlignHCenter\n                        checked: true\n                        text: Translation.tr(\"No\")\n                        onClicked: PolkitService.cancel()\n                    }\n                }\n            }\n        }\n    }\n\n    component PolkitDialogHeader: BodyRectangle {\n        implicitHeight: headerContent.implicitHeight\n        color: Looks.colors.bg2Base\n\n        CloseButton {\n            anchors {\n                top: parent.top\n                right: parent.right\n            }\n            radius: 0\n            implicitWidth: 32\n            implicitHeight: 32\n\n            onClicked: {\n                PolkitService.cancel();\n            }\n        }\n\n        ColumnLayout {\n            id: headerContent\n            anchors.fill: parent\n            anchors.leftMargin: 24\n            anchors.rightMargin: 24\n            spacing: 18\n\n            WText {\n                Layout.topMargin: 20\n                Layout.fillWidth: true\n                horizontalAlignment: Text.AlignLeft\n                text: Translation.tr(\"Polkit\")\n            }\n            WText {\n                Layout.fillWidth: true\n                Layout.bottomMargin: 12\n                horizontalAlignment: Text.AlignLeft\n                wrapMode: Text.Wrap\n                text: Translation.tr(\"Do you want to allow this app to make changes to your device?\")\n                font.pixelSize: Looks.font.pixelSize.xlarger\n                font.weight: Looks.font.weight.strongest\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/polkit/WafflePolkit.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Wayland\n\nFullscreenPolkitWindow {\n    id: root\n    contentComponent: Component {\n        WPolkitContent {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/screenSnip/WRectangularSelection.qml",
    "content": "import QtQuick\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n\n    required property int regionX\n    required property int regionY\n    required property int regionWidth\n    required property int regionHeight\n\n    property bool dashed: true\n    property color borderColor: \"#ffffff\"\n    property color overlayColor: ColorUtils.transparentize(\"#000000\", 1)\n    Component.onCompleted: overlayColor = ColorUtils.transparentize(\"#000000\", 0.4)\n    Behavior on overlayColor {\n        ColorAnimation {\n            duration: 150\n            easing.type: Easing.InOutQuad\n        }\n    }\n\n    // Overlay to darken screen\n    // Base dark overlay around region\n    Rectangle {\n        id: darkenOverlay\n        z: 1\n        anchors {\n            left: parent.left\n            top: parent.top\n            leftMargin: root.regionX - darkenOverlay.border.width\n            topMargin: root.regionY - darkenOverlay.border.width\n        }\n        width: root.regionWidth + darkenOverlay.border.width * 2\n        height: root.regionHeight + darkenOverlay.border.width * 2\n        color: \"transparent\"\n        border.color: root.overlayColor\n        border.width: Math.max(root.width, root.height)\n    }\n\n    // Selection border\n    DashedBorder {\n        id: border\n        z: 2\n        visible: root.regionWidth > 0 && root.regionHeight > 0\n        anchors {\n            left: parent.left\n            top: parent.top\n            leftMargin: Math.round(root.regionX - borderWidth)\n            topMargin: Math.round(root.regionY - borderWidth)\n        }\n        width: Math.round(root.regionWidth + borderWidth * 2)\n        height: Math.round(root.regionHeight + borderWidth * 2)\n        color: root.borderColor\n        dashLength: 4\n        gapLength: root.dashed ? 3 : 0\n        borderWidth: 1\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/screenSnip/WRegionSelectionPanel.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt.labs.synchronizer\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.utils\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nPanelWindow {\n    id: root\n\n    enum MediaType {\n        Image,\n        Video\n    }\n    enum ImageAction {\n        Copy,\n        Menu,\n        CharRecognition,\n        Search\n    }\n    enum VideoAction {\n        Record,\n        RecordWithSound\n    }\n    enum SelectionMode {\n        Rect,\n        Window\n    }\n\n    function close() {\n        root.closed();\n    }\n\n    property var mediaType: WRegionSelectionPanel.MediaType.Image\n    property var imageAction: WRegionSelectionPanel.ImageAction.Copy\n    property var selectionMode: WRegionSelectionPanel.SelectionMode.Rect\n\n    visible: false\n    color: \"transparent\"\n    WlrLayershell.namespace: \"quickshell:regionSelector\"\n    WlrLayershell.layer: WlrLayer.Overlay\n    WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n    exclusionMode: ExclusionMode.Ignore\n    anchors {\n        left: true\n        right: true\n        top: true\n        bottom: true\n    }\n\n    // Hyprland stuff\n    readonly property HyprlandMonitor hyprlandMonitor: Hyprland.monitorFor(screen)\n    readonly property real monitorScale: hyprlandMonitor.scale\n    readonly property var windows: [...HyprlandData.windowList].sort((a, b) => {\n        // Sort floating=true windows before others\n        if (a.floating === b.floating)\n            return 0;\n        return a.floating ? -1 : 1;\n    })\n\n    property string screenshotDir: Directories.screenshotTemp\n    property string screenshotPath: `${root.screenshotDir}/image-${screen.name}`\n    TempScreenshotProcess {\n        id: screenshotProc\n        running: true\n        screen: root.screen\n        screenshotDir: root.screenshotDir\n        screenshotPath: root.screenshotPath\n        onExited: (exitCode, exitStatus) => {\n            root.preparationDone = true;\n        }\n    }\n    property bool preparationDone: false\n    onPreparationDoneChanged: {\n        if (!preparationDone)\n            return;\n        root.visible = true;\n    }\n\n    function getScreenshotAction() {\n        switch (root.mediaType) {\n        case WRegionSelectionPanel.MediaType.Image:\n            switch (root.imageAction) {\n            case WRegionSelectionPanel.ImageAction.Copy:\n                return ScreenshotAction.Action.Copy;\n            case WRegionSelectionPanel.ImageAction.Menu:\n                return ScreenshotAction.Action.Edit;\n            case WRegionSelectionPanel.ImageAction.CharRecognition:\n                return ScreenshotAction.Action.CharRecognition;\n            case WRegionSelectionPanel.ImageAction.Search:\n                return ScreenshotAction.Action.Search;\n            default:\n                return ScreenshotAction.Action.Copy;\n            }\n            break;\n        case WRegionSelectionPanel.MediaType.Video:\n            switch (root.videoAction) {\n            case WRegionSelectionPanel.VideoAction.Record:\n                return ScreenshotAction.Action.Record;\n            case WRegionSelectionPanel.VideoAction.RecordWithSound:\n                return ScreenshotAction.Action.RecordWithSound;\n            }\n        }\n    }\n\n    Process {\n        id: snipProc\n    }\n\n    ScreencopyView {\n        id: screencopyView\n        anchors.fill: parent\n        live: false\n        captureSource: root.screen\n\n        focus: root.visible\n        Keys.onPressed: event => { // Esc to close\n            if (event.key === Qt.Key_Escape) {\n                root.close();\n            } else if (event.key === Qt.Key_E && event.modifiers & Qt.ControlModifier) {\n                if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) {\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Copy;\n                } else {\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Menu;\n                }\n            }\n        }\n\n        DragManager {\n            id: dragArea\n            anchors.fill: parent\n            hoverEnabled: true\n            acceptedButtons: Qt.LeftButton | Qt.RightButton\n            cursorShape: Qt.CrossCursor\n\n            property bool isWindowSelection: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window\n            property var hoveredWindow: root.windows.find(w => {\n                const inCurrentWorkspace = w.workspace.id === HyprlandData.activeWorkspace.id;\n                const withinXRange = w.at[0] <= dragArea.mouseX && dragArea.mouseX <= w.at[0] + w.size[0];\n                const withinYRange = w.at[1] <= dragArea.mouseY && dragArea.mouseY <= w.at[1] + w.size[1];\n                return inCurrentWorkspace && withinXRange && withinYRange;\n            })\n            property int winPadding: 1\n            property int selectionX: isWindowSelection ? ((hoveredWindow?.at[0] ?? 0) - winPadding) : regionTopLeftX\n            property int selectionY: isWindowSelection ? ((hoveredWindow?.at[1] ?? 0) - winPadding) : regionTopLeftY\n            property int selectionWidth: isWindowSelection ? ((hoveredWindow?.size[0] ?? 0) + winPadding * 2) : regionWidth\n            property int selectionHeight: isWindowSelection ? ((hoveredWindow?.size[1] ?? 0) + winPadding * 2) : regionHeight\n\n            onDragReleased: (diffX, diffY) => {\n                if (selectionWidth === 0 || selectionHeight === 0) {\n                    return;\n                }\n                const screenshotDir = Config.options.screenSnip.savePath !== \"\" ? Config.options.screenSnip.savePath : \"\";\n                const screenshotAction = root.getScreenshotAction();\n                const command = ScreenshotAction.getCommand(dragArea.selectionX * root.monitorScale //\n                , dragArea.selectionY * root.monitorScale //\n                , dragArea.selectionWidth * root.monitorScale//\n                , dragArea.selectionHeight * root.monitorScale //\n                , root.screenshotPath //\n                , screenshotAction //\n                , screenshotDir); // yo wtf is this formatting qmlls do be funnie\n                snipProc.command = command;\n\n                // Image post-processing\n                snipProc.startDetached();\n                root.close();\n            }\n\n            WRectangularSelection {\n                id: rectangularSelection\n                anchors.fill: parent\n                regionX: dragArea.selectionX\n                regionY: dragArea.selectionY\n                regionWidth: dragArea.selectionWidth\n                regionHeight: dragArea.selectionHeight\n                dashed: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect\n            }\n\n            RegionSelectionOptionsToolbar {\n                anchors {\n                    horizontalCenter: parent.horizontalCenter\n                    top: parent.top\n                    topMargin: 12\n                }\n            }\n        }\n    }\n\n    component RegionSelectionOptionsToolbar: WToolbar {\n        // Image/video\n        WToolbarTabBar {\n            currentIndex: switch (root.mediaType) {\n            case WRegionSelectionPanel.MediaType.Image:\n                return 0;\n            case WRegionSelectionPanel.MediaType.Video:\n                return 1;\n            default:\n                return 0;\n            }\n            WToolbarIconTabButton {\n                icon.name: \"camera\"\n                icon.color: Looks.colors.fg\n            }\n            WToolbarIconTabButton {\n                icon.name: \"video\"\n                icon.color: Looks.colors.fg\n            }\n            onCurrentIndexChanged: {\n                switch (currentIndex) {\n                case 0:\n                    root.mediaType = WRegionSelectionPanel.MediaType.Image;\n                    break;\n                case 1:\n                    root.mediaType = WRegionSelectionPanel.MediaType.Video;\n                    break;\n                }\n            }\n\n            WToolTip {\n                text: Translation.tr(\"Snip\")\n            }\n        }\n\n        // Selection type\n        WToolbarButton {\n            id: selectionTypeBtn\n            implicitWidth: selectionTypeBtnRow.implicitWidth + 11 * 2\n            leftPadding: 11\n            rightPadding: 11\n            onClicked: {\n                selectionTypeMenu.visible = !selectionTypeMenu.visible;\n            }\n            contentItem: Row {\n                id: selectionTypeBtnRow\n                spacing: 4\n                FluentIcon {\n                    anchors.verticalCenter: parent.verticalCenter\n                    icon: switch (root.selectionMode) {\n                    case WRegionSelectionPanel.SelectionMode.Rect:\n                        return \"crop\";\n                    case WRegionSelectionPanel.SelectionMode.Window:\n                        return \"calendar-add\";\n                    default:\n                        return \"crop\";\n                    }\n                    implicitSize: 18\n                }\n                FluentIcon {\n                    anchors {\n                        top: parent.top\n                        topMargin: (parent.height - height) / 2 + (selectionTypeBtn.down ? 2 : 0)\n                        Behavior on topMargin {\n                            animation: Looks.transition.enter.createObject(this)\n                        }\n                    }\n                    icon: \"chevron-down\"\n                    implicitSize: 12\n                }\n            }\n\n            WMenu {\n                id: selectionTypeMenu\n                onClosed: screencopyView.focus = true\n                x: -margins\n                y: -margins - (selectionTypeBtn.parent.height - selectionTypeBtn.height) - 16\n                topMargin: -6\n                height: implicitHeight + sourceEdgeMargin\n\n                color: Looks.colors.bg1Base\n\n                Action {\n                    icon.name: \"crop\"\n                    text: Translation.tr(\"Rectangle\")\n                    checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Rect\n                    onTriggered: {\n                        root.selectionMode = WRegionSelectionPanel.SelectionMode.Rect;\n                    }\n                }\n                Action {\n                    icon.name: \"calendar-add\"\n                    text: Translation.tr(\"Window\")\n                    checked: root.selectionMode === WRegionSelectionPanel.SelectionMode.Window\n                    onTriggered: {\n                        root.selectionMode = WRegionSelectionPanel.SelectionMode.Window;\n                    }\n                }\n            }\n\n            WToolTip {\n                text: Translation.tr(\"Snipping area\")\n            }\n        }\n\n        // Markup\n        WToolbarIconButton {\n            icon.name: \"image-edit\"\n            enabled: root.mediaType === WRegionSelectionPanel.MediaType.Image\n            checked: root.imageAction === WRegionSelectionPanel.ImageAction.Menu\n            onClicked: {\n                if (root.imageAction === WRegionSelectionPanel.ImageAction.Menu) {\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Copy;\n                } else {\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Menu;\n                }\n            }\n            WToolTip {\n                text: Translation.tr(\"Quick markup (Ctrl+E)\")\n            }\n        }\n\n        WToolbarSeparator {}\n\n        // Tools\n        WToolbarIconButton {\n            icon.name: \"search-visual\"\n            checked: root.imageAction === WRegionSelectionPanel.ImageAction.Search\n            onClicked: {\n                if (root.imageAction === WRegionSelectionPanel.ImageAction.Search && root.mediaType === WRegionSelectionPanel.MediaType.Image) {\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Copy;\n                } else {\n                    root.mediaType = WRegionSelectionPanel.MediaType.Image;\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Search;\n                }\n            }\n            WToolTip {\n                text: Translation.tr(\"Image search\")\n            }\n        }\n        WToolbarIconButton {\n            icon.name: \"eyedropper\"\n            onClicked: {\n                Quickshell.execDetached([\"bash\", \"-c\", \"sleep 0.2; hyprpicker -a\"]);\n                root.closed();\n            }\n            WToolTip {\n                text: Translation.tr(\"Color picker\")\n            }\n        }\n        WToolbarIconButton {\n            icon.name: \"scan-text\"\n            checked: root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition\n            onClicked: {\n                if (root.imageAction === WRegionSelectionPanel.ImageAction.CharRecognition && root.mediaType === WRegionSelectionPanel.MediaType.Image) {\n                    root.imageAction = WRegionSelectionPanel.ImageAction.Copy;\n                } else {\n                    root.mediaType = WRegionSelectionPanel.MediaType.Image;\n                    root.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition;\n                }\n            }\n            WToolTip {\n                text: Translation.tr(\"Text extractor\")\n            }\n        }\n\n        WToolbarSeparator {}\n\n        WToolbarIconButton {\n            icon.name: \"dismiss\"\n            onClicked: root.close()\n            WToolTip {\n                text: Translation.tr(\"Close (Esc)\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/screenSnip/WScreenSnip.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.services\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Widgets\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n\n    function dismiss() {\n        GlobalStates.regionSelectorOpen = false;\n    }\n\n    Loader {\n        id: regionSelectorLoader\n        active: GlobalStates.regionSelectorOpen\n\n        sourceComponent: WRegionSelectionPanel {\n            onClosed: root.dismiss()\n        }\n    }\n\n    function screenshot() {\n        GlobalStates.regionSelectorOpen = true;\n    }\n\n    function ocr() {\n        GlobalStates.regionSelectorOpen = true;\n        regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image;\n        regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.CharRecognition;\n    }\n\n    function record() {\n        GlobalStates.regionSelectorOpen = true;\n        regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video;\n        regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.Record;\n    }\n\n    function recordWithSound() {\n        GlobalStates.regionSelectorOpen = true;\n        regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Video;\n        regionSelectorLoader.item.videoAction = WRegionSelectionPanel.VideoAction.RecordWithSound;\n    }\n\n    function search() {\n        GlobalStates.regionSelectorOpen = true;\n        regionSelectorLoader.item.mediaType = WRegionSelectionPanel.MediaType.Image;\n        regionSelectorLoader.item.imageAction = WRegionSelectionPanel.ImageAction.Search;\n    }\n\n    IpcHandler {\n        target: \"region\"\n\n        function screenshot() {\n            root.screenshot();\n        }\n        function ocr() {\n            root.ocr();\n        }\n        function record() {\n            root.record();\n        }\n        function recordWithSound() {\n            root.recordWithSound();\n        }\n        function search() {\n            root.search();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"regionScreenshot\"\n        description: \"Takes a screenshot of the selected region\"\n        onPressed: root.screenshot()\n    }\n    GlobalShortcut {\n        name: \"regionSearch\"\n        description: \"Searches the selected region\"\n        onPressed: root.search()\n    }\n    GlobalShortcut {\n        name: \"regionOcr\"\n        description: \"Recognizes text in the selected region\"\n        onPressed: root.ocr()\n    }\n    GlobalShortcut {\n        name: \"regionRecord\"\n        description: \"Records the selected region\"\n        onPressed: root.record()\n    }\n    GlobalShortcut {\n        name: \"regionRecordWithSound\"\n        description: \"Records the selected region with sound\"\n        onPressed: root.recordWithSound()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/sessionScreen/PowerButton.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\n\nWSessionScreenTextButton {\n    id: root\n    implicitWidth: 40\n    implicitHeight: 40\n    focusRingRadius: Looks.radius.large\n    colBackground: ColorUtils.transparentize(Looks.darkColors.bg2)\n    colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover)\n    colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active)\n    property color color: {\n        if (root.down) {\n            return root.colBackgroundActive;\n        } else if (root.hovered) {\n            return root.colBackgroundHover;\n        } else {\n            return root.colBackground;\n        }\n    }\n    background: Rectangle {\n        id: background\n        radius: Looks.radius.medium\n        color: root.color\n    }\n    contentItem: Item {\n        FluentIcon {\n            anchors.centerIn: parent\n            implicitSize: 20\n            icon: \"power\"\n            color: root.fgColor\n        }\n    }\n\n    onClicked: {\n        powerMenu.visible = !powerMenu.visible;\n    }\n\n    WMenu {\n        id: powerMenu\n        x: -powerMenu.implicitWidth / 2 + root.implicitWidth / 2\n        y: -powerMenu.implicitHeight\n\n        color: Looks.darkColors.bg1Base\n        Component.onCompleted: {\n            powerMenu.backgroundPane.borderColor = Looks.applyContentTransparency(Looks.darkColors.bg2Border);\n        }\n        delegate: WMenuItem {\n            id: menuItemDelegate\n            colBackground: ColorUtils.transparentize(Looks.darkColors.bg1Base)\n            colBackgroundHover: Looks.applyContentTransparency(Looks.darkColors.bg2Hover)\n            colBackgroundActive: Looks.applyContentTransparency(Looks.darkColors.bg2Active)\n            colForeground: Looks.darkColors.fg\n        }\n\n        Action {\n            icon.name: \"power\"\n            text: Translation.tr(\"Shut down\")\n            onTriggered: Session.poweroff()\n        }\n        Action {\n            icon.name: \"arrow-counterclockwise\"\n            text: Translation.tr(\"Restart\")\n            onTriggered: Session.reboot()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/sessionScreen/SessionScreenContent.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\n\nItem {\n    id: root\n\n    Component.onCompleted: {\n        lockButton.forceActiveFocus();\n    }\n\n    ColumnLayout {\n        anchors.centerIn: parent\n        spacing: 4\n\n        WSessionScreenTextButton {\n            id: lockButton\n            focus: true\n            text: Translation.tr(\"Lock\")\n            onClicked: {\n                GlobalStates.sessionOpen = false;\n                Session.lock();\n            }\n            KeyNavigation.up: powerButton\n            KeyNavigation.down: signOutButton\n        }\n        WSessionScreenTextButton {\n            id: signOutButton\n            focus: true\n            text: Translation.tr(\"Sign out\")\n            onClicked: {\n                GlobalStates.sessionOpen = false;\n                Session.logout();\n            }\n            KeyNavigation.up: lockButton\n            KeyNavigation.down: changePasswordButton\n        }\n\n        WSessionScreenTextButton {\n            id: changePasswordButton\n            focus: true\n            text: Translation.tr(\"Change password\")\n            onClicked: {\n                GlobalStates.sessionOpen = false;\n                Session.changePassword();\n            }\n            KeyNavigation.up: signOutButton\n            KeyNavigation.down: taskManagerButton\n        }\n\n        WSessionScreenTextButton {\n            id: taskManagerButton\n            focus: true\n            text: Translation.tr(\"Task Manager\")\n            onClicked: {\n                GlobalStates.sessionOpen = false;\n                Session.launchTaskManager();\n            }\n            KeyNavigation.up: signOutButton\n            KeyNavigation.down: cancelButton\n        }\n\n        CancelButton {\n            id: cancelButton\n            Layout.fillWidth: true\n            Layout.leftMargin: 5\n            Layout.rightMargin: 5\n            Layout.topMargin: 38\n            onClicked: GlobalStates.sessionOpen = false\n            KeyNavigation.up: taskManagerButton\n            KeyNavigation.down: powerButton\n        }\n    }\n\n    RowLayout {\n        anchors {\n            bottom: parent.bottom\n            right: parent.right\n            bottomMargin: 21\n            rightMargin: 31\n        }\n        PowerButton {\n            id: powerButton\n            KeyNavigation.up: cancelButton\n            KeyNavigation.down: lockButton\n        }\n    }\n\n    component CancelButton: WBorderlessButton {\n        id: root\n        implicitHeight: 32\n        colBackground: Looks.darkColors.bg1Base\n        colBackgroundHover: Qt.lighter(Looks.darkColors.bg1Base, 1.2)\n        colBackgroundActive: Qt.lighter(Looks.darkColors.bg1Base, 1.1)\n        colForeground: Looks.darkColors.fg\n\n        property bool keyboardDown: false\n\n        Keys.onPressed: event => {\n            if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n                keyboardDown = true;\n                event.accepted = true;\n            }\n        }\n        Keys.onReleased: event => {\n            if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n                keyboardDown = false;\n                root.clicked();\n                event.accepted = true;\n            }\n        }\n\n        contentItem: WText {\n            text: Translation.tr(\"Cancel\")\n            horizontalAlignment: Text.AlignHCenter\n            font.pixelSize: Looks.font.pixelSize.large\n            color: root.colForeground\n        }\n\n        Rectangle {\n            visible: cancelButton.focus\n            anchors {\n                fill: parent\n                margins: -3\n            }\n            radius: cancelButton.background.radius + 4\n            color: \"transparent\"\n            border.width: 2\n            border.color: \"#ffffff\"\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/sessionScreen/WSessionScreenTextButton.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport qs\nimport qs.modules.waffle.looks\n\nWTextButton {\n    id: root\n\n    implicitWidth: 135\n    implicitHeight: 40\n    horizontalPadding: 5\n\n    property bool keyboardDown: false\n    property alias focusRingRadius: focusRing.radius\n    fgColor: (root.pressed || root.keyboardDown) ? Looks.darkColors.fg1 : Looks.darkColors.fg\n\n    Keys.onPressed: event => {\n        if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            keyboardDown = true;\n            event.accepted = true;\n        }\n    }\n    Keys.onReleased: event => {\n        if (event.key === Qt.Key_Return || event.key === Qt.Key_Enter) {\n            keyboardDown = false;\n            root.clicked();\n            event.accepted = true;\n        }\n    }\n\n    contentItem: Item {\n        id: contentItem\n        implicitWidth: buttonText.implicitWidth\n\n        WText {\n            id: buttonText\n            anchors.fill: parent\n            color: root.fgColor\n            text: root.text\n            font.pixelSize: Looks.font.pixelSize.large\n        }\n    }\n\n    Rectangle {\n        id: focusRing\n        visible: root.focus\n        anchors {\n            fill: parent\n            margins: -4\n        }\n        color: \"transparent\"\n        border.width: 2\n        border.color: \"#ffffff\"\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/sessionScreen/WaffleSessionScreen.qml",
    "content": "import qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: root\n    property var focusedScreen: Quickshell.screens.find(s => s.name === Hyprland.focusedMonitor?.name)\n\n    Loader {\n        id: sessionLoader\n        active: GlobalStates.sessionOpen\n        onActiveChanged: {\n            if (sessionLoader.active) SessionWarnings.refresh();\n        }\n\n        Connections {\n            target: GlobalStates\n            function onScreenLockedChanged() {\n                if (GlobalStates.screenLocked) {\n                    GlobalStates.sessionOpen = false;\n                }\n            }\n        }\n\n        sourceComponent: PanelWindow { // Session menu\n            id: sessionRoot\n            visible: sessionLoader.active\n            property string subtitle\n            \n            function hide() {\n                GlobalStates.sessionOpen = false;\n            }\n\n            exclusionMode: ExclusionMode.Ignore\n            WlrLayershell.namespace: \"quickshell:session\"\n            WlrLayershell.layer: WlrLayer.Overlay\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.Exclusive\n            // This is a big surface so we needa carefully choose the transparency,\n            // or we'll get a large scary rgb blob\n            color: \"#000000\"\n\n            anchors {\n                top: true\n                left: true\n                right: true\n                bottom: true\n            }\n\n            Item {\n                anchors.fill: parent\n                Keys.onPressed: (event) => {\n                    if (event.key === Qt.Key_Escape) {\n                        sessionRoot.hide();\n                    }\n                }\n\n                SessionScreenContent {\n                    anchors.fill: parent\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"session\"\n\n        function toggle(): void {\n            GlobalStates.sessionOpen = !GlobalStates.sessionOpen;\n        }\n\n        function close(): void {\n            GlobalStates.sessionOpen = false\n        }\n\n        function open(): void {\n            GlobalStates.sessionOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sessionToggle\"\n        description: \"Toggles session screen on press\"\n\n        onPressed: {\n            GlobalStates.sessionOpen = !GlobalStates.sessionOpen;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sessionOpen\"\n        description: \"Opens session screen on press\"\n\n        onPressed: {\n            GlobalStates.sessionOpen = true\n        }\n    }\n\n    GlobalShortcut {\n        name: \"sessionClose\"\n        description: \"Closes session screen on press\"\n\n        onPressed: {\n            GlobalStates.sessionOpen = false\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/SearchBar.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nFooterRectangle {\n    id: root\n\n    property real horizontalPadding: 32\n    property real verticalPadding: 16\n    property bool searching: text.length > 0\n    property alias searchInput: searchInput\n    property alias text: searchInput.text\n    implicitHeight: outline.implicitHeight + verticalPadding * 2\n\n    signal accepted()\n\n    Component.onCompleted: forceFocus()\n    function forceFocus() {\n        searchInput.forceActiveFocus();\n    }\n\n    focus: true\n    color: searching ? Looks.colors.bgPanelBody : Looks.colors.bgPanelFooter\n\n    Behavior on horizontalPadding {\n        enabled: Config.options.waffles.tweaks.smootherSearchBar\n        animation: Looks.transition.move.createObject(this)\n    }\n    Behavior on verticalPadding {\n        enabled: Config.options.waffles.tweaks.smootherSearchBar\n        animation: Looks.transition.move.createObject(this)\n    }\n\n    Rectangle {\n        id: outline\n        anchors {\n            left: parent.left\n            right: parent.right\n            leftMargin: root.horizontalPadding\n            rightMargin: root.horizontalPadding\n            verticalCenter: parent.verticalCenter\n        }\n        implicitHeight: 32\n        color: \"transparent\"\n        radius: height / 2\n        border.width: 1\n        border.color: Looks.colors.bg2Border\n    }\n\n    Rectangle {\n        id: searchInputBg\n        anchors.fill: outline\n        anchors.margins: 1\n        radius: height / 2\n        color: Looks.colors.inputBg\n\n        RowLayout {\n            anchors.fill: parent\n            spacing: 11\n\n            WAppIcon {\n                Layout.leftMargin: 14\n                iconName: \"system-search-checked\"\n                separateLightDark: true\n                implicitSize: 18\n            }\n\n            WTextInput {\n                id: searchInput\n                focus: true\n                Layout.fillWidth: true\n\n                WText {\n                    anchors {\n                        left: parent.left\n                        verticalCenter: parent.verticalCenter\n                    }\n                    color: Looks.colors.accentUnfocused\n                    text: Translation.tr(\"Search for apps\") // should also have \"\", settings, and documents\" but we don't have those\n                    visible: searchInput.text.length === 0\n                    font.pixelSize: Looks.font.pixelSize.large\n                }\n\n                onAccepted: {\n                    root.accepted();\n                }\n            }\n        }\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        hoverEnabled: true\n        cursorShape: Qt.IBeamCursor\n        acceptedButtons: Qt.NoButton\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport Qt.labs.synchronizer\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.startMenu.startPage\nimport qs.modules.waffle.startMenu.searchPage\n\nWBarAttachedPanelContent {\n    id: root\n\n    property bool searching: false\n    property string searchText: LauncherSearch.query\n\n    StartMenuContext {\n        id: context\n    }\n\n    Keys.onPressed: event => {\n        // Prevent Esc and Backspace from registering\n        if (event.key === Qt.Key_Escape)\n            return;\n\n        // Handle Backspace: focus and delete character if not focused\n        if (event.key === Qt.Key_Backspace) {\n            searchBar.forceFocus();\n            if (event.modifiers & Qt.ControlModifier) {\n                // Delete word before cursor\n                let text = searchBar.text;\n                let pos = searchBar.searchInput.cursorPosition;\n                if (pos > 0) {\n                    // Find the start of the previous word\n                    let left = text.slice(0, pos);\n                    let match = left.match(/(\\s*\\S+)\\s*$/);\n                    let deleteLen = match ? match[0].length : 1;\n                    searchBar.text = text.slice(0, pos - deleteLen) + text.slice(pos);\n                    searchBar.searchInput.cursorPosition = pos - deleteLen;\n                }\n            } else {\n                // Delete character before cursor if any\n                if (searchBar.searchInput.cursorPosition > 0) {\n                    searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition - 1) + searchBar.text.slice(searchBar.searchInput.cursorPosition);\n                    searchBar.searchInput.cursorPosition -= 1;\n                }\n            }\n            // Always move cursor to end after programmatic edit\n            searchBar.searchInput.cursorPosition = searchBar.text.length;\n            event.accepted = true;\n            // If already focused, let TextField handle it\n            return;\n        }\n\n        // Only handle visible printable characters (ignore control chars, arrows, etc.)\n        if (event.text && event.text.length === 1 && event.key !== Qt.Key_Enter && event.key !== Qt.Key_Return && event.key !== Qt.Key_Delete && event.text.charCodeAt(0) >= 0x20) // ignore control chars like Backspace, Tab, etc.\n        {\n            if (!searchBar.searchInput.activeFocus) {\n                searchBar.forceFocus();\n                // Insert the character at the cursor position\n                searchBar.text = searchBar.text.slice(0, searchBar.searchInput.cursorPosition) + event.text + searchBar.text.slice(searchBar.searchInput.cursorPosition);\n                searchBar.searchInput.cursorPosition += 1;\n                event.accepted = true;\n                context.setCurrentIndex(0);\n            }\n        }\n\n        // Arrow keys for item navigation\n        if (event.key === Qt.Key_Down) {\n            let maxIndex = Math.max(0, LauncherSearch.results.length - 1);\n            context.setCurrentIndex(Math.min(context.currentIndex + 1, maxIndex));\n            event.accepted = true;\n        } else if (event.key === Qt.Key_Up) {\n            context.setCurrentIndex(Math.max(context.currentIndex - 1, 0));\n            event.accepted = true;\n        }\n    }\n\n    contentItem: WPane {\n        contentItem: WPanelPageColumn {\n            SearchBar {\n                id: searchBar\n                Layout.fillWidth: true\n                implicitWidth: 832 // TODO: Make sizes naturally inferred\n                horizontalPadding: 32\n                // verticalPadding: root.searching ? 32 : 16 // TODO: make this not nuke the panel\n                Synchronizer on searching {\n                    property alias target: root.searching\n                }\n                focus: true\n                text: root.searchText\n                onTextChanged: {\n                    LauncherSearch.query = text;\n                }\n                onAccepted: {\n                    context.accepted();\n                }\n            }\n            Item {\n                implicitHeight: root.searching ? 800 : 800 // TODO: Make sizes naturally inferred\n                Layout.fillWidth: true\n                Loader {\n                    id: pageContentLoader\n                    anchors.fill: parent\n                    sourceComponent: root.searching ? searchPageComp : startPageComp\n                }\n            }\n        }\n    }\n\n    Component {\n        id: searchPageComp\n        SearchPageContent {\n            context: context\n        }\n    }\n\n    Component {\n        id: startPageComp\n        StartPageContent {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/StartMenuContext.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs\nimport qs.modules.common\nimport qs.services\n\nScope {\n    id: root\n\n    signal accepted\n\n    property int currentIndex: 0\n    function setCurrentIndex(index) {\n        if (index == currentIndex)\n            return;\n        currentIndex = index;\n    }\n\n    function selectCategory(category) {\n        for (let i = 0; i < root.categories.length; i++) {\n            const thisCategoryName = root.categories[i].name;\n            if (thisCategoryName.startsWith(category) || category.startsWith(thisCategoryName)) {\n                LauncherSearch.ensurePrefix(root.categories[i].prefix);\n                return;\n            }\n        }\n    }\n    property list<var> categories: [\n        {\n            name: Translation.tr(\"All\"),\n            prefix: \"\"\n        },\n        {\n            name: Translation.tr(\"Apps\"),\n            prefix: Config.options.search.prefix.app\n        },\n        {\n            name: Translation.tr(\"Actions\"),\n            prefix: Config.options.search.prefix.action\n        },\n        {\n            name: Translation.tr(\"Clipboard\"),\n            prefix: Config.options.search.prefix.clipboard\n        },\n        {\n            name: Translation.tr(\"Emojis\"),\n            prefix: Config.options.search.prefix.emojis\n        },\n        {\n            name: Translation.tr(\"Math\"),\n            prefix: Config.options.search.prefix.math\n        },\n        {\n            name: Translation.tr(\"Commands\"),\n            prefix: Config.options.search.prefix.shellCommand\n        },\n        {\n            name: Translation.tr(\"Web\"),\n            prefix: Config.options.search.prefix.webSearch\n        },\n    ]\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/WaffleStartMenu.qml",
    "content": "import QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\n\nScope {\n    id: root\n\n    Connections {\n        target: GlobalStates\n\n        function onSearchOpenChanged() {\n            if (GlobalStates.searchOpen) {\n                LauncherSearch.query = \"\";\n                panelLoader.active = true;\n            }\n        }\n    }\n\n    Loader {\n        id: panelLoader\n        active: GlobalStates.searchOpen\n        sourceComponent: PanelWindow {\n            id: panelWindow\n            exclusiveZone: 0\n            WlrLayershell.namespace: \"quickshell:wStartMenu\"\n            WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n            color: \"transparent\"\n\n            anchors {\n                bottom: Config.options.waffles.bar.bottom\n                top: !Config.options.waffles.bar.bottom\n                left: Config.options.waffles.bar.leftAlignApps\n            }\n\n            implicitWidth: content.implicitWidth\n            implicitHeight: content.implicitHeight\n\n            HyprlandFocusGrab {\n                id: focusGrab\n                active: true\n                windows: [panelWindow]\n                onCleared: content.close()\n            }\n\n            Connections {\n                target: GlobalStates\n                function onSearchOpenChanged() {\n                    if (!GlobalStates.searchOpen)\n                        content.close();\n                }\n            }\n\n            StartMenuContent {\n                id: content\n                anchors.fill: parent\n                focus: true\n\n                onClosed: {\n                    GlobalStates.searchOpen = false;\n                    panelLoader.active = false;\n                    LauncherSearch.query = \"\";\n                }\n            }\n        }\n    }\n\n    function toggleClipboard() {\n        if (LauncherSearch.query.startsWith(Config.options.search.prefix.clipboard) || !GlobalStates.searchOpen) {\n            GlobalStates.searchOpen = !GlobalStates.searchOpen;\n        }\n        LauncherSearch.ensurePrefix(Config.options.search.prefix.clipboard);\n    }\n    function toggleEmojis() {\n        if (LauncherSearch.query.startsWith(Config.options.search.prefix.emojis) || !GlobalStates.searchOpen) {\n            GlobalStates.searchOpen = !GlobalStates.searchOpen;\n        }\n        LauncherSearch.ensurePrefix(Config.options.search.prefix.emojis);\n    }\n\n    IpcHandler {\n        target: \"search\"\n\n        function toggle() {\n            GlobalStates.searchOpen = !GlobalStates.searchOpen;\n        }\n        function close() {\n            GlobalStates.searchOpen = false;\n        }\n        function open() {\n            GlobalStates.searchOpen = true;\n        }\n        function toggleReleaseInterrupt() {\n            GlobalStates.superReleaseMightTrigger = false;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"searchToggle\"\n        description: \"Toggles search on press\"\n\n        onPressed: {\n            GlobalStates.searchOpen = !GlobalStates.searchOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"searchToggleRelease\"\n        description: \"Toggles search on release\"\n\n        onPressed: {\n            GlobalStates.superReleaseMightTrigger = true;\n        }\n\n        onReleased: {\n            if (!GlobalStates.superReleaseMightTrigger) {\n                GlobalStates.superReleaseMightTrigger = true;\n                return;\n            }\n            GlobalStates.searchOpen = !GlobalStates.searchOpen;\n        }\n    }\n    GlobalShortcut {\n        name: \"searchToggleReleaseInterrupt\"\n        description: \"Interrupts possibility of search being toggled on release. \" + \"This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key. \" + \"To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.\"\n\n        onPressed: {\n            GlobalStates.superReleaseMightTrigger = false;\n        }\n    }\n\n    GlobalShortcut {\n        name: \"overviewClipboardToggle\"\n        description: \"Toggle clipboard query on overview widget\"\n\n        onPressed: {\n            root.toggleClipboard();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"overviewEmojiToggle\"\n        description: \"Toggle emoji query on overview widget\"\n\n        onPressed: {\n            root.toggleEmojis();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchEntryIcon.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nItem {\n    id: root\n    required property LauncherSearchResult entry\n    property int iconSize: 24\n    implicitWidth: Math.max(iconSize, textIconLoader.implicitWidth)\n    implicitHeight: iconSize\n    Loader {\n        anchors.centerIn: parent\n        active: root.entry.iconType === LauncherSearchResult.IconType.System && root.entry.iconName !== \"\"\n        sourceComponent: WAppIcon {\n            implicitSize: root.iconSize\n            iconName: root.entry.iconName\n            tryCustomIcon: false\n            animated: false\n        }\n    }\n    Loader {\n        id: textIconLoader\n        anchors.centerIn: parent\n        active: root.entry.iconType === LauncherSearchResult.IconType.Text\n        sourceComponent: WText {\n            text: root.entry.iconName\n            font.pixelSize: root.iconSize\n            horizontalAlignment: Text.AlignHCenter\n            verticalAlignment: Text.AlignVCenter\n        }\n    }\n    Loader {\n        anchors.centerIn: parent\n        active: root.entry.iconType === LauncherSearchResult.IconType.Material || root.entry.iconType === LauncherSearchResult.IconType.None || root.entry.iconName === \"\"\n        sourceComponent: FluentIcon {\n            icon: root.entry.iconName ? WIcons.fluentFromMaterial(root.entry.iconName) : WIcons.guessIconForName(root.entry.name)\n            implicitSize: root.iconSize\n            animated: false\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchPageContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\n\nBodyRectangle {\n    id: root\n\n    property alias context: searchResults.context\n    property string searchText: LauncherSearch.query\n    property alias currentIndex: searchResults.currentIndex\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            topMargin: 2\n            leftMargin: 24\n            rightMargin: 24\n        }\n        spacing: 12\n\n        TagStrip {\n            context: root.context\n            Layout.fillWidth: true\n            Layout.fillHeight: false\n        }\n\n        SearchResults {\n            id: searchResults\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResultButton.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nWChoiceButton {\n    id: root\n\n    required property LauncherSearchResult entry\n    property bool firstEntry: false\n\n    signal requestFocus()\n\n    checked: focus\n    animateChoiceHighlight: false\n    implicitWidth: contentLayout.implicitWidth + leftPadding + rightPadding\n    implicitHeight: contentLayout.implicitHeight + topPadding + bottomPadding\n\n    onClicked: {\n        execute();\n    }\n\n    function execute() {\n        GlobalStates.searchOpen = false;\n        root.entry.execute();\n    }\n\n    horizontalPadding: 0\n    verticalPadding: 0\n\n    contentItem: RowLayout {\n        id: contentLayout\n        spacing: 0\n\n        WButton {\n            id: launchButton\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            horizontalPadding: 10\n            verticalPadding: 11\n            implicitHeight: Math.max(root.firstEntry ? 62 : 36, entryContentRow.implicitHeight + 8 * 2)\n            implicitWidth: entryContentRow.implicitWidth + leftPadding + rightPadding\n            topRightRadius: 0\n            bottomRightRadius: 0\n            onClicked: root.click()\n            contentItem: Item {\n                RowLayout {\n                    id: entryContentRow\n                    anchors {\n                        left: parent.left\n                        right: parent.right\n                        verticalCenter: parent.verticalCenter\n                    }\n                    spacing: 8\n\n                    SearchEntryIcon {\n                        entry: root.entry\n                        iconSize: 24\n                    }\n                    EntryNameColumn {\n                        Layout.fillWidth: true\n                        Layout.alignment: Qt.AlignVCenter\n                    }\n                }\n            }\n        }\n        Rectangle {\n            id: separator\n            opacity: (root.hovered && !root.checked) ? 1 : 0\n            Layout.fillHeight: true\n            implicitWidth: 1\n            color: ColorUtils.transparentize(Looks.colors.fg, 0.75)\n        }\n        WButton {\n            visible: !root.checked\n            Layout.fillHeight: true\n            implicitWidth: 47\n            topLeftRadius: 0\n            bottomLeftRadius: 0\n            onClicked: root.requestFocus()\n            contentItem: Item {\n                FluentIcon {\n                    anchors.centerIn: parent\n                    icon: \"chevron-right\"\n                    implicitSize: 14\n                }\n            }\n        }\n    }\n\n    component EntryNameColumn: ColumnLayout {\n        spacing: 4\n\n        WText {\n            Layout.fillWidth: true\n            wrapMode: Text.Wrap\n            text: root.entry.name\n            font.pixelSize: Looks.font.pixelSize.large\n            maximumLineCount: 2\n            elide: Text.ElideRight\n        }\n\n        WText {\n            Layout.fillWidth: true\n            visible: root.firstEntry\n            text: root.entry.type\n            color: Looks.colors.accentUnfocused\n            elide: Text.ElideRight\n        }\n    }\n\n    MouseArea {\n        anchors.fill: parent\n        // hoverEnabled: true\n        acceptedButtons: Qt.NoButton\n        cursorShape: Qt.PointingHandCursor\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/SearchResults.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.waffle.looks\nimport qs.modules.common.functions\nimport qs.modules.common.models\nimport qs.modules.waffle.startMenu\nimport Quickshell\nimport QtQuick.Layouts\nimport QtQuick.Controls\nimport QtQuick\n\nRowLayout {\n    id: root\n\n    property int maxResultsPerCategory: 4\n    property int resultLimit: 20\n    property StartMenuContext context\n    property int currentIndex: context.currentIndex\n    onCurrentIndexChanged: {\n        forceCurrentIndex(currentIndex);\n    }\n    function focusFirstItem() {\n        forceCurrentIndex(0);\n    }\n    function forceCurrentIndex(index) {\n        context.currentIndex = index;\n        // Somehow this hack is needed\n        if (index === 0) {\n            resultList.incrementCurrentIndex();\n            resultList.decrementCurrentIndex();\n        } else {\n            resultList.decrementCurrentIndex();\n            resultList.incrementCurrentIndex();\n        }\n    }\n\n    Connections {\n        target: context\n        function onAccepted() {\n            resultList.currentItem?.execute();\n        }\n    }\n\n    ResultList {\n        id: resultList\n        Layout.fillHeight: true\n        Layout.fillWidth: true\n    }\n    ResultPreview {\n        Layout.preferredWidth: 386\n        Layout.leftMargin: 1\n        Layout.rightMargin: 1\n        entry: resultList.model[resultList.currentIndex] ?? searchResultComp.createObject()\n    }\n\n    component ResultList: WListView {\n        id: resultListView\n        section {\n            criteria: ViewSection.FullString\n            property: \"category\" // This is \"type\" with tweaks to make it match more closely\n            labelPositioning: ViewSection.InlineLabels\n            delegate: Item {\n                id: sectionButton\n                required property string section\n                implicitHeight: sectionChoiceButton.implicitHeight + resultListView.spacing\n                width: ListView.view?.width\n                WChoiceButton {\n                    id: sectionChoiceButton\n                    anchors {\n                        left: parent.left\n                        right: parent.right\n                        top: parent.top\n                    }\n                    implicitHeight: 38\n                    contentItem: WText {\n                        text: sectionButton.section\n                        font.pixelSize: Looks.font.pixelSize.large\n                        font.weight: Looks.font.weight.strong\n                    }\n                    onClicked: {\n                        root.context.selectCategory(sectionButton.section);\n                    }\n                }\n            }\n        }\n        clip: true\n        spacing: 4\n        currentIndex: root.currentIndex\n\n        // We can't use a ScriptModel here because it would mess up sections\n        model: {\n            const allResults = LauncherSearch.results;\n            // Find categories\n            var categories = new Set();\n            for (let i = 0; i < allResults.length; i++) {\n                categories.add(allResults[i].type);\n            }\n\n            // Collect max 4 per category\n            var categorizedResults = [];\n            let categoriesArray = Array.from(categories);\n            let totalCount = 0;\n            for (let c = 0; c < categoriesArray.length; c++) {\n                let category = categoriesArray[c];\n                let count = 0;\n                for (let i = 0; i < allResults.length; i++) {\n                    if (allResults[i].type === category) {\n                        if (totalCount >= root.resultLimit) {\n                            break;\n                        }\n                        const entry = allResults[i];\n                        const tweakedEntry = searchResultComp.createObject(null, Object.assign({}, entry));\n                        tweakedEntry.category = categorizedResults.length === 0 ? Translation.tr(\"Best match\") : entry.type;\n\n                        categorizedResults.push(tweakedEntry); // Section header\n                        count++;\n                        totalCount++;\n                        if (count >= root.maxResultsPerCategory) {\n                            break;\n                        }\n                    }\n                }\n                if (totalCount >= root.resultLimit) {\n                    break;\n                }\n            }\n            \n            // print(JSON.stringify(categorizedResults, null, 2));\n            return categorizedResults;\n        }\n        onModelChanged: {\n            root.focusFirstItem();\n        }\n        delegate: SearchResultButton {\n            required property int index\n            required property var modelData\n            entry: modelData\n            firstEntry: index === 0\n            width: ListView.view?.width\n            checked: resultListView.currentIndex === index\n            onRequestFocus: {\n                root.forceCurrentIndex(index);\n            }\n        }\n    }\n\n    component ResultPreview: Rectangle {\n        id: resultPreview\n\n        property LauncherSearchResult entry // LauncherSearchResult\n\n        Layout.fillHeight: true\n        color: Looks.colors.bg1\n        radius: Looks.radius.large\n\n        ColumnLayout {\n            anchors.fill: parent\n            anchors.margins: 22\n            spacing: 13\n\n            ColumnLayout {\n                id: mainInfoColumn\n                Layout.alignment: Qt.AlignHCenter\n                SearchEntryIcon {\n                    Layout.alignment: Qt.AlignHCenter\n                    Layout.topMargin: 10\n                    Layout.bottomMargin: 12\n                    entry: resultPreview.entry\n                    iconSize: 64\n                }\n                WText {\n                    Layout.fillWidth: true\n                    horizontalAlignment: Text.AlignHCenter\n                    elide: Text.ElideRight\n                    wrapMode: Text.Wrap\n                    maximumLineCount: 2\n                    text: resultPreview.entry?.name || \"\"\n                    font.pixelSize: Looks.font.pixelSize.xlarger\n                }\n                WText {\n                    Layout.alignment: Qt.AlignHCenter\n                    text: resultPreview.entry?.type || \"\"\n                    color: Looks.colors.accentUnfocused\n                    font.pixelSize: Looks.font.pixelSize.normal\n                }\n            }\n            Rectangle {\n                id: resultSeparator\n                implicitHeight: 2\n                Layout.topMargin: 16\n                Layout.fillWidth: true\n                color: Looks.colors.bg2Hover\n            }\n            WListView {\n                id: actionsColumn\n                Layout.fillHeight: true\n                Layout.fillWidth: true\n                clip: true\n                spacing: 2\n                model: {\n                    const isAppEntry = resultPreview.entry.type === Translation.tr(\"App\");\n                    const appId = isAppEntry ? resultPreview.entry.id : \"\";\n                    const pinned = isAppEntry ? (Config.options.dock.pinnedApps.includes(appId)) : false;\n                    const startPinned = isAppEntry ? (Config.options.launcher.pinnedApps.includes(appId)) : false;\n                    var result = [\n                        searchResultComp.createObject(null, {\n                            name: resultPreview.entry.verb,\n                            iconName: isAppEntry ? \"open_in_new\" : \"keyboard_return\",\n                            iconType: LauncherSearchResult.IconType.Material,\n                            execute: () => {\n                                resultPreview.entry.execute();\n                            }\n                        }),\n                        ...(isAppEntry ? [\n                            searchResultComp.createObject(null, {\n                                name: startPinned ? Translation.tr(\"Unpin from Start\") : Translation.tr(\"Pin to Start\"),\n                                iconName: startPinned ? \"keep_off\" : \"keep\",\n                                iconType: LauncherSearchResult.IconType.Material,\n                                execute: () => {\n                                    LauncherApps.togglePin(appId);\n                                }\n                            })\n                        ] : []),\n                        ...(isAppEntry ? [\n                            searchResultComp.createObject(null, {\n                                name: pinned ? Translation.tr(\"Unpin from taskbar\") : Translation.tr(\"Pin to taskbar\"),\n                                iconName: pinned ? \"keep_off\" : \"keep\",\n                                iconType: LauncherSearchResult.IconType.Material,\n                                execute: () => {\n                                    TaskbarApps.togglePin(appId);\n                                }\n                            })\n                        ] : []),\n                    ];\n                    result = result.concat(resultPreview.entry.actions);\n                    return result;\n                }\n                delegate: WButton {\n                    id: actionButton\n                    required property var modelData\n                    width: ListView.view?.width\n                    icon.name: modelData.iconName\n                    text: modelData.name\n                    onClicked: modelData.execute();\n\n                    contentItem: RowLayout {\n                        spacing: 11\n                        SearchEntryIcon {\n                            entry: actionButton.modelData\n                            iconSize: 16\n                        }\n                        WText {\n                            Layout.fillWidth: true\n                            horizontalAlignment: Text.AlignLeft\n                            text: actionButton.text\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Component {\n        id: searchResultComp\n        LauncherSearchResult {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/searchPage/TagStrip.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.waffle.looks\nimport qs.modules.waffle.startMenu\n\nRowLayout {\n    id: root\n    property StartMenuContext context\n\n    WPanelIconButton {\n        implicitWidth: 36\n        implicitHeight: 36\n        iconSize: 24\n        iconName: \"arrow-left\"\n        onClicked: LauncherSearch.query = \"\"\n    }\n    ListView {\n        id: tagListView\n        Layout.fillWidth: true\n        Layout.fillHeight: true\n        orientation: Qt.Horizontal\n        spacing: 4\n        model: root.context.categories\n        clip: true\n        delegate: WBorderedButton {\n            id: tagButton\n            required property var modelData\n            border.width: 1\n            radius: height / 2\n            implicitWidth: tagButtonText.implicitWidth + 12 * 2\n            implicitHeight: 32\n            checked: {\n                if (modelData.prefix != \"\") {\n                    return LauncherSearch.query.startsWith(modelData.prefix);\n                } else {\n                    return !tagListView.model.some(i => (i.prefix != \"\" && LauncherSearch.query.startsWith(i.prefix)));\n                }\n            }\n            contentItem: Item {\n                WText {\n                    id: tagButtonText\n                    anchors.centerIn: parent\n                    color: tagButton.fgColor\n                    text: tagButton.modelData.name\n                    font.pixelSize: Looks.font.pixelSize.large\n                }\n            }\n            onClicked: LauncherSearch.ensurePrefix(tagButton.modelData.prefix)\n        }\n    }\n    WPanelIconButton {\n        id: optionsButton\n        implicitWidth: 36\n        implicitHeight: 36\n        iconSize: 24\n        iconName: \"more-horizontal\"\n\n        onClicked: accountsMenu.open()\n\n        WMenu {\n            id: accountsMenu\n            x: -accountsMenu.implicitWidth + optionsButton.implicitWidth + 10\n            y: optionsButton.height\n            downDirection: true\n            Action {\n                icon.name: \"people-settings\"\n                text: Translation.tr(\"Manage accounts\")\n                onTriggered: {\n                    Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.manageUser])\n                    GlobalStates.searchOpen = false;\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AggregatedAppCategoryModel.qml",
    "content": "import QtQuick\nimport qs.services\n\nQtObject {\n    property string name\n    property list<string> categories\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AllAppsGrid.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nGridLayout {\n    id: root\n\n    columns: 4\n\n    Component {\n        id: aggAppCatComp\n        AggregatedAppCategoryModel {}\n    }\n    property list<AggregatedAppCategoryModel> aggregatedCategories: [\n        aggAppCatComp.createObject(null, {\n            name: Translation.tr(\"Productivity\"),\n            categories: [\"Development\", \"Education\", \"Network\", \"Office\"]\n        }), aggAppCatComp.createObject(null, {\n            name: Translation.tr(\"Utilities & Tools\"),\n            categories: [\"Utility\", \"Science\"]\n        }), aggAppCatComp.createObject(null, {\n            name: Translation.tr(\"Creativity\"),\n            categories: [\"AudioVideo\", \"Graphics\"]\n        }), aggAppCatComp.createObject(null, {\n            name: Translation.tr(\"System\"),\n            categories: [\"Settings\", \"System\"]\n        }), aggAppCatComp.createObject(null, {\n            name: Translation.tr(\"Other\"),\n            categories: [\"Game\"]\n        }), \n    ]\n\n    Repeater {\n        model: root.aggregatedCategories\n        delegate: AppCategory {\n            required property var modelData\n            aggregatedCategory: modelData\n        }\n    }\n\n    columnSpacing: 27\n    rowSpacing: 12\n    component AppCategory: Item {\n        id: categoryItem\n        property AggregatedAppCategoryModel aggregatedCategory\n        implicitWidth: categoryLayout.implicitWidth\n        implicitHeight: categoryLayout.implicitHeight\n        ColumnLayout {\n            id: categoryLayout\n            anchors.fill: parent\n            spacing: 4\n\n            AppCategoryGrid {\n                id: categoryGrid\n                Layout.fillWidth: true\n                aggregatedCategory: categoryItem.aggregatedCategory\n            }\n\n            WButton {\n                id: categoryButton\n                Layout.fillWidth: true\n                implicitHeight: 32\n\n                contentItem: WText {\n                    id: categoryButtonText\n                    Layout.fillWidth: true\n                    horizontalAlignment: Text.AlignHCenter\n                    elide: Text.ElideRight\n                    text: categoryItem.aggregatedCategory.name\n                }\n                onClicked: {\n                    categoryGrid.openCategoryFolder();\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/AppCategoryGrid.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nRectangle {\n    id: root\n    property AggregatedAppCategoryModel aggregatedCategory\n    property list<DesktopEntry> desktopEntries: [...DesktopEntries.applications.values.filter(app => {\n        const appCategories = app.categories;\n        const gridCategories = root.aggregatedCategory.categories;\n        return appCategories.some(cat => gridCategories.indexOf(cat) !== -1);\n    })].sort((a, b) => a.name.localeCompare(b.name));\n\n    property Item windowRootItem: {\n        var item = root;\n        // print(\"FINDING ROOT\")\n        while (item.parent != null) {\n            if (item.parent.toString().includes(\"ProxyWindow\"))\n                break;\n            item = item.parent;\n        }\n        // print(item.width, item.height)\n        return item;\n    }\n    function openCategoryFolder() {\n        categoryFolderPopup.open();\n    }\n\n    radius: Looks.radius.large\n    color: Looks.colors.bg1\n    border.width: 1\n    border.color: ColorUtils.transparentize(Looks.colors.ambientShadow, 0.7)\n    implicitWidth: 156\n    implicitHeight: 156\n\n    GridLayout {\n        id: categoryAppsGrid\n        anchors.fill: parent\n        anchors.margins: 10\n        columns: 2\n        rows: 2\n        columnSpacing: 0\n        rowSpacing: 0\n        uniformCellHeights: true\n        uniformCellWidths: true\n\n        Repeater {\n            model: ScriptModel {\n                values: root.desktopEntries.slice(0, 3)\n            }\n            delegate: SmallGridAppButton {\n                required property DesktopEntry modelData\n                desktopEntry: modelData\n            }\n        }\n        Loader {\n            id: categoryOpenButtonLoader\n            // It's like this on the real thing - you get an invisible button if there's not enough items\n            opacity: root.desktopEntries.length > 3 ? 1 : 0\n            active: true\n            sourceComponent: CategoryOpenButton {\n                aggregatedCategory: root.aggregatedCategory\n            }\n        }\n    }\n\n    Popup {\n        id: categoryFolderPopup\n        // I don't even know what the fuck is going on at this point\n        // I hate point mapping\n        property point originPoint: categoryOpenButtonLoader.mapToItem(root, categoryOpenButtonLoader.width / 2, categoryOpenButtonLoader.height / 2)\n        property point windowCenterPoint: {\n            const rootContentItem = root.windowRootItem;\n            const canvasPosInRoot = root.mapFromItem(rootContentItem, rootContentItem.width / 2, rootContentItem.height / 2);\n            const sectionItem = root.parent.parent.parent;\n            const positionInSection = sectionItem.mapFromItem(categoryOpenButtonLoader, categoryOpenButtonLoader.x, categoryOpenButtonLoader.y);\n            const targetY = Math.max(-positionInSection.y + 212, canvasPosInRoot.y);\n            return Qt.point(canvasPosInRoot.x, targetY);\n        }\n\n        enter: Transition {\n            NumberAnimation {\n                target: categoryFolderPopup\n                property: \"x\"\n                from: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2\n                to: categoryFolderPopup.windowCenterPoint.x - categoryFolderPopup.width / 2\n                duration: 300\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n            }\n            NumberAnimation {\n                target: categoryFolderPopup\n                property: \"y\"\n                from: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2\n                to: categoryFolderPopup.windowCenterPoint.y - categoryFolderPopup.height / 2\n                duration: 300\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n            }\n            NumberAnimation {\n                target: categoryFolderPopup\n                property: \"scale\"\n                from: 0\n                to: 1\n                duration: 300\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n            }\n        }\n\n        exit: Transition {\n            NumberAnimation {\n                target: categoryFolderPopup\n                property: \"x\"\n                to: categoryFolderPopup.originPoint.x - categoryOpenButtonLoader.width * 5 / 2\n                duration: 200\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut\n            }\n            NumberAnimation {\n                target: categoryFolderPopup\n                property: \"y\"\n                to: categoryFolderPopup.originPoint.y - categoryOpenButtonLoader.height * 3 / 2\n                duration: 200\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut\n            }\n            NumberAnimation {\n                target: categoryFolderPopup\n                property: \"scale\"\n                from: 1\n                to: 0\n                duration: 200\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Looks.transition.easing.bezierCurve.easeOut\n            }\n        }\n\n        background: null\n\n        Loader {\n            id: folderContentLoader\n            active: categoryFolderPopup.visible\n            sourceComponent: WRectangularShadowThis {\n                CategoryFolderContent {\n                    title: root.aggregatedCategory.name\n                    desktopEntries: root.desktopEntries\n                }\n            }\n        }\n    }\n\n    component CategoryFolderContent: WToolTipContent {\n        id: categoryFolderContent\n        property string title\n        property list<DesktopEntry> desktopEntries: root.desktopEntries\n        horizontalPadding: 0\n        verticalPadding: 0\n        radius: Looks.radius.large\n        realContentItem: Item {\n            implicitWidth: 448\n            implicitHeight: 376\n            ColumnLayout {\n                anchors {\n                    fill: parent\n                    leftMargin: 32\n                    rightMargin: 32\n                    topMargin: 40\n                    bottomMargin: 32\n                }\n                spacing: 28\n                WText {\n                    Layout.fillWidth: true\n                    text: categoryFolderContent.title\n                    font.pixelSize: Looks.font.pixelSize.xlarger\n                    font.weight: Looks.font.weight.stronger\n                    elide: Text.ElideRight\n                    horizontalAlignment: Text.AlignHCenter\n                }\n                Item {\n                    Layout.fillWidth: true\n                    Layout.fillHeight: true\n\n                    SwipeView {\n                        id: categoryFolderSwipeView\n                        anchors.fill: parent\n                        orientation: Qt.Vertical\n                        clip: true\n\n                        Repeater {\n                            model: Math.ceil(root.desktopEntries.length / 12)\n                            delegate: Item {\n                                id: folderPage\n                                required property int index\n                                width: SwipeView.view.width\n                                height: SwipeView.view.height\n                                BigAppGrid {\n                                    anchors {\n                                        top: parent.top\n                                        left: parent.left\n                                    }\n                                    columns: 4\n                                    rows: 3\n                                    desktopEntries: root.desktopEntries.slice(folderPage.index * 12, (folderPage.index + 1) * 12)\n                                }\n                            }\n                        }\n                    }\n                    VerticalPageIndicator {\n                        anchors.verticalCenter: parent.verticalCenter\n                        anchors.right: categoryFolderSwipeView.right\n                        anchors.rightMargin: -19\n\n                        showArrows: false\n                        currentIndex: categoryFolderSwipeView.currentIndex\n                        count: Math.ceil(root.desktopEntries.length / 12)\n                        onClicked: index => categoryFolderSwipeView.currentIndex = index\n                    }\n                }\n            }\n            FocusedScrollMouseArea {\n                z: 999\n                anchors.fill: parent\n                acceptedButtons: Qt.NoButton\n                hoverEnabled: false\n                onScrollUp: categoryFolderSwipeView.decrementCurrentIndex()\n                onScrollDown: categoryFolderSwipeView.incrementCurrentIndex()\n            }\n        }\n    }\n\n    component CategoryOpenButton: SmallGridButton {\n        id: categoryOpenButton\n        property AggregatedAppCategoryModel aggregatedCategory\n\n        onClicked: root.openCategoryFolder()\n        contentItem: Item {\n            Behavior on scale {\n                NumberAnimation {\n                    id: scaleAnim\n                    easing.type: Easing.BezierSpline\n                    easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n                }\n            }\n            GridLayout {\n                anchors.centerIn: parent\n                rows: 2\n                columns: 2\n                rowSpacing: 2\n                columnSpacing: 2\n\n                Repeater {\n                    model: root.desktopEntries.slice(3, 7)\n                    delegate: WAppIcon {\n                        required property DesktopEntry modelData\n                        tryCustomIcon: false\n                        iconName: modelData.icon\n                        implicitSize: 16\n                    }\n                }\n            }\n        }\n    }\n\n    component SmallGridAppButton: SmallGridButton {\n        id: smallGridAppButton\n        property DesktopEntry desktopEntry\n\n        property bool pinnedStart: LauncherApps.isPinned(smallGridAppButton.desktopEntry.id);\n        property bool pinnedTaskbar: TaskbarApps.isPinned(smallGridAppButton.desktopEntry.id);\n\n        onClicked: {\n            GlobalStates.searchOpen = false;\n            desktopEntry.execute();\n        }\n\n        contentItem: Item {\n            Behavior on scale {\n                NumberAnimation {\n                    id: scaleAnim\n                    easing.type: Easing.BezierSpline\n                    easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n                }\n            }\n            WAppIcon {\n                anchors.centerIn: parent\n                tryCustomIcon: false\n                iconName: smallGridAppButton.desktopEntry.icon\n                implicitSize: 34\n            }\n        }\n\n        WToolTip {\n            text: smallGridAppButton.desktopEntry.name\n        }\n\n        altAction: () => {\n            appMenu.popup();\n        }\n\n        WMenu {\n            id: appMenu\n            downDirection: true\n\n            WMenuItem {\n                icon.name: smallGridAppButton.pinnedStart ? \"pin-off\" : \"pin\"\n                text: smallGridAppButton.pinnedStart ? Translation.tr(\"Unpin from Start\") : Translation.tr(\"Pin to Start\")\n                onTriggered: {\n                    LauncherApps.togglePin(smallGridAppButton.desktopEntry.id);\n                }\n            }\n            WMenuItem {\n                icon.name: smallGridAppButton.pinnedTaskbar ? \"pin-off\" : \"pin\"\n                text: smallGridAppButton.pinnedTaskbar ? Translation.tr(\"Unpin from taskbar\") : Translation.tr(\"Pin to taskbar\")\n                onTriggered: {\n                    TaskbarApps.togglePin(smallGridAppButton.desktopEntry.id);\n                }\n            }\n        }\n    }\n\n    component SmallGridButton: WButton {\n        id: root\n        implicitWidth: 68\n        implicitHeight: 68\n\n        property real pressedScale: 5 / 6\n\n        onDownChanged: {\n            contentItem.scale = root.down ? root.pressedScale : 1; // If/When we do dragging, the scale is 1.25\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/BigAppGrid.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nGridLayout {\n    id: root\n\n    property list<var> desktopEntries: []\n\n    columnSpacing: 0\n    rowSpacing: 0\n\n    uniformCellHeights: true\n    uniformCellWidths: true\n\n    Repeater {\n        model: root.desktopEntries\n        delegate: StartAppButton {\n            id: pinnedAppButton\n            required property var modelData\n            desktopEntry: modelData\n            onClicked: {\n                GlobalStates.searchOpen = false;\n                desktopEntry.execute();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartAppButton.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nWButton {\n    id: root\n    required property DesktopEntry desktopEntry\n\n    property bool pinnedStart: LauncherApps.isPinned(root.desktopEntry.id);\n    property bool pinnedTaskbar: TaskbarApps.isPinned(root.desktopEntry.id);\n\n    implicitWidth: 96\n    implicitHeight: 84\n    horizontalPadding: 0\n    verticalPadding: 0\n    contentItem: ColumnLayout {\n        spacing: 3\n        WAppIcon {\n            Layout.topMargin: 12\n            Layout.alignment: Qt.AlignHCenter\n            iconName: root.desktopEntry.icon\n            implicitSize: 34\n            tryCustomIcon: false\n        }\n        WText {\n            Layout.fillHeight: true\n            Layout.fillWidth: true\n            Layout.leftMargin: 8\n            Layout.rightMargin: 8\n            text: root.desktopEntry.name\n            wrapMode: Text.Wrap\n            elide: Text.ElideRight\n            maximumLineCount: 2\n            horizontalAlignment: Text.AlignHCenter\n            verticalAlignment: Text.AlignTop\n        }\n    }\n    WToolTip {\n        text: root.desktopEntry.name\n    }\n\n    altAction: () => {\n        appMenu.popup()\n    }\n\n    WMenu {\n        id: appMenu\n        downDirection: true\n        \n        WMenuItem {\n            visible: root.pinnedStart\n            icon.name: \"arrow-up-left\"\n            text: Translation.tr(\"Move to front\")\n            onTriggered: {\n                LauncherApps.moveToFront(root.desktopEntry.id);\n            }\n        }\n        WMenuItem {\n            visible: root.pinnedStart\n            icon.name: \"arrow-left\"\n            text: Translation.tr(\"Move left\")\n            onTriggered: {\n                LauncherApps.moveLeft(root.desktopEntry.id);\n            }\n        }\n        WMenuItem {\n            visible: root.pinnedStart\n            icon.name: \"arrow-right\"\n            text: Translation.tr(\"Move right\")\n            onTriggered: {\n                LauncherApps.moveRight(root.desktopEntry.id);\n            }\n        }\n        WMenuItem {\n            icon.name: root.pinnedStart ? \"pin-off\" : \"pin\"\n            text: root.pinnedStart ? Translation.tr(\"Unpin from Start\") : Translation.tr(\"Pin to Start\")\n            onTriggered: {\n                LauncherApps.togglePin(root.desktopEntry.id);\n            }\n        }\n        WMenuItem {\n            icon.name: root.pinnedTaskbar ? \"pin-off\" : \"pin\"\n            text: root.pinnedTaskbar ? Translation.tr(\"Unpin from taskbar\") : Translation.tr(\"Pin to taskbar\")\n            onTriggered: {\n                TaskbarApps.togglePin(root.desktopEntry.id);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageApps.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nBodyRectangle {\n    id: root\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            leftMargin: 32\n            rightMargin: 32\n            topMargin: 25\n            bottomMargin: 30\n        }\n        spacing: 26\n\n        PinnedApps {\n            Layout.fillWidth: true\n        }\n\n        AllApps {\n            implicitHeight: 300 // for now\n        }\n    }\n\n    component PinnedApps: PageSection {\n        title: Translation.tr(\"Pinned\")\n\n        BigAppGrid {\n            Layout.fillWidth: true\n            columns: 8\n            desktopEntries: Config.options.launcher.pinnedApps.map(appId => DesktopEntries.byId(appId))\n        }\n    }\n\n    component AllApps: PageSection {\n        title: Translation.tr(\"All\")\n        // TODO: Do we wanna also implement list view and grid view?\n        //       (instead of only category view)\n        AllAppsGrid {\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            Layout.leftMargin: 32\n            Layout.rightMargin: 32\n        }\n    }\n\n    component PageSection: ColumnLayout {\n        id: pageSection\n        required property string title\n        default property alias pageData: pageSectionContentArea.data\n\n        spacing: 16\n\n        WText {\n            Layout.leftMargin: 32\n            text: pageSection.title\n            font.pixelSize: Looks.font.pixelSize.large\n            font.weight: Looks.font.weight.stronger\n        }\n\n        ColumnLayout {\n            id: pageSectionContentArea\n            Layout.fillWidth: true\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartPageContent.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nWPanelPageColumn {\n    id: root\n\n    WPanelSeparator {}\n\n    StartPageApps {\n        Layout.fillHeight: true\n    }\n\n    WPanelSeparator {}\n\n    StartFooter {\n        Layout.fillWidth: true\n    }\n\n    component StartFooter: FooterRectangle {\n        implicitHeight: 63\n\n        StartUserButton {\n            anchors {\n                left: parent.left\n                leftMargin: 52\n                bottom: parent.bottom\n                bottomMargin: 12\n            }\n        }\n\n        PowerButton {\n            anchors {\n                right: parent.right\n                rightMargin: 52\n                bottom: parent.bottom\n                bottomMargin: 12\n            }\n        }\n    }\n\n    component PowerButton: WBorderlessButton {\n        id: powerButton\n        implicitWidth: 40\n        implicitHeight: 40\n\n        contentItem: Item {\n            FluentIcon {\n                anchors.centerIn: parent\n                icon: \"power\"\n                implicitSize: 20\n            }\n        }\n\n        WToolTip {\n            extraVisibleCondition: !powerMenu.visible\n            text: qsTr(\"Power\")\n        }\n\n        onClicked: {\n            powerMenu.open()\n        }\n\n        WMenu {\n            id: powerMenu\n            x: -powerMenu.implicitWidth / 2 + powerButton.implicitWidth / 2\n            y: -powerMenu.implicitHeight - 4\n            Action {\n                icon.name: \"lock-closed\"\n                text: Translation.tr(\"Lock\")\n                onTriggered: Session.lock()\n            }\n            Action {\n                icon.name: \"weather-moon\"\n                text: Translation.tr(\"Sleep\")\n                onTriggered: Session.suspend()\n            }\n            Action {\n                icon.name: \"power\"\n                text: Translation.tr(\"Shut down\")\n                onTriggered: Session.poweroff()\n            }\n            Action {\n                icon.name: \"arrow-counterclockwise\"\n                text: Translation.tr(\"Restart\")\n                onTriggered: Session.reboot()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/startMenu/startPage/StartUserButton.qml",
    "content": "pragma ComponentBehavior: Bound\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nWBorderlessButton {\n    id: userButton\n    implicitWidth: userButtonRow.implicitWidth + 12 * 2\n    implicitHeight: 40\n\n    contentItem: Item {\n        RowLayout {\n            id: userButtonRow\n            anchors.centerIn: parent\n            spacing: 12\n\n            WUserAvatar {\n                sourceSize: Qt.size(32, 32)\n            }\n            WText {\n                Layout.alignment: Qt.AlignVCenter\n                text: SystemInfo.username\n            }\n        }\n    }\n\n    onClicked: {\n        userMenu.open();\n    }\n\n    WToolTip {\n        text: SystemInfo.username\n    }\n\n    Popup {\n        id: userMenu\n        x: -51\n        y: -userMenu.implicitHeight + userButton.implicitHeight / 2 - 10\n\n        background: null\n        \n        WToolTipContent {\n            id: popupContent\n            horizontalPadding: 10\n            verticalPadding: 7\n            radius: Looks.radius.large\n            realContentItem: Item {\n                implicitWidth: userMenuContentLayout.implicitWidth\n                implicitHeight: userMenuContentLayout.implicitHeight\n                \n                ColumnLayout {\n                    id: userMenuContentLayout\n                    anchors {\n                        fill: parent\n                        leftMargin: popupContent.horizontalPadding\n                        rightMargin: popupContent.horizontalPadding\n                        topMargin: popupContent.verticalPadding\n                        bottomMargin: popupContent.verticalPadding\n                    }\n                    spacing: 5\n\n                    RowLayout {\n                        Layout.fillWidth: true\n                        Layout.leftMargin: 6\n                        FluentIcon {\n                            Layout.alignment: Qt.AlignVCenter\n                            implicitSize: 22\n                            icon: \"corporation\"\n                            monochrome: false\n                        }\n                        WText {\n                            Layout.alignment: Qt.AlignVCenter\n                            text: \"Megahard\"\n                            font.pixelSize: Looks.font.pixelSize.large\n                            font.weight: Looks.font.weight.strong\n                        }\n                        Item { Layout.fillWidth: true }\n                        WBorderlessButton {\n                            Layout.alignment: Qt.AlignVCenter\n                            implicitHeight: 36\n                            implicitWidth: textItem.implicitWidth + 10 * 2\n                            contentItem: WText {\n                                id: textItem\n                                text: Translation.tr(\"Sign out\")\n                                font.pixelSize: Looks.font.pixelSize.large\n                            }\n                            onClicked: Session.logout()\n                        }\n                    }\n                    Item { // Force min width 360 (using min on the item somehow doesn't work)\n                        implicitWidth: 334\n                    }\n                    RowLayout {\n                        Layout.fillWidth: true\n                        Layout.bottomMargin: 7\n                        Layout.leftMargin: 6\n                        spacing: 12\n                        WUserAvatar {\n                            sourceSize: Qt.size(58, 58)\n                        }\n                        ColumnLayout {\n                            Layout.fillWidth: true\n                            Layout.alignment: Qt.AlignVCenter\n                            spacing: 2\n                            WText {\n                                text: SystemInfo.username\n                                font.pixelSize: Looks.font.pixelSize.larger\n                                font.weight: Looks.font.weight.strong\n                            }\n                            WText {\n                                color: Looks.colors.fg1\n                                text: Translation.tr(\"Local account\")\n                            }\n                            WText {\n                                color: Looks.colors.accent\n                                text: Translation.tr(\"Manage my account\")\n                                MouseArea {\n                                    anchors.fill: parent\n                                    cursorShape: Qt.PointingHandCursor\n                                    onClicked: {\n                                        Quickshell.execDetached([\"bash\", \"-c\", Config.options.apps.manageUser])\n                                        GlobalStates.searchOpen = false;\n                                    }\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/taskView/TaskViewContent.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.models\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport \"window-layout.js\" as WindowLayout\n\nRectangle {\n    id: root\n\n    color: ColorUtils.transparentize(Looks.colors.bg1Base, 0.5)\n    property bool draggingWindow: false\n    property real openProgress: 0\n    property Item hoveredWorkspace: null\n    signal closed\n\n    Component.onCompleted: {\n        openAnim.start();\n    }\n    function close() {\n        closeAnim.start();\n    }\n\n    PropertyAnimation {\n        id: openAnim\n        target: root\n        property: \"openProgress\"\n        to: 1\n        duration: 250\n        easing.type: Easing.BezierSpline\n        easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n    }\n    SequentialAnimation {\n        id: closeAnim\n\n        PropertyAnimation {\n            target: root\n            property: \"openProgress\"\n            to: 0\n            duration: 250\n            easing.type: Easing.BezierSpline\n            easing.bezierCurve: Looks.transition.easing.bezierCurve.easeIn\n        }\n        ScriptAction {\n            script: {\n                root.closed();\n            }\n        }\n    }\n\n    // Windows\n    property real maxWindowHeight: 290\n    property real maxWindowWidth: 738\n    property real padding: 52\n    property real spacing: 25\n    readonly property list<var> toplevels: ToplevelManager.toplevels.values.filter(t => {\n        const client = HyprlandData.clientForToplevel(t);\n        return client && client.workspace.id === HyprlandData.activeWorkspace?.id;\n    })\n    readonly property list<var> arrangedToplevels: {\n        const maxRowWidth = width - padding * 2;\n        const count = toplevels.length;\n        const resultLayout = [];\n\n        var i = 0;\n        while (i < count) {\n            var row = [];\n            var rowWidth = 0;\n            var j = i;\n\n            while (j < count) {\n                const toplevel = toplevels[j];\n                const client = HyprlandData.clientForToplevel(toplevel);\n                const scaledSize = WindowLayout.scaleWindow(client, maxWindowWidth, maxWindowHeight);\n\n                if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) {\n                    row.push(toplevel);\n                    rowWidth += scaledSize.width;\n                    j++;\n                } else {\n                    break;\n                }\n            }\n\n            resultLayout.push(row);\n            i = j;\n        }\n        return resultLayout;\n    }\n\n    MouseArea {\n        z: 0\n        anchors.fill: parent\n        onClicked: {\n            GlobalStates.overviewOpen = false;\n        }\n    }\n\n    // Windows\n    WListView {\n        id: windowListView\n        z: root.openProgress == 1 ? 2 : 1\n        anchors {\n            left: parent.left\n            right: parent.right\n            top: parent.top\n            topMargin: (root.height - (wsBorder.height + 16) - height) / 2\n        }\n        spacing: root.spacing\n        topMargin: root.padding\n        bottomMargin: root.padding\n        leftMargin: root.padding\n        rightMargin: root.padding\n        height: Math.min(contentHeight + topMargin + bottomMargin, root.height - (wsBorder.height + 16))\n\n        interactive: (height < contentHeight) && !root.draggingWindow\n        clip: root.openProgress > 0.99 && !root.draggingWindow\n\n        model: ScriptModel {\n            values: root.arrangedToplevels\n        }\n        delegate: RowLayout {\n            id: clientRow\n            required property var modelData\n            spacing: root.spacing\n            anchors.horizontalCenter: parent?.horizontalCenter ?? undefined\n\n            Repeater {\n                model: ScriptModel {\n                    values: clientRow.modelData\n                }\n                delegate: Item {\n                    id: clientGridArea\n                    required property int index\n                    required property var modelData\n                    implicitWidth: windowItem.openedSize.width\n                    implicitHeight: windowItem.openedSize.height + windowItem.titleBarImplicitHeight\n\n                    TaskViewWindow {\n                        id: windowItem\n                        z: Drag.active ? 2 : 1\n                        opacity: openAnim.running ? root.openProgress : 1\n\n                        property int mappedX: {\n                            // print(\"AAAWAWAAWAWWA: \", -(clientRow.x + clientGridArea.x + root.padding));\n                            var rootPosToThis = -(clientRow.x + clientGridArea.x + root.padding);\n                            return rootPosToThis + hyprlandClient.at[0];\n                        }\n                        property int mappedY: {\n                            // print(\"AAAWAWAAWAWWA YYYY YUIUSDFOIU: \", clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight)\n                            var rootPosToThis = -(clientRow.y + windowListView.y + root.padding + windowItem.titleBarImplicitHeight);\n                            return rootPosToThis + hyprlandClient.at[1];\n                        }\n                        property int openedX: 0\n                        property int openedY: 0\n                        // property int openedX: Drag.active ? (dragHandler.xAxis.activeValue) : 0\n                        // property int openedY: Drag.active ? (dragHandler.yAxis.activeValue) : 0\n                        scaleSize: (root.openProgress > 0 && !closeAnim.running)\n                        x: mappedX + (openedX - mappedX) * root.openProgress\n                        y: mappedY + (openedY - mappedY) * root.openProgress\n\n                        droppable: root.hoveredWorkspace !== null\n                        Drag.active: dragHandler.active\n                        Drag.hotSpot.x: mouseX\n                        Drag.hotSpot.y: mouseY\n\n                        DragHandler {\n                            id: dragHandler\n                            target: null\n                            xAxis.onActiveValueChanged: {\n                                windowItem.openedX = dragHandler.xAxis.activeValue;\n                            }\n                            yAxis.onActiveValueChanged: {\n                                windowItem.openedY = dragHandler.yAxis.activeValue;\n                            }\n                            onActiveChanged: {\n                                if (active) {\n                                    root.draggingWindow = true;\n                                } else {\n                                    root.draggingWindow = false;\n                                    if (root.hoveredWorkspace !== null && root.hoveredWorkspace.workspace !== windowItem.hyprlandClient.workspace.id) {\n                                        Hyprland.dispatch(`hl.dsp.window.move({ workspace = ${root.hoveredWorkspace.workspace}, follow = false, window = \"address:${windowItem.hyprlandClient.address}\" })`)\n                                    } else {\n                                        windowItem.openedX = 0;\n                                        windowItem.openedY = 0;\n                                    }\n                                }\n                            }\n                        }\n\n                        Layout.alignment: Qt.AlignTop\n                        maxHeight: root.maxWindowHeight\n                        maxWidth: root.maxWindowWidth\n                        toplevel: clientGridArea.modelData\n                    }\n                }\n            }\n        }\n    }\n\n    // Workspaces\n    Rectangle {\n        id: wsBorder\n        z: root.openProgress == 1 ? 1 : 2\n        property real sourceEdgeMargin: -(height + 8) + root.openProgress * (height + 16)\n        anchors {\n            left: parent.left\n            right: parent.right\n            bottom: parent.bottom\n            leftMargin: 8\n            rightMargin: 8\n            topMargin: sourceEdgeMargin\n            bottomMargin: sourceEdgeMargin\n        }\n        border.color: Looks.colors.bg2Border\n        border.width: 1\n        radius: Looks.radius.large\n        color: \"transparent\"\n\n        implicitHeight: wsBg.implicitHeight + border.width * 2\n\n        Rectangle {\n            id: wsBg\n            anchors.fill: parent\n            anchors.margins: wsBorder.border.width\n            radius: wsBorder.radius - wsBorder.border.width\n            color: Looks.colors.bgPanelFooterBackground\n\n            implicitHeight: 174\n\n            WListView {\n                id: workspaceListView\n                anchors {\n                    top: parent.top\n                    bottom: parent.bottom\n                    horizontalCenter: parent.horizontalCenter\n                    topMargin: 5\n                    bottomMargin: 5\n                }\n                flickableDirection: Flickable.HorizontalFlick\n                orientation: ListView.Horizontal\n                interactive: width == parent.width\n                width: Math.min(contentWidth + leftMargin + rightMargin, parent.width)\n                leftMargin: 5\n                rightMargin: 5\n                clip: true\n                spacing: 4\n\n                function reposition() {\n                    positionViewAtIndex(HyprlandData.activeWorkspace.id - 1, ListView.Contain);\n                }\n\n                Connections {\n                    target: HyprlandData\n                    function onActiveWorkspaceChanged() {\n                        workspaceListView.reposition();\n                    }\n                }\n                model: IndexModel {\n                    id: workspaceIndexModel\n                    count: {\n                        const maxWorkspaceId = Math.max.apply(null, HyprlandData.workspaces.map(ws => ws.id));\n                        return Math.max(maxWorkspaceId, 1) + 1;\n                    }\n                }\n                delegate: TaskViewWorkspace {\n                    id: workspaceItem\n                    required property int index\n                    workspace: index + 1\n                    newWorkspace: index == workspaceIndexModel.count - 1\n\n                    droppable: root.hoveredWorkspace === workspaceItem\n                    DropArea {\n                        anchors.fill: parent\n                        onEntered: drag => {\n                            root.hoveredWorkspace = workspaceItem;\n                        }\n                        onExited: {\n                            if (root.hoveredWorkspace === workspaceItem) {\n                                root.hoveredWorkspace = null;\n                            }\n                        }\n                    }\n\n                    onClicked: {\n                        GlobalStates.overviewOpen = false;\n                        root.closed(); // Close immediately to avoid weird animations\n                        Hyprland.dispatch(`hl.dsp.focus({workspace = ${workspaceItem.workspace}})`);\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/taskView/TaskViewWindow.qml",
    "content": "import QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\nimport \"window-layout.js\" as WindowLayout\n\nWMouseAreaButton {\n    id: root\n\n    required property var toplevel\n    required property int maxHeight\n    required property int maxWidth\n\n    property var hyprlandClient: HyprlandData.clientForToplevel(root.toplevel)\n    property string address: hyprlandClient?.address\n\n    property string iconName: AppSearch.guessIcon(hyprlandClient?.class)\n\n    color: drag.active ? ColorUtils.transparentize(Looks.colors.bg1Base) : (containsMouse ? Looks.colors.bg1Base : Looks.colors.bgPanelFooterBackground)\n    borderColor: ColorUtils.transparentize(Looks.colors.bg2Border, drag.active ? 1 : 0)\n    radius: Looks.radius.xLarge\n\n    property real titleBarImplicitHeight: titleBar.implicitHeight\n    property bool scaleSize: true\n    property size openedSize: WindowLayout.scaleWindow(hyprlandClient, maxWidth, maxHeight);\n    property size fullSize: Qt.size(hyprlandClient?.size[0] ?? maxWidth, hyprlandClient?.size[1] ?? maxHeight)\n    property size size: scaleSize ? openedSize : fullSize\n    implicitWidth: Math.max(Math.round(contentItem.implicitWidth), 138)\n    implicitHeight: Math.round(contentItem.implicitHeight)\n\n    layer.enabled: true\n    layer.effect: OpacityMask {\n        maskSource: Item {\n            width: root.background.width\n            height: root.background.height\n            Rectangle {\n                radius: root.background.radius\n                anchors {\n                    fill: parent\n                    topMargin: root.drag.active ? root.titleBarImplicitHeight : 0\n                }\n            }\n        }\n    }\n    property bool droppable: false\n    scale: (root.pressedButtons & Qt.LeftButton || root.Drag.active) ? (droppable ? 0.4 : 0.95) : 1\n    Behavior on scale {\n        NumberAnimation {\n            id: scaleAnim\n            duration: 200\n            easing.type: Easing.OutExpo\n        }\n    }\n\n    function closeWindow() {\n        Hyprland.dispatch(`hl.dsp.window.close({window = \"address:${root.hyprlandClient?.address}\"})`)\n    }\n\n    acceptedButtons: Qt.LeftButton | Qt.MiddleButton | Qt.RightButton\n    onClicked: event => {\n        if (event.button === Qt.LeftButton) {\n            GlobalStates.overviewOpen = false;\n            Hyprland.dispatch(`hl.dsp.focus({window = \"address:${root.hyprlandClient?.address}\"})`)\n            GlobalStates.overviewOpen = false;\n        } else if (event.button === Qt.MiddleButton) {\n            root.closeWindow();\n            event.accepted = true;\n        } else if (event.button === Qt.RightButton) {\n            if (!windowMenu.visible)\n                windowMenu.popup();\n            else\n                windowMenu.close();\n        }\n    }\n\n    ColumnLayout {\n        id: contentItem\n        z: 2\n        anchors.fill: parent\n        anchors.margins: 1\n        spacing: 0\n\n        RowLayout {\n            id: titleBar\n            opacity: root.drag.active ? 0 : 1\n            spacing: 8\n            WAppIcon {\n                Layout.leftMargin: 10\n                Layout.alignment: Qt.AlignVCenter\n                iconName: root.iconName\n                implicitSize: 16\n                tryCustomIcon: false\n            }\n            WText {\n                Layout.fillWidth: true\n                Layout.alignment: Qt.AlignVCenter\n                elide: Text.ElideRight\n                text: root.hyprlandClient?.title ?? \"\"\n            }\n            CloseButton {\n                implicitWidth: 38\n                implicitHeight: 38\n                padding: 8\n                onClicked: root.closeWindow()\n            }\n        }\n\n        ScreencopyView {\n            Layout.fillHeight: true\n            Layout.alignment: Qt.AlignHCenter\n            implicitWidth: Math.round(root.size.width)\n            implicitHeight: Math.round(root.size.height)\n            constraintSize: Qt.size(Math.round(root.size.width), Math.round(root.size.height))\n\n            Behavior on implicitWidth {\n                animation: Looks.transition.enter.createObject(this)\n            }\n            Behavior on implicitHeight {\n                animation: Looks.transition.enter.createObject(this)\n            }\n\n            captureSource: root.toplevel ?? null\n            live: true\n        }\n    }\n\n    WMenu {\n        id: windowMenu\n        downDirection: true\n\n        Action {\n            enabled: root.hyprlandClient?.floating\n            property bool isPinned: root.hyprlandClient?.pinned\n            icon.name: isPinned ? \"checkmark\" : \"empty\"\n            text: Translation.tr(\"Show this window on all desktops\")\n            onTriggered: {\n                Hyprland.dispatch(`hl.dsp.window.pin({window = \"address:${root.hyprlandClient?.address}\"})`);\n            }\n        }\n        Action {\n            icon.name: \"empty\"\n            text: Translation.tr(\"Close\")\n            onTriggered: root.closeWindow()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/taskView/TaskViewWorkspace.qml",
    "content": "import QtQuick\nimport QtQuick.Layouts\nimport Qt5Compat.GraphicalEffects\nimport Quickshell\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.functions\nimport qs.modules.common.widgets\nimport qs.modules.waffle.looks\n\nWMouseAreaButton {\n    id: root\n\n    required property int workspace\n    property bool newWorkspace: false\n    property bool droppable: false\n\n    readonly property bool isActiveWorkspace: HyprlandData.activeWorkspace?.id === root.workspace\n    readonly property real screenWidth: QsWindow.window?.width ?? 0\n    readonly property real screenHeight: QsWindow.window?.height ?? 0\n    readonly property real screenAspectRatio: screenWidth / screenHeight\n    readonly property real windowScale: wallpaperHeight / screenHeight\n\n    property real wallpaperHeight: 124\n\n    height: ListView.view?.height ?? 100\n    implicitWidth: 244 // for now\n\n    colBackground: ColorUtils.transparentize(Looks.colors.bg2, (isActiveWorkspace || droppable) ? 0 : 1)\n    Behavior on color {\n        animation: Looks.transition.color.createObject(this)\n    }\n\n    scale: root.containsPress ? 0.95 : 1\n    Behavior on scale {\n        NumberAnimation {\n            id: scaleAnim\n            duration: 300\n            easing.type: Easing.OutExpo\n        }\n    }\n\n    // Content\n    ColumnLayout {\n        id: contentItem\n        anchors {\n            fill: parent\n            leftMargin: 12\n            rightMargin: 12\n            topMargin: 9\n            bottomMargin: 8\n        }\n        spacing: 8\n\n        WText {\n            Layout.fillWidth: true\n            Layout.fillHeight: false\n            horizontalAlignment: Text.AlignLeft\n            elide: Text.ElideRight\n            text: root.newWorkspace ? Translation.tr(\"New desktop\") : Translation.tr(\"Desktop %1\").arg(root.workspace)\n        }\n\n        Rectangle {\n            id: wsBg\n            height: root.wallpaperHeight\n            Layout.fillHeight: true\n            Layout.fillWidth: true\n            color: Looks.colors.bg1\n\n            layer.enabled: true\n            layer.effect: OpacityMask {\n                maskSource: Rectangle {\n                    width: wsBg.width\n                    height: wsBg.height\n                    radius: Looks.radius.medium\n                }\n            }\n\n            // Workspace content\n            Loader {\n                anchors.fill: parent\n                active: !root.newWorkspace\n                sourceComponent: StyledImage {\n                    cache: true\n                    source: Config.options.background.wallpaperPath\n                    fillMode: Image.PreserveAspectCrop\n\n                    Repeater {\n                        model: ScriptModel {\n                            values: HyprlandData.toplevelsForWorkspace(root.workspace)\n                        }\n                        delegate: ScreencopyView {\n                            required property var modelData\n                            readonly property var hyprlandWindowData: HyprlandData.windowByAddress[`0x${modelData.HyprlandToplevel?.address}`]\n                            captureSource: modelData\n                            live: true\n                            width: hyprlandWindowData?.size[0] * root.windowScale\n                            height: hyprlandWindowData?.size[1] * root.windowScale\n                            x: hyprlandWindowData?.at[0] * root.windowScale\n                            y: hyprlandWindowData?.at[1] * root.windowScale\n                        }\n                    }\n                }\n            }\n\n            // New plus icon\n            Loader {\n                anchors.centerIn: parent\n                active: root.newWorkspace\n                sourceComponent: FluentIcon {\n                    icon: \"add\"\n                }\n            }\n\n            Rectangle {\n                z: 2\n                visible: root.droppable && !root.newWorkspace\n                anchors.fill: parent\n                color: Looks.colors.accent\n                opacity: 0.2\n            }\n        }\n    }\n\n    // Active indicator\n    WFadeLoader {\n        anchors {\n            horizontalCenter: parent.horizontalCenter\n            bottom: parent.bottom\n        }\n        shown: root.isActiveWorkspace\n\n        sourceComponent: Rectangle {\n            id: activeIndicator\n            implicitWidth: 32\n            implicitHeight: 3\n            color: Looks.colors.accent\n            radius: height / 2\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/taskView/WaffleTaskView.qml",
    "content": "pragma ComponentBehavior: Bound\nimport qs\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport Qt.labs.synchronizer\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\nScope {\n    id: overviewScope\n    property bool dontAutoCancelSearch: false\n\n    Variants {\n        id: overviewVariants\n        model: Quickshell.screens\n\n        Loader {\n            id: panelLoader\n            required property var modelData\n            active: false\n            Connections {\n                target: GlobalStates\n                function onOverviewOpenChanged() {\n                    if (GlobalStates.overviewOpen)\n                        panelLoader.active = true;\n                }\n            }\n            sourceComponent: PanelWindow {\n                id: root\n                property string searchingText: \"\"\n                readonly property HyprlandMonitor monitor: Hyprland.monitorFor(root.screen)\n                property bool monitorIsFocused: (Hyprland.focusedMonitor?.id == monitor?.id)\n                screen: panelLoader.modelData\n\n                WlrLayershell.namespace: \"quickshell:wTaskView\"\n                WlrLayershell.layer: WlrLayer.Overlay\n                WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand\n                color: \"transparent\"\n\n                anchors {\n                    top: true\n                    bottom: true\n                    left: true\n                    right: true\n                }\n\n                TaskViewContent {\n                    id: taskViewContent\n                    anchors.fill: parent\n\n                    Component.onCompleted: {\n                        taskViewContent.forceActiveFocus();\n                    }\n                    Keys.onPressed: event => {\n                        if (event.key === Qt.Key_Escape) {\n                            GlobalStates.overviewOpen = false;\n                        }\n                    }\n\n                    Connections {\n                        target: GlobalStates\n                        function onOverviewOpenChanged() {\n                            if (!GlobalStates.overviewOpen)\n                                taskViewContent.close();\n                        }\n                    }\n                    onClosed: panelLoader.active = false\n                }\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"search\"\n\n        function toggle() {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n        function workspacesToggle() {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n        function close() {\n            GlobalStates.overviewOpen = false;\n        }\n        function open() {\n            GlobalStates.overviewOpen = true;\n        }\n        function toggleReleaseInterrupt() {\n            GlobalStates.superReleaseMightTrigger = false;\n        }\n        function clipboardToggle() {\n            overviewScope.toggleClipboard();\n        }\n    }\n\n    GlobalShortcut {\n        name: \"overviewWorkspacesToggle\"\n        description: \"Toggles overview on press\"\n\n        onPressed: {\n            GlobalStates.overviewOpen = !GlobalStates.overviewOpen;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/modules/waffle/taskView/window-layout.js",
    "content": "function scaleWindow(hyprlandClient, maxWindowWidth, maxWindowHeight) {\n    const [width, height] = hyprlandClient.size;\n    const [xScale, yScale] = [maxWindowWidth / width, maxWindowHeight / height];\n    const scale = Math.min(xScale, yScale);\n    return Qt.size(width * scale, height * scale)\n}\n\nfunction arrangedClients(hyprlandClients, maxRowWidth, maxWindowWidth, maxWindowHeight) {\n    const count = hyprlandClients.length;\n    const resultLayout = [];\n\n    var i = 0;\n    while (i < count) {\n        var row = [];\n        var rowWidth = 0;\n        var j = i;\n\n        while (j < count) {\n            const client = hyprlandClients[j];\n            const scaledSize = scaleWindow(client, maxWindowWidth, maxWindowHeight);\n\n            if (rowWidth + scaledSize.width <= maxRowWidth || row.length === 0) {\n                row.push(client);\n                rowWidth += scaledSize.width;\n                j++;\n            } else {\n                break;\n            }\n        }\n        \n        resultLayout.push(row);\n        i = j;\n    }\n\n    return resultLayout;\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/panelFamilies/IllogicalImpulseFamily.qml",
    "content": "import QtQuick\nimport Quickshell\n\nimport qs.modules.common\nimport qs.modules.ii.background\nimport qs.modules.ii.bar\nimport qs.modules.ii.cheatsheet\nimport qs.modules.ii.dock\nimport qs.modules.ii.lock\nimport qs.modules.ii.mediaControls\nimport qs.modules.ii.notificationPopup\nimport qs.modules.ii.onScreenDisplay\nimport qs.modules.ii.onScreenKeyboard\nimport qs.modules.ii.overview\nimport qs.modules.ii.polkit\nimport qs.modules.ii.regionSelector\nimport qs.modules.ii.screenCorners\nimport qs.modules.ii.screenTranslator\nimport qs.modules.ii.sessionScreen\nimport qs.modules.ii.sidebarLeft\nimport qs.modules.ii.sidebarRight\nimport qs.modules.ii.overlay\nimport qs.modules.ii.verticalBar\nimport qs.modules.ii.wallpaperSelector\n\nScope {\n    PanelLoader { extraCondition: !Config.options.bar.vertical; component: Bar {} }\n    PanelLoader { component: Background {} }\n    PanelLoader { component: Cheatsheet {} }\n    PanelLoader { extraCondition: Config.options.dock.enable; component: Dock {} }\n    PanelLoader { component: Lock {} }\n    PanelLoader { component: MediaControls {} }\n    PanelLoader { component: NotificationPopup {} }\n    PanelLoader { component: OnScreenDisplay {} }\n    PanelLoader { component: OnScreenKeyboard {} }\n    PanelLoader { component: Overlay {} }\n    PanelLoader { component: Overview {} }\n    PanelLoader { component: Polkit {} }\n    PanelLoader { component: RegionSelector {} }\n    PanelLoader { component: ScreenCorners {} }\n    PanelLoader { component: ScreenTranslator {} }\n    PanelLoader { component: SessionScreen {} }\n    PanelLoader { component: SidebarLeft {} }\n    PanelLoader { component: SidebarRight {} }\n    PanelLoader { extraCondition: Config.options.bar.vertical; component: VerticalBar {} }\n    PanelLoader { component: WallpaperSelector {} }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/panelFamilies/PanelLoader.qml",
    "content": "import QtQuick\nimport Quickshell\n\nimport qs.modules.common\n\nLazyLoader {\n    property bool extraCondition: true\n    active: Config.ready && extraCondition\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/panelFamilies/WaffleFamily.qml",
    "content": "import QtQuick\nimport Quickshell\n\nimport qs.modules.common\nimport qs.modules.waffle.actionCenter\nimport qs.modules.waffle.background\nimport qs.modules.waffle.bar\nimport qs.modules.waffle.lock\nimport qs.modules.waffle.notificationCenter\nimport qs.modules.waffle.notificationPopup\nimport qs.modules.waffle.onScreenDisplay\n// import qs.modules.waffle.overlay\nimport qs.modules.waffle.polkit\nimport qs.modules.waffle.screenSnip\nimport qs.modules.waffle.startMenu\nimport qs.modules.waffle.sessionScreen\nimport qs.modules.waffle.taskView\n\n// Fallbacks\nimport qs.modules.ii.cheatsheet\nimport qs.modules.ii.onScreenKeyboard\nimport qs.modules.ii.overlay\nimport qs.modules.ii.screenTranslator\nimport qs.modules.ii.wallpaperSelector\n\nScope {\n    PanelLoader { component: WaffleActionCenter {} }\n    PanelLoader { component: WaffleBar {} }\n    PanelLoader { component: WaffleBackground {} }\n    PanelLoader { component: WaffleLock {} }\n    PanelLoader { component: WaffleNotificationCenter {} }\n    PanelLoader { component: WaffleNotificationPopup {} }\n    PanelLoader { component: WaffleOSD {} }\n    // PanelLoader { component: WaffleOverlay {} }\n    PanelLoader { component: WafflePolkit {} }\n    PanelLoader { component: WScreenSnip {} }\n    PanelLoader { component: WaffleStartMenu {} }\n    PanelLoader { component: WaffleSessionScreen {} }\n    PanelLoader { component: WaffleTaskView {} }\n\n    PanelLoader { component: Cheatsheet {} }\n    PanelLoader { component: OnScreenKeyboard {} }\n    PanelLoader { component: Overlay {} }\n    PanelLoader { component: ScreenTranslator {} }\n    PanelLoader { component: WallpaperSelector {} }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/ai/gemini-categorize-wallpaper.sh",
    "content": "#!/usr/bin/env bash\n\nif [[ -z \"$1\" ]]; then\n    echo \"Usage: $0 <image_path> [model] [prompt]\"\n    echo \"Tip: set GEMINI_WALLPAPER_MODEL and/or GEMINI_WALLPAPER_PROMPT to provide defaults.\"\n    exit 1\nfi\n\n# Variables\nSOURCE_IMG_PATH=\"$1\"\nMODEL=\"${2:-${GEMINI_WALLPAPER_MODEL:-gemini-2.5-flash}}\" # We use the flash variant so it's fast\nWALLPAPER_NAME=\"$(basename \"$SOURCE_IMG_PATH\")\"\nPROMPT=\"${3:-${GEMINI_WALLPAPER_PROMPT:-Categorize the wallpaper. Its file name is $WALLPAPER_NAME}}\"\nRESIZED_IMG_PATH=\"/tmp/quickshell/ai/wallpaper.jpg\"\n\n# Resize image for speed\nmkdir -p \"$(dirname \"$RESIZED_IMG_PATH\")\"\nmagick \"$SOURCE_IMG_PATH\" -resize 200x -quality 50 \"$RESIZED_IMG_PATH\"\n\n# Get API key\nAPI_KEY=$(secret-tool lookup 'application' 'illogical-impulse' | jq -r '.apiKeys.gemini')\n\n# Encode image to base64\nif [[ \"$(base64 --version 2>&1)\" = *\"FreeBSD\"* ]]; then\n    B64FLAGS=\"--input\"\nelse\n    B64FLAGS=\"-w0\"\nfi\nB64DATA=\"$(base64 $B64FLAGS $RESIZED_IMG_PATH)\"\n# echo $B64DATA\n\n# Prepare request data\npayload='{\n    \"contents\": [{\n        \"parts\":[\n            {\n                \"inline_data\": {\n                \"mime_type\":\"image/jpeg\",\n                \"data\": \"'\"$B64DATA\"'\"\n                }\n            },\n            {\"text\": \"'\"$PROMPT\"'\"}\n        ]\n    }],\n    \"generationConfig\": {\n        \"responseMimeType\": \"text/x.enum\",\n        \"responseSchema\": {\n            \"type\": \"string\",\n            \"enum\": [ \"abstract\", \"anime\", \"city\", \"minimalist\", \"landscape\", \"plants\", \"person\", \"space\" ]\n        },\n        \"temperature\": 0\n    }\n}'\n# echo \"$payload\" | jq\n\n# Make the request\nresponse=$(curl \"https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent\" \\\n-H \"x-goog-api-key: $API_KEY\" \\\n-H 'Content-Type: application/json' \\\n-X POST \\\n-d \"$payload\" 2> /dev/null)\n# echo \"$response\" | jq\n\n# Write the result\necho \"$response\" | jq -r '.candidates[0].content.parts[0].text'\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/ai/gemini-translate.sh",
    "content": "#!/usr/bin/env bash\n\nif [[ -z \"$1\" ]]; then\n    echo \"Usage: $0 <target_locale> [model]\"\n    exit 1\nfi\n\n# Variables\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nXDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:-$HOME/.config}\"\nSHELL_CONFIG_DIR=\"$XDG_CONFIG_HOME/illogical-impulse\"\nSHELL_CONFIG_FILE=\"${SHELL_CONFIG_DIR}/config.json\"\nTRANSLATIONS_DIR=\"${SCRIPT_DIR}/../../translations\"\nTRANSLATIONS_TARGET_DIR=\"${SHELL_CONFIG_DIR}/translations\"\nSOURCE_LOCALE=\"en_US\"\nNOTIFICATION_APP_NAME=\"Shell\"\nTARGET_LOCALE=\"$1\"\nMODEL=\"${2:-${GEMINI_MODEL:-gemini-2.5-flash}}\"\n\n# Update the source keys for translation\n\"${TRANSLATIONS_DIR}/tools/manage-translations.sh\" update -l \"$SOURCE_LOCALE\" --yes\nmkdir -p \"$TRANSLATIONS_TARGET_DIR\"\n\n# Construct the prompt string\ninstruction='You are to translate the user interface of a **desktop shell**. Given a JSON object of key-value pairs, return a JSON with the same structure, with keys unchanged and values translated to '\"$TARGET_LOCALE\"'. Be as **concise** as possible to save screen space, and make sure terminology is relevant (e.g. \"discharging\" refers to the battery status).'\ncontent=$(cat \"${TRANSLATIONS_DIR}/en_US.json\")\nprompt_json=$(jq -n --arg prompt_text \"$instruction\" --arg content \"$content\" '$prompt_text + \"\\n```\\n\" + $content + \"\\n```\\n\"')\n\n# Prepare request data using jq\npayload=$(jq -n \\\n    --arg prompt \"$prompt_json\" \\\n    --arg temperature \"0\" \\\n    --arg model \"$MODEL\" \\\n    '{\n        contents: [{\n            parts: [\n                {text: $prompt}\n            ]\n        }],\n        generationConfig: {\n            temperature: ($temperature | tonumber),\n            \"responseMimeType\": \"application/json\",\n        }\n    }'\n)\n# echo \"$payload\" | jq\n\n# Get API key\nAPI_KEY=$(secret-tool lookup 'application' 'illogical-impulse' | jq -r '.apiKeys.gemini')\n\n# Notify start\nnotify-send \"Translation started\" \"Will take 2 minutes, and you'll be notified when it's done, so feel free to do something else in the meantime.\" -a \"$NOTIFICATION_APP_NAME\"\n\n# Make the request\nresponse=$(curl \"https://generativelanguage.googleapis.com/v1beta/models/${MODEL}:generateContent\" \\\n-H \"x-goog-api-key: $API_KEY\" \\\n-H 'Content-Type: application/json' \\\n-X POST \\\n-d \"$payload\" 2> /dev/null)\n# echo \"$response\" | jq\n\n# Write the result\necho \"$response\" | jq -r '.candidates[0].content.parts[0].text' > \"${TRANSLATIONS_TARGET_DIR}/${TARGET_LOCALE}.json\"\njq --arg locale \"$TARGET_LOCALE\" '.language.ui = $locale' \"$SHELL_CONFIG_FILE\" > \"${SHELL_CONFIG_FILE}.tmp\" && mv \"${SHELL_CONFIG_FILE}.tmp\" \"$SHELL_CONFIG_FILE\"\nnotify-send \"Translation complete\" \"Enjoy! In case you wanna refine it, the file is in ${TRANSLATIONS_TARGET_DIR}/${TARGET_LOCALE}.json\" -a \"$NOTIFICATION_APP_NAME\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/ai/show-installed-ollama-models.sh",
    "content": "#!/usr/bin/env bash\n\n# Get the list, skip the header, and extract the first column (model names)\nmodel_names=$(ollama list | tail -n +2 | awk '{print $1}')\n\n# Build a JSON array\njson_array=\"[\"\nfor name in $model_names; do\n    json_array+=\"\\\"$name\\\",\"\ndone\n\n# Remove trailing comma and close the array\njson_array=\"${json_array%,}]\"\n\n# Output the JSON array\necho \"$json_array\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/cava/raw_output_config.txt",
    "content": "[general]\nmode = waves\nframerate = 60\nautosens = 1\nbars = 50\n\n[output]\nmethod = raw\nraw_target = /dev/stdout\ndata_format = ascii\nchannels = mono\nmono_option = average\n\n[smoothing]\nnoise_reduction = 20\n\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/applycolor.sh",
    "content": "#!/usr/bin/env bash\n\nQUICKSHELL_CONFIG_NAME=\"ii\"\nXDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:-$HOME/.config}\"\nXDG_CACHE_HOME=\"${XDG_CACHE_HOME:-$HOME/.cache}\"\nXDG_STATE_HOME=\"${XDG_STATE_HOME:-$HOME/.local/state}\"\nCONFIG_DIR=\"$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME\"\nCACHE_DIR=\"$XDG_CACHE_HOME/quickshell\"\nSTATE_DIR=\"$XDG_STATE_HOME/quickshell\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nterm_alpha=100 #Set this to < 100 make all your terminals transparent\n# sleep 0 # idk i wanted some delay or colors dont get applied properly\nif [ ! -d \"$STATE_DIR\"/user/generated ]; then\n  mkdir -p \"$STATE_DIR\"/user/generated\nfi\ncd \"$CONFIG_DIR\" || exit\n\ncolornames=''\ncolorstrings=''\ncolorlist=()\ncolorvalues=()\n\ncolornames=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f1)\ncolorstrings=$(cat $STATE_DIR/user/generated/material_colors.scss | cut -d: -f2 | cut -d ' ' -f2 | cut -d \";\" -f1)\nIFS=$'\\n'\ncolorlist=($colornames)     # Array of color names\ncolorvalues=($colorstrings) # Array of color values\n\napply_kitty() {  \n  # Check if terminal escape sequence template exists\n  if [ ! -f \"$SCRIPT_DIR/terminal/kitty-theme.conf\" ]; then\n    echo \"Template file not found for Kitty theme. Skipping that.\"\n    return\n  fi\n  # Copy template\n  mkdir -p \"$STATE_DIR\"/user/generated/terminal\n  cp \"$SCRIPT_DIR/terminal/kitty-theme.conf\" \"$STATE_DIR\"/user/generated/terminal/kitty-theme.conf\n  # Apply colors\n  for i in \"${!colorlist[@]}\"; do\n    sed -i \"s/${colorlist[$i]} #/${colorvalues[$i]#\\#}/g\" \"$STATE_DIR\"/user/generated/terminal/kitty-theme.conf\n  done\n\n  # Reload\n  if ! pgrep -f kitty >/dev/null; then\n    return\n  fi\n  kill -SIGUSR1 $(pidof kitty)\n}\n\napply_anyterm() {\n  # Check if terminal escape sequence template exists\n  if [ ! -f \"$SCRIPT_DIR/terminal/sequences.txt\" ]; then\n    echo \"Template file not found for Terminal. Skipping that.\"\n    return\n  fi\n  # Copy template\n  mkdir -p \"$STATE_DIR\"/user/generated/terminal\n  cp \"$SCRIPT_DIR/terminal/sequences.txt\" \"$STATE_DIR\"/user/generated/terminal/sequences.txt\n  # Apply colors\n  for i in \"${!colorlist[@]}\"; do\n    sed -i \"s/${colorlist[$i]} #/${colorvalues[$i]#\\#}/g\" \"$STATE_DIR\"/user/generated/terminal/sequences.txt\n  done\n\n  sed -i \"s/\\$alpha/$term_alpha/g\" \"$STATE_DIR/user/generated/terminal/sequences.txt\"\n\n  for file in /dev/pts/*; do\n    if [[ $file =~ ^/dev/pts/[0-9]+$ ]]; then\n      {\n      cat \"$STATE_DIR\"/user/generated/terminal/sequences.txt >\"$file\"\n      } & disown || true\n    fi\n  done\n}\n\napply_term() {\n  apply_anyterm &\n  apply_kitty &\n}\n\n# Check if terminal theming is enabled in config\nCONFIG_FILE=\"$XDG_CONFIG_HOME/illogical-impulse/config.json\"\nif [ -f \"$CONFIG_FILE\" ]; then\n  enable_terminal=$(jq -r '.appearance.wallpaperTheming.enableTerminal' \"$CONFIG_FILE\")\n  if [ \"$enable_terminal\" = \"true\" ]; then\n    apply_term &\n  fi\nelse\n  echo \"Config file not found at $CONFIG_FILE. Applying terminal theming by default.\"\n  apply_term &\nfi\n\n# apply_qt & # Qt theming is already handled by kde-material-colors\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/code/material-code-set-color.sh",
    "content": "#!/usr/bin/env bash\nCOLOR_FILE_PATH=\"${XDG_STATE_HOME:-$HOME/.local/state}/quickshell/user/generated/color.txt\"\n\n# Define an array of possible VSCode settings file paths for various forks\nsettings_paths=(\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/Code/User/settings.json\"\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/VSCodium/User/settings.json\"\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/Code - OSS/User/settings.json\"\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/Code - Insiders/User/settings.json\"\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/Cursor/User/settings.json\"\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/Antigravity/User/settings.json\"\n    \"${XDG_CONFIG_HOME:-$HOME/.config}/Windsurf/User/settings.json\"\n    \n    # Add more paths as needed for other forks\n)\n\nnew_color=$(cat \"$COLOR_FILE_PATH\")\n\n# Loop through each settings file path\nfor CODE_SETTINGS_PATH in \"${settings_paths[@]}\"; do\n    if [[ -f \"$CODE_SETTINGS_PATH\" ]]; then\n        # Try to update the key if it exists\n        if grep -q '\"material-code.primaryColor\"' \"$CODE_SETTINGS_PATH\"; then\n            sed -i -E \\\n                \"s/(\\\"material-code.primaryColor\\\"\\s*:\\s*\\\")[^\\\"]*(\\\")/\\1${new_color}\\2/\" \\\n                \"$CODE_SETTINGS_PATH\"\n        else # If the key is not already there, add it\n            sed -i '$ s/}/,\\n  \"material-code.primaryColor\": \"'${new_color}'\"\\n}/' \"$CODE_SETTINGS_PATH\"\n            sed -i '$ s/,\\n,/,/' \"$CODE_SETTINGS_PATH\"\n        fi\n    fi\ndone\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/generate_colors_material.py",
    "content": "#!/usr/bin/env -S\\_/bin/sh\\_-c\\_\"source\\_\\$(eval\\_echo\\_\\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\\_python\\_-E\\_\"\\$0\"\\_\"\\$@\"\"\nimport argparse\nimport math\nimport json\nfrom PIL import Image\nfrom materialyoucolor.quantize import QuantizeCelebi\nfrom materialyoucolor.score.score import Score\nfrom materialyoucolor.hct import Hct\nfrom materialyoucolor.dynamiccolor.material_dynamic_colors import MaterialDynamicColors\nfrom materialyoucolor.utils.color_utils import (rgba_from_argb, argb_from_rgb, argb_from_rgba)\nfrom materialyoucolor.utils.math_utils import (sanitize_degrees_double, difference_degrees, rotation_direction)\n\nparser = argparse.ArgumentParser(description='Color generation script')\nparser.add_argument('--path', type=str, default=None, help='generate colorscheme from image')\nparser.add_argument('--size', type=int , default=128 , help='bitmap image size')\nparser.add_argument('--color', type=str, default=None, help='generate colorscheme from color')\nparser.add_argument('--mode', type=str, choices=['dark', 'light'], default='dark', help='dark or light mode')\nparser.add_argument('--scheme', type=str, default='vibrant', help='material scheme to use')\nparser.add_argument('--smart', action='store_true', default=False, help='decide scheme type based on image color')\nparser.add_argument('--transparency', type=str, choices=['opaque', 'transparent'], default='opaque', help='enable transparency')\nparser.add_argument('--termscheme', type=str, default=None, help='JSON file containg the terminal scheme for generating term colors')\nparser.add_argument('--harmony', type=float , default=0.8, help='(0-1) Color hue shift towards accent')\nparser.add_argument('--harmonize_threshold', type=float , default=100, help='(0-180) Max threshold angle to limit color hue shift')\nparser.add_argument('--term_fg_boost', type=float , default=0.35, help='Make terminal foreground more different from the background')\nparser.add_argument('--blend_bg_fg', action='store_true', default=False, help='Shift terminal background or foreground towards accent')\nparser.add_argument('--cache', type=str, default=None, help='file path to store the generated color')\nparser.add_argument('--debug', action='store_true', default=False, help='debug mode')\nargs = parser.parse_args()\n\nrgba_to_hex = lambda rgba: \"#{:02X}{:02X}{:02X}\".format(rgba[0], rgba[1], rgba[2])\nargb_to_hex = lambda argb: \"#{:02X}{:02X}{:02X}\".format(*map(round, rgba_from_argb(argb)))\nhex_to_argb = lambda hex_code: argb_from_rgb(int(hex_code[1:3], 16), int(hex_code[3:5], 16), int(hex_code[5:], 16))\ndisplay_color = lambda rgba : \"\\x1B[38;2;{};{};{}m{}\\x1B[0m\".format(rgba[0], rgba[1], rgba[2], \"\\x1b[7m   \\x1b[7m\")\n\ndef calculate_optimal_size (width: int, height: int, bitmap_size: int) -> (int, int):\n    image_area = width * height;\n    bitmap_area = bitmap_size ** 2\n    scale = math.sqrt(bitmap_area/image_area) if image_area > bitmap_area else 1\n    new_width = round(width * scale)\n    new_height = round(height * scale)\n    if new_width == 0:\n        new_width = 1\n    if new_height == 0:\n        new_height = 1\n    return new_width, new_height\n\ndef harmonize (design_color: int, source_color: int, threshold: float = 35, harmony: float = 0.5) -> int:\n    from_hct = Hct.from_int(design_color)\n    to_hct = Hct.from_int(source_color)\n    difference_degrees_ = difference_degrees(from_hct.hue, to_hct.hue)\n    rotation_degrees = min(difference_degrees_ * harmony, threshold)\n    output_hue = sanitize_degrees_double(\n        from_hct.hue + rotation_degrees * rotation_direction(from_hct.hue, to_hct.hue)\n    )\n    return Hct.from_hct(output_hue, from_hct.chroma, from_hct.tone).to_int()\n\ndef boost_chroma_tone (argb: int, chroma: float = 1, tone: float = 1) -> int:\n    hct = Hct.from_int(argb)\n    return Hct.from_hct(hct.hue, hct.chroma * chroma, hct.tone * tone).to_int()\n\ndarkmode = (args.mode == 'dark')\ntransparent = (args.transparency == 'transparent')\n\nif args.path is not None:\n    image = Image.open(args.path)\n\n    if image.format == \"GIF\":\n        image.seek(1)\n\n    if image.mode in [\"L\", \"P\"]:\n        image = image.convert('RGB')\n    wsize, hsize = image.size\n    wsize_new, hsize_new = calculate_optimal_size(wsize, hsize, args.size)\n    if wsize_new < wsize or hsize_new < hsize:\n        image = image.resize((wsize_new, hsize_new), Image.Resampling.BICUBIC)\n    colors = QuantizeCelebi(list(image.getdata()), 128)\n    argb = Score.score(colors)[0]\n\n    if args.cache is not None:\n        with open(args.cache, 'w') as file:\n            file.write(argb_to_hex(argb))\n    hct = Hct.from_int(argb)\n    if(args.smart):\n        if(hct.chroma < 20):\n            args.scheme = 'neutral'\nelif args.color is not None:\n    argb = hex_to_argb(args.color)\n    hct = Hct.from_int(argb)\n\nif args.scheme == 'scheme-fruit-salad':\n    from materialyoucolor.scheme.scheme_fruit_salad import SchemeFruitSalad as Scheme\nelif args.scheme == 'scheme-expressive':\n    from materialyoucolor.scheme.scheme_expressive import SchemeExpressive as Scheme\nelif args.scheme == 'scheme-monochrome':\n    from materialyoucolor.scheme.scheme_monochrome import SchemeMonochrome as Scheme\nelif args.scheme == 'scheme-rainbow':\n    from materialyoucolor.scheme.scheme_rainbow import SchemeRainbow as Scheme\nelif args.scheme == 'scheme-tonal-spot':\n    from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme\nelif args.scheme == 'scheme-neutral':\n    from materialyoucolor.scheme.scheme_neutral import SchemeNeutral as Scheme\nelif args.scheme == 'scheme-fidelity':\n    from materialyoucolor.scheme.scheme_fidelity import SchemeFidelity as Scheme\nelif args.scheme == 'scheme-content':\n    from materialyoucolor.scheme.scheme_content import SchemeContent as Scheme\nelif args.scheme == 'scheme-vibrant':\n    from materialyoucolor.scheme.scheme_vibrant import SchemeVibrant as Scheme\nelse:\n    from materialyoucolor.scheme.scheme_tonal_spot import SchemeTonalSpot as Scheme\n# Generate\nscheme = Scheme(hct, darkmode, 0.0)\n\nmaterial_colors = {}\nterm_colors = {}\n\nfor color in vars(MaterialDynamicColors).keys():\n    color_name = getattr(MaterialDynamicColors, color)\n    if hasattr(color_name, \"get_hct\"):\n        rgba = color_name.get_hct(scheme).to_rgba()\n        material_colors[color] = rgba_to_hex(rgba)\n\n# Extended material\nif darkmode == True:\n    material_colors['success'] = '#B5CCBA'\n    material_colors['onSuccess'] = '#213528'\n    material_colors['successContainer'] = '#374B3E'\n    material_colors['onSuccessContainer'] = '#D1E9D6'\nelse:\n    material_colors['success'] = '#4F6354'\n    material_colors['onSuccess'] = '#FFFFFF'\n    material_colors['successContainer'] = '#D1E8D5'\n    material_colors['onSuccessContainer'] = '#0C1F13'\n\n# Terminal Colors\nif args.termscheme is not None:\n    with open(args.termscheme, 'r') as f:\n        json_termscheme = f.read()\n    term_source_colors = json.loads(json_termscheme)['dark' if darkmode else 'light']\n\n    primary_color_argb = hex_to_argb(material_colors['primary_paletteKeyColor'])\n    for color, val in term_source_colors.items():\n        if(args.scheme == 'monochrome') :\n            term_colors[color] = val\n            continue\n        if args.blend_bg_fg and color == \"term0\":\n            harmonized = boost_chroma_tone(hex_to_argb(material_colors['surfaceContainerLow']), 1.2, 0.95)\n        elif args.blend_bg_fg and color == \"term15\":\n            harmonized = boost_chroma_tone(hex_to_argb(material_colors['onSurface']), 3, 1)\n        else:\n            harmonized = harmonize(hex_to_argb(val), primary_color_argb, args.harmonize_threshold, args.harmony)\n            harmonized = boost_chroma_tone(harmonized, 1, 1 + (args.term_fg_boost * (1 if darkmode else -1)))\n        term_colors[color] = argb_to_hex(harmonized)\n\nif args.debug == False:\n    print(f\"$darkmode: {darkmode};\")\n    print(f\"$transparent: {transparent};\")\n    for color, code in material_colors.items():\n        print(f\"${color}: {code};\")\n    for color, code in term_colors.items():\n        print(f\"${color}: {code};\")\nelse:\n    if args.path is not None:\n        print('\\n--------------Image properties-----------------')\n        print(f\"Image size: {wsize} x {hsize}\")\n        print(f\"Resized image: {wsize_new} x {hsize_new}\")\n    print('\\n---------------Selected color------------------')\n    print(f\"Dark mode: {darkmode}\")\n    print(f\"Scheme: {args.scheme}\")\n    print(f\"Accent color: {display_color(rgba_from_argb(argb))} {argb_to_hex(argb)}\")\n    print(f\"HCT: {hct.hue:.2f}  {hct.chroma:.2f}  {hct.tone:.2f}\")\n    print('\\n---------------Material colors-----------------')\n    for color, code in material_colors.items():\n        rgba = rgba_from_argb(hex_to_argb(code))\n        print(f\"{color.ljust(32)} : {display_color(rgba)}  {code}\")\n    print('\\n----------Harmonize terminal colors------------')\n    for color, code in term_colors.items():\n        rgba = rgba_from_argb(hex_to_argb(code))\n        code_source = term_source_colors[color]\n        rgba_source = rgba_from_argb(hex_to_argb(code_source))\n        print(f\"{color.ljust(6)} : {display_color(rgba_source)} {code_source} --> {display_color(rgba)} {code}\")\n    print('-----------------------------------------------')\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/random/random_konachan_wall.sh",
    "content": "#!/usr/bin/env bash\n\nget_pictures_dir() {\n    if command -v xdg-user-dir &> /dev/null; then\n        xdg-user-dir PICTURES\n        return\n    fi\n\n    local config_file=\"${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs\"\n    if [ -f \"$config_file\" ]; then\n        local pictures_path\n        pictures_path=$(source \"$config_file\" >/dev/null 2>&1; echo \"$XDG_PICTURES_DIR\")\n        echo \"${pictures_path/#\\$HOME/$HOME}\"\n        return\n    fi\n\n    echo \"$HOME/Pictures\"\n}\n\nQUICKSHELL_CONFIG_NAME=\"ii\"\nXDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:-$HOME/.config}\"\nXDG_CACHE_HOME=\"${XDG_CACHE_HOME:-$HOME/.cache}\"\nXDG_STATE_HOME=\"${XDG_STATE_HOME:-$HOME/.local/state}\"\nPICTURES_DIR=$(get_pictures_dir)\nCONFIG_DIR=\"$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME\"\nCACHE_DIR=\"$XDG_CACHE_HOME/quickshell\"\nSTATE_DIR=\"$XDG_STATE_HOME/quickshell\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nmkdir -p \"$PICTURES_DIR/Wallpapers\"\npage=$((1 + RANDOM % 1000));\nresponse=$(curl \"https://konachan.net/post.json?tags=rating%3Asafe&limit=1&page=$page\")\nlink=$(echo \"$response\" | jq '.[0].file_url' -r);\next=$(echo \"$link\" | awk -F. '{print $NF}')\ndownloadPath=\"$PICTURES_DIR/Wallpapers/random_wallpaper.$ext\"\nillogicalImpulseConfigPath=\"$HOME/.config/illogical-impulse/config.json\"\ncurrentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath)\nif [ \"$downloadPath\" == \"$currentWallpaperPath\" ]; then\n    downloadPath=\"$PICTURES_DIR/Wallpapers/random_wallpaper-1.$ext\"\nfi\ncurl \"$link\" -o \"$downloadPath\"\n\"$SCRIPT_DIR/../switchwall.sh\" --image \"$downloadPath\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/random/random_osu_wall.sh",
    "content": "#!/usr/bin/env bash\n\nget_pictures_dir() {\n    if command -v xdg-user-dir &> /dev/null; then\n        xdg-user-dir PICTURES\n        return\n    fi\n\n    local config_file=\"${XDG_CONFIG_HOME:-$HOME/.config}/user-dirs.dirs\"\n    if [ -f \"$config_file\" ]; then\n        local pictures_path\n        pictures_path=$(source \"$config_file\" >/dev/null 2>&1; echo \"$XDG_PICTURES_DIR\")\n        echo \"${pictures_path/#\\$HOME/$HOME}\"\n        return\n    fi\n\n    echo \"$HOME/Pictures\"\n}\n\nQUICKSHELL_CONFIG_NAME=\"ii\"\nXDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:-$HOME/.config}\"\nXDG_CACHE_HOME=\"${XDG_CACHE_HOME:-$HOME/.cache}\"\nXDG_STATE_HOME=\"${XDG_STATE_HOME:-$HOME/.local/state}\"\nPICTURES_DIR=$(get_pictures_dir)\nCONFIG_DIR=\"$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME\"\nCACHE_DIR=\"$XDG_CACHE_HOME/quickshell\"\nSTATE_DIR=\"$XDG_STATE_HOME/quickshell\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nmkdir -p \"$PICTURES_DIR/Wallpapers\"\n\nresponse=$(curl \"https://osu.ppy.sh/api/v2/seasonal-backgrounds\")\nimages=$(echo \"$response\" | jq '.backgrounds | length' -r);\nrandomIndex=$((RANDOM % images));\nlink=$(echo \"$response\" | jq \".backgrounds[$randomIndex].url\" -r)\next=$(echo \"$link\" | awk -F. '{print $NF}')\ndownloadPath=\"$PICTURES_DIR/Wallpapers/random_wallpaper.$ext\"\nillogicalImpulseConfigPath=\"$HOME/.config/illogical-impulse/config.json\"\ncurrentWallpaperPath=$(jq -r '.background.wallpaperPath' $illogicalImpulseConfigPath)\nif [ \"$downloadPath\" == \"$currentWallpaperPath\" ]; then\n    downloadPath=\"$PICTURES_DIR/Wallpapers/random_wallpaper-1.$ext\"\nfi\ncurl \"$link\" -o \"$downloadPath\"\n\"$SCRIPT_DIR/../switchwall.sh\" --image \"$downloadPath\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/scheme_for_image.py",
    "content": "#!/usr/bin/env python3\nimport sys\nimport cv2\nimport numpy as np\n\n# Allowed scheme types\nSCHEMES = [\n    \"scheme-content\",\n    \"scheme-expressive\",\n    \"scheme-fidelity\",\n    \"scheme-fruit-salad\",\n    \"scheme-monochrome\",\n    \"scheme-neutral\",\n    \"scheme-rainbow\",\n    \"scheme-tonal-spot\"\n]\n\ndef image_colorfulness(image):\n    # Based on Hasler and Süsstrunk's colorfulness metric\n    (B, G, R) = cv2.split(image.astype(\"float\"))\n    rg = np.absolute(R - G)\n    yb = np.absolute(0.5 * (R + G) - B)\n    std_rg = np.std(rg)\n    std_yb = np.std(yb)\n    mean_rg = np.mean(rg)\n    mean_yb = np.mean(yb)\n    colorfulness = np.sqrt(std_rg ** 2 + std_yb ** 2) + (0.3 * np.sqrt(mean_rg ** 2 + mean_yb ** 2))\n    return colorfulness\n\n# scheme-content respects the image's colors very well, but it might\n# look too saturated, so we only use it for not very colorful images to be safe\ndef pick_scheme(colorfulness):\n    if colorfulness < 40:\n        return \"scheme-neutral\"\n    else:\n        return \"scheme-tonal-spot\"\n\ndef load_and_resize_image(img_path, max_dim=128):\n    img = cv2.imread(img_path)\n    if img is None:\n        return None\n    h, w = img.shape[:2]\n    if max(h, w) > max_dim:\n        scale = max_dim / max(h, w)\n        img = cv2.resize(img, (int(w * scale), int(h * scale)), interpolation=cv2.INTER_AREA)\n    return img\n\ndef main():\n    colorfulness_mode = False\n    args = sys.argv[1:]\n    if '--colorfulness' in args:\n        colorfulness_mode = True\n        args.remove('--colorfulness')\n    if len(args) < 1:\n        print(\"scheme-tonal-spot\")\n        sys.exit(1)\n    img_path = args[0]\n    img = load_and_resize_image(img_path)\n    if img is None:\n        print(\"scheme-tonal-spot\")\n        sys.exit(1)\n    colorfulness = image_colorfulness(img)\n    if colorfulness_mode:\n        print(f\"{colorfulness}\")\n    else:\n        scheme = pick_scheme(colorfulness)\n        print(scheme)\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/switchwall.sh",
    "content": "#!/usr/bin/env bash\n\nQUICKSHELL_CONFIG_NAME=\"ii\"\nXDG_CONFIG_HOME=\"${XDG_CONFIG_HOME:-$HOME/.config}\"\nXDG_CACHE_HOME=\"${XDG_CACHE_HOME:-$HOME/.cache}\"\nXDG_STATE_HOME=\"${XDG_STATE_HOME:-$HOME/.local/state}\"\nCONFIG_DIR=\"$XDG_CONFIG_HOME/quickshell/$QUICKSHELL_CONFIG_NAME\"\nCACHE_DIR=\"$XDG_CACHE_HOME/quickshell\"\nSTATE_DIR=\"$XDG_STATE_HOME/quickshell\"\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSHELL_CONFIG_FILE=\"$XDG_CONFIG_HOME/illogical-impulse/config.json\"\nMATUGEN_DIR=\"$XDG_CONFIG_HOME/matugen\"\nterminalscheme=\"$SCRIPT_DIR/terminal/scheme-base.json\"\n\nhandle_kde_material_you_colors() {\n    # Check if Qt app theming is enabled in config\n    if [ -f \"$SHELL_CONFIG_FILE\" ]; then\n        enable_qt_apps=$(jq -r '.appearance.wallpaperTheming.enableQtApps' \"$SHELL_CONFIG_FILE\")\n        if [ \"$enable_qt_apps\" == \"false\" ]; then\n            return\n        fi\n    fi\n\n    # Map $type_flag to allowed scheme variants for kde-material-you-colors-wrapper.sh\n    local kde_scheme_variant=\"\"\n    case \"$type_flag\" in\n        scheme-content|scheme-expressive|scheme-fidelity|scheme-fruit-salad|scheme-monochrome|scheme-neutral|scheme-rainbow|scheme-tonal-spot)\n            kde_scheme_variant=\"$type_flag\"\n            ;;\n        *)\n            kde_scheme_variant=\"scheme-tonal-spot\" # default\n            ;;\n    esac\n    \"$XDG_CONFIG_HOME\"/matugen/templates/kde/kde-material-you-colors-wrapper.sh --scheme-variant \"$kde_scheme_variant\"\n}\n\npre_process() {\n    local mode_flag=\"$1\"\n    # Set GNOME color-scheme if mode_flag is dark or light\n    if [[ \"$mode_flag\" == \"dark\" ]]; then\n        gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'\n        gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3-dark'\n    elif [[ \"$mode_flag\" == \"light\" ]]; then\n        gsettings set org.gnome.desktop.interface color-scheme 'prefer-light'\n        gsettings set org.gnome.desktop.interface gtk-theme 'adw-gtk3'\n    fi\n\n    if [ ! -d \"$CACHE_DIR\"/user/generated ]; then\n        mkdir -p \"$CACHE_DIR\"/user/generated\n    fi\n}\n\npost_process() {\n    local screen_width=\"$1\"\n    local screen_height=\"$2\"\n    local wallpaper_path=\"$3\"\n\n    handle_kde_material_you_colors &\n    \"$SCRIPT_DIR/code/material-code-set-color.sh\" &\n}\n\ncheck_and_prompt_upscale() {\n    local img=\"$1\"\n    min_width_desired=\"$(hyprctl monitors -j | jq '([.[].width] | max)' | xargs)\" # max monitor width\n    min_height_desired=\"$(hyprctl monitors -j | jq '([.[].height] | max)' | xargs)\" # max monitor height\n\n    if command -v identify &>/dev/null && [ -f \"$img\" ]; then\n        local img_width img_height\n        if is_video \"$img\"; then # Not check resolution for videos, just let em pass\n            img_width=$min_width_desired\n            img_height=$min_height_desired\n        else\n            img_width=$(identify -format \"%w\" \"$img\" 2>/dev/null)\n            img_height=$(identify -format \"%h\" \"$img\" 2>/dev/null)\n        fi\n        if [[ \"$img_width\" -lt \"$min_width_desired\" || \"$img_height\" -lt \"$min_height_desired\" ]]; then\n            action=$(notify-send \"Upscale?\" \\\n                \"Image resolution (${img_width}x${img_height}) is lower than screen resolution (${min_width_desired}x${min_height_desired})\" \\\n                -A \"open_upscayl=Open Upscayl\"\\\n                -a \"Wallpaper switcher\")\n            if [[ \"$action\" == \"open_upscayl\" ]]; then\n                if command -v upscayl &>/dev/null; then\n                    nohup upscayl > /dev/null 2>&1 &\n                else\n                    action2=$(notify-send \\\n                        -a \"Wallpaper switcher\" \\\n                        -c \"im.error\" \\\n                        -A \"install_upscayl=Install Upscayl (Arch)\" \\\n                        \"Install Upscayl?\" \\\n                        \"yay -S upscayl-bin\")\n                    if [[ \"$action2\" == \"install_upscayl\" ]]; then\n                        kitty -1 yay -S upscayl-bin\n                        if command -v upscayl &>/dev/null; then\n                            nohup upscayl > /dev/null 2>&1 &\n                        fi\n                    fi\n                fi\n            fi\n        fi\n    fi\n}\n\nCUSTOM_DIR=\"$XDG_CONFIG_HOME/hypr/custom\"\nRESTORE_SCRIPT_DIR=\"$CUSTOM_DIR/scripts\"\nRESTORE_SCRIPT=\"$RESTORE_SCRIPT_DIR/__restore_video_wallpaper.sh\"\nTHUMBNAIL_DIR=\"$RESTORE_SCRIPT_DIR/mpvpaper_thumbnails\"\nVIDEO_OPTS=\"no-audio loop hwdec=auto scale=bilinear interpolation=no video-sync=display-resample panscan=1.0 video-scale-x=1.0 video-scale-y=1.0 video-align-x=0.5 video-align-y=0.5 load-scripts=no\"\n\nis_video() {\n    local extension=\"${1##*.}\"\n    [[ \"$extension\" == \"mp4\" || \"$extension\" == \"webm\" || \"$extension\" == \"mkv\" || \"$extension\" == \"avi\" || \"$extension\" == \"mov\" ]] && return 0 || return 1\n}\n\nkill_existing_mpvpaper() {\n    pkill -f -9 mpvpaper || true\n}\n\ncreate_restore_script() {\n    local video_path=$1\n    cat > \"$RESTORE_SCRIPT.tmp\" << EOF\n#!/bin/bash\n# Generated by switchwall.sh - Don't modify it by yourself.\n# Time: $(date)\n\npkill -f -9 mpvpaper\n\nfor monitor in \\$(hyprctl monitors -j | jq -r '.[] | .name'); do\n    mpvpaper -o \"$VIDEO_OPTS\" \"\\$monitor\" \"$video_path\" &\n    sleep 0.1\ndone\nEOF\n    mv \"$RESTORE_SCRIPT.tmp\" \"$RESTORE_SCRIPT\"\n    chmod +x \"$RESTORE_SCRIPT\"\n}\n\nremove_restore() {\n    cat > \"$RESTORE_SCRIPT.tmp\" << EOF\n#!/bin/bash\n# The content of this script will be generated by switchwall.sh - Don't modify it by yourself.\nEOF\n    mv \"$RESTORE_SCRIPT.tmp\" \"$RESTORE_SCRIPT\"\n}\n\nset_wallpaper_path() {\n    local path=\"$1\"\n    if [ -f \"$SHELL_CONFIG_FILE\" ]; then\n        jq --arg path \"$path\" '.background.wallpaperPath = $path' \"$SHELL_CONFIG_FILE\" > \"$SHELL_CONFIG_FILE.tmp\" && mv \"$SHELL_CONFIG_FILE.tmp\" \"$SHELL_CONFIG_FILE\"\n    fi\n}\n\nset_thumbnail_path() {\n    local path=\"$1\"\n    if [ -f \"$SHELL_CONFIG_FILE\" ]; then\n        jq --arg path \"$path\" '.background.thumbnailPath = $path' \"$SHELL_CONFIG_FILE\" > \"$SHELL_CONFIG_FILE.tmp\" && mv \"$SHELL_CONFIG_FILE.tmp\" \"$SHELL_CONFIG_FILE\"\n    fi\n}\n\ncategorize_wallpaper() {\n    img_cat=$(\"$SCRIPT_DIR/../ai/gemini-categorize-wallpaper.sh\" \"$1\")\n    # notify-send \"Wallpaper category\" \"$img_cat\"\n    echo \"$img_cat\" > \"$STATE_DIR/user/generated/wallpaper/category.txt\"\n}\n\nswitch() {\n    imgpath=\"$1\"\n    mode_flag=\"$2\"\n    type_flag=\"$3\"\n    color_flag=\"$4\"\n    color=\"$5\"\n\n    # Start Gemini auto-categorization if enabled\n    aiStylingEnabled=$(jq -r '.background.widgets.clock.cookie.aiStyling' \"$SHELL_CONFIG_FILE\")\n    if [[ \"$aiStylingEnabled\" == \"true\" ]]; then\n        categorize_wallpaper \"$imgpath\" &\n    fi\n\n    read scale screenx screeny screensizey < <(hyprctl monitors -j | jq '.[] | select(.focused) | .scale, .x, .y, .height' | xargs)\n    cursorposx=$(hyprctl cursorpos -j | jq '.x' 2>/dev/null) || cursorposx=960\n    cursorposx=$(bc <<< \"scale=0; ($cursorposx - $screenx) * $scale / 1\")\n    cursorposy=$(hyprctl cursorpos -j | jq '.y' 2>/dev/null) || cursorposy=540\n    cursorposy=$(bc <<< \"scale=0; ($cursorposy - $screeny) * $scale / 1\")\n    cursorposy_inverted=$((screensizey - cursorposy))\n\n    matugen_args=(--source-color-index 0)\n\n    if [[ \"$color_flag\" == \"1\" ]]; then\n        matugen_args+=(color hex \"$color\")\n        generate_colors_material_args=(--color \"$color\")\n    else\n        if [[ -z \"$imgpath\" ]]; then\n            echo 'Aborted'\n            exit 0\n        fi\n\n        check_and_prompt_upscale \"$imgpath\" &\n        kill_existing_mpvpaper\n\n        if is_video \"$imgpath\"; then\n            mkdir -p \"$THUMBNAIL_DIR\"\n\n            missing_deps=()\n            if ! command -v mpvpaper &> /dev/null; then\n                missing_deps+=(\"mpvpaper\")\n            fi\n            if ! command -v ffmpeg &> /dev/null; then\n                missing_deps+=(\"ffmpeg\")\n            fi\n            if [ ${#missing_deps[@]} -gt 0 ]; then\n                echo \"Missing deps: ${missing_deps[*]}\"\n                echo \"Arch: sudo pacman -S ${missing_deps[*]}\"\n                action=$(notify-send \\\n                    -a \"Wallpaper switcher\" \\\n                    -c \"im.error\" \\\n                    -A \"install_arch=Install (Arch)\" \\\n                    \"Can't switch to video wallpaper\" \\\n                    \"Missing dependencies: ${missing_deps[*]}\")\n                if [[ \"$action\" == \"install_arch\" ]]; then\n                    kitty -1 sudo pacman -S \"${missing_deps[*]}\"\n                    if command -v mpvpaper &>/dev/null && command -v ffmpeg &>/dev/null; then\n                        notify-send 'Wallpaper switcher' 'Alright, try again!' -a \"Wallpaper switcher\"\n                    fi\n                fi\n                exit 0\n            fi\n\n            # Set wallpaper path\n            set_wallpaper_path \"$imgpath\"\n\n            # Set video wallpaper\n            local video_path=\"$imgpath\"\n            monitors=$(hyprctl monitors -j | jq -r '.[] | .name')\n            for monitor in $monitors; do\n                mpvpaper -o \"$VIDEO_OPTS\" \"$monitor\" \"$video_path\" &\n                sleep 0.1\n            done\n\n            # Extract first frame for color generation\n            thumbnail=\"$THUMBNAIL_DIR/$(basename \"$imgpath\").jpg\"\n            ffmpeg -y -i \"$imgpath\" -vframes 1 \"$thumbnail\" 2>/dev/null\n\n            # Set thumbnail path\n            set_thumbnail_path \"$thumbnail\"\n\n            if [ -f \"$thumbnail\" ]; then\n                matugen_args+=(image \"$thumbnail\")\n                generate_colors_material_args=(--path \"$thumbnail\")\n                create_restore_script \"$video_path\"\n            else\n                echo \"Cannot create image to colorgen\"\n                remove_restore\n                exit 1\n            fi\n        else\n            matugen_args+=(image \"$imgpath\")\n            generate_colors_material_args=(--path \"$imgpath\")\n            # Update wallpaper path in config\n            set_wallpaper_path \"$imgpath\"\n            remove_restore\n        fi\n    fi\n\n    # Determine mode if not set\n    if [[ -z \"$mode_flag\" ]]; then\n        current_mode=$(gsettings get org.gnome.desktop.interface color-scheme 2>/dev/null | tr -d \"'\")\n        if [[ \"$current_mode\" == \"prefer-dark\" ]]; then\n            mode_flag=\"dark\"\n        else\n            mode_flag=\"light\"\n        fi\n    fi\n\n    # enforce dark mode for terminal\n    if [[ -n \"$mode_flag\" ]]; then\n        matugen_args+=(--mode \"$mode_flag\")\n        if [[ $(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.forceDarkMode' \"$SHELL_CONFIG_FILE\") == \"true\" ]]; then\n            generate_colors_material_args+=(--mode \"dark\")\n        else\n            generate_colors_material_args+=(--mode \"$mode_flag\")\n        fi\n    fi\n    [[ -n \"$type_flag\" ]] && matugen_args+=(--type \"$type_flag\") && generate_colors_material_args+=(--scheme \"$type_flag\")\n    generate_colors_material_args+=(--termscheme \"$terminalscheme\" --blend_bg_fg)\n    generate_colors_material_args+=(--cache \"$STATE_DIR/user/generated/color.txt\")\n\n    pre_process \"$mode_flag\"\n\n    # Check if app and shell theming is enabled in config\n    if [ -f \"$SHELL_CONFIG_FILE\" ]; then\n        enable_apps_shell=$(jq -r '.appearance.wallpaperTheming.enableAppsAndShell' \"$SHELL_CONFIG_FILE\")\n        if [ \"$enable_apps_shell\" == \"false\" ]; then\n            echo \"App and shell theming disabled, skipping matugen and color generation\"\n            return\n        fi\n    fi\n\n    # Set harmony and related properties\n    if [ -f \"$SHELL_CONFIG_FILE\" ]; then\n        harmony=$(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.harmony' \"$SHELL_CONFIG_FILE\")\n        harmonize_threshold=$(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.harmonizeThreshold' \"$SHELL_CONFIG_FILE\")\n        term_fg_boost=$(jq -r '.appearance.wallpaperTheming.terminalGenerationProps.termFgBoost' \"$SHELL_CONFIG_FILE\")\n        [[ \"$harmony\" != \"null\" && -n \"$harmony\" ]] && generate_colors_material_args+=(--harmony \"$harmony\")\n        [[ \"$harmonize_threshold\" != \"null\" && -n \"$harmonize_threshold\" ]] && generate_colors_material_args+=(--harmonize_threshold \"$harmonize_threshold\")\n        [[ \"$term_fg_boost\" != \"null\" && -n \"$term_fg_boost\" ]] && generate_colors_material_args+=(--term_fg_boost \"$term_fg_boost\")\n    fi\n\n    matugen \"${matugen_args[@]}\"\n    source \"$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\"\n    python3 \"$SCRIPT_DIR/generate_colors_material.py\" \"${generate_colors_material_args[@]}\" \\\n        > \"$STATE_DIR\"/user/generated/material_colors.scss\n    deactivate\n    \"$SCRIPT_DIR\"/applycolor.sh\n\n    # Pass screen width, height, and wallpaper path to post_process\n    max_width_desired=\"$(hyprctl monitors -j | jq '([.[].width] | min)' | xargs)\"\n    max_height_desired=\"$(hyprctl monitors -j | jq '([.[].height] | min)' | xargs)\"\n    post_process \"$max_width_desired\" \"$max_height_desired\" \"$imgpath\"\n}\n\nmain() {\n    imgpath=\"\"\n    mode_flag=\"\"\n    type_flag=\"\"\n    color_flag=\"\"\n    color=\"\"\n    noswitch_flag=\"\"\n\n    get_type_from_config() {\n        jq -r '.appearance.palette.type' \"$SHELL_CONFIG_FILE\" 2>/dev/null || echo \"auto\"\n    }\n    get_accent_color_from_config() {\n        jq -r '.appearance.palette.accentColor' \"$SHELL_CONFIG_FILE\" 2>/dev/null || echo \"\"\n    }\n    set_accent_color() {\n        local color=\"$1\"\n        jq --arg color \"$color\" '.appearance.palette.accentColor = $color' \"$SHELL_CONFIG_FILE\" > \"$SHELL_CONFIG_FILE.tmp\" && mv \"$SHELL_CONFIG_FILE.tmp\" \"$SHELL_CONFIG_FILE\"\n    }\n\n    detect_scheme_type_from_image() {\n        local img=\"$1\"\n        source \"$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\"\n        \"$SCRIPT_DIR\"/scheme_for_image.py \"$img\" 2>/dev/null | tr -d '\\n'\n        deactivate\n    }\n\n    while [[ $# -gt 0 ]]; do\n        case \"$1\" in\n            --mode)\n                mode_flag=\"$2\"\n                shift 2\n                ;;\n            --type)\n                type_flag=\"$2\"\n                shift 2\n                ;;\n            --color)\n                if [[ \"$2\" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then\n                    set_accent_color \"$2\"\n                    shift 2\n                elif [[ \"$2\" == \"clear\" ]]; then\n                    set_accent_color \"\"\n                    shift 2\n                else\n                    set_accent_color $(hyprpicker --no-fancy)\n                    shift\n                fi\n                ;;\n            --image)\n                imgpath=\"$2\"\n                shift 2\n                ;;\n            --noswitch)\n                noswitch_flag=\"1\"\n                imgpath=$(jq -r '.background.wallpaperPath' \"$SHELL_CONFIG_FILE\" 2>/dev/null || echo \"\")\n                shift\n                ;;\n            *)\n                if [[ -z \"$imgpath\" ]]; then\n                    imgpath=\"$1\"\n                fi\n                shift\n                ;;\n        esac\n    done\n\n    # If accentColor is set in config, use it\n    config_color=\"$(get_accent_color_from_config)\"\n    if [[ \"$config_color\" =~ ^#?[A-Fa-f0-9]{6}$ ]]; then\n        color_flag=\"1\"\n        color=\"$config_color\"\n    fi\n\n    # If type_flag is not set, get it from config\n    if [[ -z \"$type_flag\" ]]; then\n        type_flag=\"$(get_type_from_config)\"\n    fi\n\n    # Validate type_flag (allow 'auto' as well)\n    allowed_types=(scheme-content scheme-expressive scheme-fidelity scheme-fruit-salad scheme-monochrome scheme-neutral scheme-rainbow scheme-tonal-spot auto)\n    valid_type=0\n    for t in \"${allowed_types[@]}\"; do\n        if [[ \"$type_flag\" == \"$t\" ]]; then\n            valid_type=1\n            break\n        fi\n    done\n    if [[ $valid_type -eq 0 ]]; then\n        echo \"[switchwall.sh] Warning: Invalid type '$type_flag', defaulting to 'auto'\" >&2\n        type_flag=\"auto\"\n    fi\n\n    # Only prompt for wallpaper if not using --color and not using --noswitch and no imgpath set\n    if [[ -z \"$imgpath\" && -z \"$color_flag\" && -z \"$noswitch_flag\" ]]; then\n        cd \"$(xdg-user-dir PICTURES)/Wallpapers/showcase\" 2>/dev/null || cd \"$(xdg-user-dir PICTURES)/Wallpapers\" 2>/dev/null || cd \"$(xdg-user-dir PICTURES)\" || return 1\n        imgpath=\"$(kdialog --getopenfilename . --title 'Choose wallpaper')\"\n    fi\n\n    if [[ -n \"$imgpath\" && -z \"$noswitch_flag\" ]]; then\n        set_accent_color \"\"\n        color_flag=\"\"\n        color=\"\"\n    fi\n\n    # If type_flag is 'auto', detect scheme type from image (after imgpath is set)\n    if [[ \"$type_flag\" == \"auto\" ]]; then\n        if [[ -n \"$imgpath\" && -f \"$imgpath\" ]]; then\n            detected_type=\"$(detect_scheme_type_from_image \"$imgpath\")\"\n            # Only use detected_type if it's valid\n            valid_detected=0\n            for t in \"${allowed_types[@]}\"; do\n                if [[ \"$detected_type\" == \"$t\" && \"$detected_type\" != \"auto\" ]]; then\n                    valid_detected=1\n                    break\n                fi\n            done\n            if [[ $valid_detected -eq 1 ]]; then\n                type_flag=\"$detected_type\"\n            else\n                echo \"[switchwall] Warning: Could not auto-detect a valid scheme, defaulting to 'scheme-tonal-spot'\" >&2\n                type_flag=\"scheme-tonal-spot\"\n            fi\n        else\n            echo \"[switchwall] Warning: No image to auto-detect scheme from, defaulting to 'scheme-tonal-spot'\" >&2\n            type_flag=\"scheme-tonal-spot\"\n        fi\n    fi\n\n    switch \"$imgpath\" \"$mode_flag\" \"$type_flag\" \"$color_flag\" \"$color\"\n}\n\nmain \"$@\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/terminal/kitty-theme.conf",
    "content": "background            #$term0 #\n\ncolor0                #$term0 #\ncolor1                #$term1 #\ncolor2                #$term2 #\ncolor3                #$term3 #\ncolor4                #$term4 #\ncolor5                #$term5 #\ncolor6                #$term6 #\ncolor7                #$term7 #\ncolor8                #$term8 #\ncolor9                #$term9 #\ncolor10               #$term10 #\ncolor11               #$term11 #\ncolor12               #$term12 #\ncolor13               #$term13 #\ncolor14               #$term14 #\ncolor15               #$term15 #\n\ncursor                #$term7 #\n\nforeground            #$term7 #\n\nselection_background  #$onSecondaryContainer #\nselection_foreground  #$secondaryContainer #\n\n# Override obscure colors for starship prompt (these are greys at the end)\ncolor255              #$primary #\ncolor254              #$primaryContainer #\ncolor253              #$secondary #\ncolor252              #$secondaryContainer #\ncolor251              #$tertiary #\ncolor250              #$tertiaryContainer #\ncolor249              #$error #\ncolor248              #$errorContainer #\n\ncolor232              #$onPrimary #\ncolor233              #$onPrimaryContainer #\ncolor234              #$onSecondary #\ncolor235              #$onSecondaryContainer #\ncolor236              #$onTertiary #\ncolor237              #$onTertiaryContainer #\ncolor238              #$onError #\ncolor239              #$onErrorContainer #\ncolor240              #$onPrimary #\n# Somehow 232 doesn't work so i gotta use another number\n\n# Some stuff should specifically use the colors in the middle so they look acceptable in both unthemed light/dark\ncolor243              #$primary #\ncolor244              #$error #\ncolor245              #$outlineVariant #\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/terminal/scheme-base.json",
    "content": "{\n    \"dark\": {\n        \"term0\"   : \"#282828\",\n        \"term1\"   : \"#CC241D\",\n        \"term2\"   : \"#98971A\",\n        \"term3\"   : \"#D79921\",\n        \"term4\"   : \"#458588\",\n        \"term5\"   : \"#B16286\",\n        \"term6\"   : \"#689D6A\",\n        \"term7\"   : \"#A89984\",\n        \"term8\"   : \"#928374\",\n        \"term9\"   : \"#FB4934\",\n        \"term10\"  : \"#B8BB26\",\n        \"term11\"  : \"#FABD2F\",\n        \"term12\"  : \"#83A598\",\n        \"term13\"  : \"#D3869B\",\n        \"term14\"  : \"#8EC07C\",\n        \"term15\"  : \"#EBDBB2\"\n    },\n    \"light\": {\n        \"term0\"   : \"#FDF9F3\",\n        \"term1\"   : \"#FF6188\",\n        \"term2\"   : \"#A9DC76\",\n        \"term3\"   : \"#FC9867\",\n        \"term4\"   : \"#FFD866\",\n        \"term5\"   : \"#F47FD4\",\n        \"term6\"   : \"#78DCE8\",\n        \"term7\"   : \"#333034\",\n        \"term8\"   : \"#121212\",\n        \"term9\"   : \"#FF6188\",\n        \"term10\"  : \"#A9DC76\",\n        \"term11\"  : \"#FC9867\",\n        \"term12\"  : \"#FFD866\",\n        \"term13\"  : \"#F47FD4\",\n        \"term14\"  : \"#78DCE8\",\n        \"term15\"  : \"#333034\"\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/colors/terminal/sequences.txt",
    "content": "\u001b]4;0;#$term0 #\u001b\\\u001b]1;0;#$term0 #\u001b\\\u001b]4;1;#$term1 #\u001b\\\u001b]4;2;#$term2 #\u001b\\\u001b]4;3;#$term3 #\u001b\\\u001b]4;4;#$term4 #\u001b\\\u001b]4;5;#$term5 #\u001b\\\u001b]4;6;#$term6 #\u001b\\\u001b]4;7;#$term7 #\u001b\\\u001b]4;8;#$term8 #\u001b\\\u001b]4;9;#$term9 #\u001b\\\u001b]4;10;#$term10 #\u001b\\\u001b]4;11;#$term11 #\u001b\\\u001b]4;12;#$term12 #\u001b\\\u001b]4;13;#$term13 #\u001b\\\u001b]4;14;#$term14 #\u001b\\\u001b]4;15;#$term15 #\u001b\\\u001b]4;232;#$term7 #\u001b\\\u001b]4;255;#$primary #\u001b\\\u001b]4;254;#$primaryContainer #\u001b\\\u001b]4;253;#$secondary #\u001b\\\u001b]4;252;#$secondaryContainer #\u001b\\\u001b]4;251;#$tertiary #\u001b\\\u001b]4;250;#$tertiaryContainer #\u001b\\\u001b]4;249;#$error #\u001b\\\u001b]4;248;#$errorContainer #\u001b\\\u001b]4;232;#$onPrimary #\u001b\\\u001b]4;233;#$onPrimaryContainer #\u001b\\\u001b]4;234;#$onSecondary #\u001b\\\u001b]4;235;#$onSecondaryContainer #\u001b\\\u001b]4;236;#$onTertiary #\u001b\\\u001b]4;237;#$onTertiaryContainer #\u001b\\\u001b]4;238;#$onError #\u001b\\\u001b]4;239;#$onErrorContainer #\u001b\\\u001b]4;240;#$onPrimary #\u001b\\\u001b]4;243;#$primary #\u001b\\\u001b]4;244;#$error #\u001b\\\u001b]4;245;#$outlineVariant #\u001b\\\u001b]10;#$term7 #\u001b\\\u001b]11;[100]#$term0 #\u001b\\\u001b]12;#$term7 #\u001b\\\u001b]13;#$term7 #\u001b\\\u001b]17;#$term7 #\u001b\\\u001b]19;#$term0 #\u001b\\\u001b]708;[100]#$term0 #\u001b"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/hyprland/hyprconfigurator.py",
    "content": "#!/usr/bin/env -S\\_/bin/sh\\_-c\\_\"source\\_\\$(eval\\_echo\\_\\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\\_python\\_-E\\_\"\\$0\"\\_\"\\$@\"\"\nimport argparse\nimport re\nimport os\nimport tempfile\n\ndef format_value(value):\n    \"\"\"Format value: quote strings, leave numbers and booleans as-is\"\"\"\n    if value in ('true', 'false'):\n        return value\n    try:\n        float(value)\n        return value\n    except ValueError:\n        return f'\"{value}\"'\n\ndef build_nested_structure(key_parts, value):\n    \"\"\"Recursively build nested structure from key parts\"\"\"\n    if len(key_parts) == 1:\n        return f'{key_parts[0]}={format_value(value)}'\n    else:\n        return f'{key_parts[0]}={{{build_nested_structure(key_parts[1:], value)}}}'\n\ndef generate_config_line(key, value):\n    \"\"\"Generate hl.config line for given key and value\"\"\"\n    key_parts = key.split(':')\n    nested_structure = build_nested_structure(key_parts, value)\n    return f'hl.config({{{nested_structure}}})\\n'\n\ndef edit_hyprland_config(file_path, set_args, reset_args):\n    if os.path.exists(file_path):\n        with open(file_path, 'r') as file:\n            lines = file.readlines()\n    else:\n        lines = []\n    \n    set_dict = {k: v for k, v in set_args} if set_args else {}\n    reset_set = set(reset_args) if reset_args else set()\n    \n    new_lines = []\n    found_keys = set()\n    \n    patterns = {}\n    for k in list(set_dict.keys()) + list(reset_set):\n        key_parts = k.split(':')\n        main_key = key_parts[0]\n        if len(key_parts) > 1:\n            # Build pattern to match nested structure\n            pattern_parts = [rf'\\s*{re.escape(part)}\\s*=' for part in key_parts]\n            nested_pattern = '\\{'.join(pattern_parts)\n            patterns[k] = re.compile(rf'^\\s*hl\\.config\\(\\{{\\s*{nested_pattern}')\n        else:\n            patterns[k] = re.compile(rf'^\\s*hl\\.config\\(\\{{\\s*{re.escape(main_key)}\\s*=')\n        \n    for line in lines:\n        matched = False\n        \n        # Check if line matches a key to be reset\n        for key in reset_set:\n            if patterns[key].match(line):\n                matched = True\n                break\n                \n        if matched:\n            continue\n            \n        # Check if line matches a key to be set\n        for key, value in set_dict.items():\n            if patterns[key].match(line):\n                new_line = generate_config_line(key, value)\n                new_lines.append(new_line)\n                found_keys.add(key)\n                matched = True\n                break\n                \n        if matched:\n            continue\n            \n        new_lines.append(line)\n        \n    if set_dict:\n        for key, value in set_dict.items():\n            if key not in found_keys:\n                if new_lines and not new_lines[-1].endswith('\\n'):\n                    new_lines[-1] += '\\n'\n                new_lines.append(generate_config_line(key, value))\n                \n    dir_name = os.path.dirname(os.path.abspath(file_path))\n    os.makedirs(dir_name, exist_ok=True)\n    temp_path = None\n    try:\n        with tempfile.NamedTemporaryFile(mode='w', dir=dir_name, delete=False) as temp_file:\n            temp_file.writelines(new_lines)\n            temp_path = temp_file.name\n        \n        if os.path.exists(file_path):\n            os.chmod(temp_path, os.stat(file_path).st_mode)\n        else:\n            os.chmod(temp_path, 0o644)\n            \n        os.replace(temp_path, file_path)\n    except Exception as e:\n        if temp_path and os.path.exists(temp_path):\n            os.remove(temp_path)\n        print(f\"Error saving file: {e}\")\n        return\n        \n    for key in reset_set:\n        print(f\"Removed '{key}' from '{file_path}'\")\n    for key, value in set_dict.items():\n        print(f\"Updated '{file_path}' with {generate_config_line(key, value).strip()}\")\n\nif __name__ == \"__main__\":\n    parser = argparse.ArgumentParser(description=\"Edit a Hyprland config file. Subkeys use colon (:) for nesting.\")\n    parser.add_argument(\"--file\", default=\"~/.config/hypr/hyprland.conf\", help=\"Path to the Hyprland config file (default: ~/.config/hypr/hyprland.conf).\")\n    \n    parser.add_argument(\"--set\", nargs=2, action=\"append\", metavar=(\"KEY\", \"VALUE\"), help=\"Set a configuration key to a value.\")\n    parser.add_argument(\"--reset\", action=\"append\", metavar=\"KEY\", help=\"Remove a configuration key.\")\n    \n    args = parser.parse_args()\n    \n    file_path = os.path.expanduser(args.file)\n    \n    raw_set_args = args.set or []\n    reset_args = args.reset or []\n    \n    set_args = []\n    for key, value in raw_set_args:\n        if value == \"[[EMPTY]]\":\n            reset_args.append(key)\n        else:\n            set_args.append((key, value))\n    \n    if not set_args and not reset_args:\n        print(\"Error: Must specify at least one key to set or reset.\")\n    else:\n        edit_hyprland_config(file_path, set_args, reset_args)\n        "
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/images/find-regions-venv.sh",
    "content": "#!/usr/bin/env bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n\"$SCRIPT_DIR/find_regions.py\" \"$@\"\ndeactivate\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/images/find_regions.py",
    "content": "#!/usr/bin/env python3\n\nimport argparse\nimport cv2\nimport json\nimport numpy as np\nimport sys\n\nDEFAULT_IMAGE_PATH = '/tmp/quickshell/media/screenshot/image'\n\ndef iou(boxA, boxB):\n    # Compute intersection over union for two boxes\n    xA = max(boxA['x'], boxB['x'])\n    yA = max(boxA['y'], boxB['y'])\n    xB = min(boxA['x'] + boxA['width'], boxB['x'] + boxB['width'])\n    yB = min(boxA['y'] + boxA['height'], boxB['y'] + boxB['height'])\n    interW = max(0, xB - xA)\n    interH = max(0, yB - yA)\n    interArea = interW * interH\n    boxAArea = boxA['width'] * boxA['height']\n    boxBArea = boxB['width'] * boxB['height']\n    iou = interArea / float(boxAArea + boxBArea - interArea) if (boxAArea + boxBArea - interArea) > 0 else 0\n    return iou\n\ndef non_max_suppression(regions, iou_threshold=0.7):\n    # Sort by area (largest first)\n    regions = sorted(regions, key=lambda r: r['width'] * r['height'], reverse=True)\n    keep = []\n    while regions:\n        current = regions.pop(0)\n        keep.append(current)\n        regions = [r for r in regions if iou(current, r) < iou_threshold]\n    return keep\n\ndef find_regions(image_path, min_width, min_height, max_width=None, max_height=None, quality=False, k=150, min_size=20, sigma=0.8, resize_factor=1.0):\n    image = cv2.imread(image_path)\n    if image is None:\n        print(f'Error: Could not load image {image_path}', file=sys.stderr)\n        sys.exit(1)\n    orig_h, orig_w = image.shape[:2]\n    if resize_factor != 1.0:\n        image = cv2.resize(image, (int(orig_w * resize_factor), int(orig_h * resize_factor)), interpolation=cv2.INTER_AREA)\n    ss = cv2.ximgproc.segmentation.createSelectiveSearchSegmentation()\n    ss.setBaseImage(image)\n    if quality:\n        ss.switchToSelectiveSearchQuality(k, min_size, sigma)\n    else:\n        ss.switchToSelectiveSearchFast(k, min_size, sigma)\n    rects = ss.process()\n    regions = []\n    for (x, y, w, h) in rects:\n        # Scale regions back to original image size if resized\n        if resize_factor != 1.0:\n            x = int(x / resize_factor)\n            y = int(y / resize_factor)\n            w = int(w / resize_factor)\n            h = int(h / resize_factor)\n        # Filter out region that is exactly the same size as the original image\n        if w == orig_w and h == orig_h and x == 0 and y == 0:\n            continue\n        if w > min_width and h > min_height:\n            if (max_width is None or w < max_width) and (max_height is None or h < max_height):\n                regions.append({'x': int(x), 'y': int(y), 'width': int(w), 'height': int(h)})\n    # Remove duplicates/overlaps\n    regions = non_max_suppression(regions, iou_threshold=0.7)\n    return regions, cv2.imread(image_path)  # Return original image for drawing\n\ndef draw_regions(image, regions, output_path):\n    for region in regions:\n        if 'x' in region:\n            x, y, w, h = region['x'], region['y'], region['width'], region['height']\n        elif 'at' in region and 'size' in region:\n            x, y = region['at']\n            w, h = region['size']\n        else:\n            continue\n        cv2.rectangle(image, (x, y), (x + w, y + h), (0, 0, 255), 2)\n    cv2.imwrite(output_path, image)\n\ndef main():\n    parser = argparse.ArgumentParser(description='Find regions of interest in an image using selective search.')\n    parser.add_argument('-i', '--image', default=DEFAULT_IMAGE_PATH, help='Path to input image')\n    parser.add_argument('-do', '--debug-output', help='Path to save debug image with rectangles')\n    parser.add_argument('--min-width', type=int, default=200, help='Minimum width of detected region')\n    parser.add_argument('--min-height', type=int, default=100, help='Minimum height of detected region')\n    parser.add_argument('--max-width', type=int, help='Maximum width of detected region')\n    parser.add_argument('--max-height', type=int, help='Maximum height of detected region')\n    parser.add_argument('--single', action='store_true', help='Only output the most likely (largest) region')\n    parser.add_argument('--quality', action='store_true', help='Use quality mode for selective search (slower, less sensitive)')\n    parser.add_argument('--k', type=int, default=3000, help='Segmentation parameter k (default: 150)')\n    parser.add_argument('--min-size', type=int, default=50, help='Segmentation parameter min_size (default: 20)')\n    parser.add_argument('--sigma', type=float, default=0.6, help='Segmentation parameter sigma (default: 0.8)')\n    parser.add_argument('--resize-factor', type=float, default=0.1, help='Resize factor for input image before processing (default: 1.0, e.g. 0.5 for half size)')\n    parser.add_argument('--hyprctl', action='store_true', help='Mimics hyprctl\\'s window output, like {\"at\": [x, y], \"size\": [w, h]}')\n    args = parser.parse_args()\n\n    regions, image = find_regions(\n        args.image,\n        min_width=args.min_width,\n        min_height=args.min_height,\n        max_width=args.max_width,\n        max_height=args.max_height,\n        quality=args.quality,\n        k=args.k,\n        min_size=args.min_size,\n        sigma=args.sigma,\n        resize_factor=args.resize_factor\n    )\n    if args.single and regions:\n        largest = max(regions, key=lambda r: r['width'] * r['height'])\n        regions = [largest]\n    if args.hyprctl:\n        regions = [{\"at\": [r['x'], r['y']], \"size\": [r['width'], r['height']]} for r in regions]\n    print(json.dumps(regions))\n    if args.debug_output:\n        draw_regions(image, regions, args.debug_output)\n\nif __name__ == '__main__':\n    main()\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/images/least-busy-region-venv.sh",
    "content": "#!/usr/bin/env bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n\"$SCRIPT_DIR/least_busy_region.py\" \"$@\"\ndeactivate\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/images/least_busy_region.py",
    "content": "#!/usr/bin/env python3\n# Disclaimer: This script was ai-generated and went through minimal revision.\n\nimport os\nos.environ[\"OPENCV_LOG_LEVEL\"] = \"SILENT\"\nimport cv2\nimport numpy as np\nimport argparse\nimport json\n\ndef center_crop(img, target_w, target_h):\n    h, w = img.shape[:2]\n    if w == target_w and h == target_h:\n        return img\n    x1 = max(0, (w - target_w) // 2)\n    y1 = max(0, (h - target_h) // 2)\n    x2 = x1 + target_w\n    y2 = y1 + target_h\n    return img[y1:y2, x1:x2]\n\ndef find_least_busy_region(image_path, region_width=300, region_height=200, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode=\"fill\", horizontal_padding=50, vertical_padding=50, busiest=False):\n    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n    if img is None:\n        raise FileNotFoundError(f\"Image not found: {image_path}\")\n    orig_h, orig_w = img.shape\n    scale = 1.0\n    if screen_width is not None and screen_height is not None:\n        scale_w = screen_width / orig_w\n        scale_h = screen_height / orig_h\n        if screen_mode == \"fill\":\n            scale = max(scale_w, scale_h)\n        else:\n            scale = min(scale_w, scale_h)\n        new_w = int(orig_w * scale)\n        new_h = int(orig_h * scale)\n        if verbose:\n            print(f\"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})\")\n        img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)\n        img = center_crop(img, screen_width, screen_height)\n        if verbose:\n            print(f\"Cropped image to {screen_width}x{screen_height}\")\n    else:\n        if verbose:\n            print(f\"Using original image size: {orig_w}x{orig_h}\")\n    arr = img.astype(np.float64)\n    h, w = arr.shape\n    # Validate & adjust stride\n    stride = max(1, int(stride) if stride else 1)\n    # Adjust region size if it does not fit given padding\n    if horizontal_padding * 2 >= w or vertical_padding * 2 >= h:\n        # Reduce padding to fit at least a 1x1 region\n        horizontal_padding = max(0, min(horizontal_padding, (w - 1) // 2))\n        vertical_padding = max(0, min(vertical_padding, (h - 1) // 2))\n    max_region_w = w - 2 * horizontal_padding\n    max_region_h = h - 2 * vertical_padding\n    if max_region_w <= 0 or max_region_h <= 0:\n        raise ValueError(\"Image too small for the specified padding.\")\n    if region_width > max_region_w:\n        if verbose:\n            print(f\"Requested region_width {region_width} too large; clamping to {max_region_w}\")\n        region_width = max_region_w\n    if region_height > max_region_h:\n        if verbose:\n            print(f\"Requested region_height {region_height} too large; clamping to {max_region_h}\")\n        region_height = max_region_h\n    # Use OpenCV's integral for fast computation\n    integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]\n    integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]\n    def region_sum(ii, x1, y1, x2, y2):\n        # Assume bounds have been checked before calling\n        total = ii[y2, x2]\n        if x1 > 0:\n            total -= ii[y2, x1-1]\n        if y1 > 0:\n            total -= ii[y1-1, x2]\n        if x1 > 0 and y1 > 0:\n            total += ii[y1-1, x1-1]\n        return total\n    min_var = None\n    max_var = None\n    min_coords = (horizontal_padding, vertical_padding)\n    max_coords = (horizontal_padding, vertical_padding)\n    area = region_width * region_height\n    x_start = horizontal_padding\n    y_start = vertical_padding\n    x_end = w - region_width - horizontal_padding + 1\n    y_end = h - region_height - vertical_padding + 1\n    if x_end < x_start:\n        x_end = x_start\n    if y_end < y_start:\n        y_end = y_start\n    for y in range(y_start, y_end + 1, stride):\n        for x in range(x_start, x_end + 1, stride):\n            x1, y1 = x, y\n            x2, y2 = x + region_width - 1, y + region_height - 1\n            if x2 >= w or y2 >= h:\n                continue  # Skip out-of-bounds window\n            s = region_sum(integral, x1, y1, x2, y2)\n            s2 = region_sum(integral_sq, x1, y1, x2, y2)\n            mean = s / area\n            var = (s2 / area) - (mean ** 2)\n            if (min_var is None) or (var < min_var):\n                min_var = var\n                min_coords = (x, y)\n            if (max_var is None) or (var > max_var):\n                max_var = var\n                max_coords = (x, y)\n    if busiest:\n        return max_coords, max_var\n    else:\n        return min_coords, min_var\n\ndef find_largest_region(image_path, screen_width=None, screen_height=None, verbose=False, stride=2, screen_mode=\"fill\", threshold=100.0, aspect_ratio=1.0, horizontal_padding=50, vertical_padding=50):\n    img = cv2.imread(image_path, cv2.IMREAD_GRAYSCALE)\n    if img is None:\n        raise FileNotFoundError(f\"Image not found: {image_path}\")\n    orig_h, orig_w = img.shape\n    # ...existing scaling logic...\n    scale = 1.0\n    if screen_width is not None and screen_height is not None:\n        scale_w = screen_width / orig_w\n        scale_h = screen_height / orig_h\n        if screen_mode == \"fill\":\n            scale = max(scale_w, scale_h)\n        else:\n            scale = min(scale_w, scale_h)\n        new_w = int(orig_w * scale)\n        new_h = int(orig_h * scale)\n        if verbose:\n            print(f\"Scaling image from {orig_w}x{orig_h} to {new_w}x{new_h} (scale: {scale:.3f}, mode: {screen_mode})\")\n        img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)\n        img = center_crop(img, screen_width, screen_height)\n        if verbose:\n            print(f\"Cropped image to {screen_width}x{screen_height}\")\n    else:\n        if verbose:\n            print(f\"Using original image size: {orig_w}x{orig_h}\")\n    arr = img.astype(np.float64)\n    h, w = arr.shape\n    stride = max(1, int(stride) if stride else 1)\n    threshold = max(0.0, float(threshold))\n    # Adjust padding if image too small\n    if horizontal_padding * 2 >= w or vertical_padding * 2 >= h:\n        horizontal_padding = max(0, min(horizontal_padding, (w - 1) // 2))\n        vertical_padding = max(0, min(vertical_padding, (h - 1) // 2))\n    # Use OpenCV's integral for fast computation\n    integral = cv2.integral(arr, sdepth=cv2.CV_64F)[1:,1:]\n    integral_sq = cv2.integral(arr**2, sdepth=cv2.CV_64F)[1:,1:]\n    def region_sum(ii, x1, y1, x2, y2):\n        total = ii[y2, x2]\n        if x1 > 0:\n            total -= ii[y2, x1-1]\n        if y1 > 0:\n            total -= ii[y1-1, x2]\n        if x1 > 0 and y1 > 0:\n            total += ii[y1-1, x1-1]\n        return total\n    min_size = 10\n    # Determine maximum feasible size respecting padding\n    effective_w = w - 2 * horizontal_padding\n    effective_h = h - 2 * vertical_padding\n    if effective_w <= 0 or effective_h <= 0:\n        return None, (0, 0), None\n    # Largest square-ish dimension given aspect ratio and effective space\n    if aspect_ratio >= 1.0:\n        max_size = min(effective_h, int(effective_w / aspect_ratio))\n    else:\n        max_size = min(int(effective_h * aspect_ratio), effective_w)\n    if max_size < min_size:\n        min_size = 1\n        max_size = max(1, max_size)\n    best = None\n    while min_size <= max_size:\n        mid = (min_size + max_size) // 2\n        if aspect_ratio >= 1.0:\n            region_h = mid\n            region_w = int(round(mid * aspect_ratio))\n        else:\n            region_w = mid\n            region_h = int(round(mid / aspect_ratio if aspect_ratio != 0 else mid))\n        if region_w <= 0 or region_h <= 0:\n            break\n        if region_w > effective_w or region_h > effective_h:\n            max_size = mid - 1\n            continue\n        found = False\n        x_start = horizontal_padding\n        y_start = vertical_padding\n        x_end = w - region_w - horizontal_padding\n        y_end = h - region_h - vertical_padding\n        for y in range(y_start, y_end + 1, stride):\n            for x in range(x_start, x_end + 1, stride):\n                x1, y1 = x, y\n                x2, y2 = x + region_w - 1, y + region_h - 1\n                if x2 >= w or y2 >= h:\n                    continue\n                s = region_sum(integral, x1, y1, x2, y2)\n                s2 = region_sum(integral_sq, x1, y1, x2, y2)\n                area = region_w * region_h\n                mean = s / area\n                var = (s2 / area) - (mean ** 2)\n                if var <= threshold:\n                    found = True\n                    best = (x, y, region_w, region_h, var)\n                    break\n            if found:\n                break\n        if found:\n            min_size = mid + 1\n        else:\n            max_size = mid - 1\n    if best:\n        x, y, region_w, region_h, var = best\n        center_x = x + region_w // 2\n        center_y = y + region_h // 2\n        return (center_x, center_y), (region_w, region_h), var\n    else:\n        return None, (0, 0), None\n\ndef draw_region(image_path, coords, region_width=300, region_height=200, output_path='output.png', screen_width=None, screen_height=None, screen_mode=\"fill\"):\n    img = cv2.imread(image_path)\n    if img is None:\n        raise FileNotFoundError(f\"Image not found: {image_path}\")\n    orig_h, orig_w = img.shape[:2]\n    if screen_width is not None and screen_height is not None:\n        scale_w = screen_width / orig_w\n        scale_h = screen_height / orig_h\n        if screen_mode == \"fill\":\n            scale = max(scale_w, scale_h)\n        else:\n            scale = min(scale_w, scale_h)\n        new_w = int(orig_w * scale)\n        new_h = int(orig_h * scale)\n        img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)\n        img = center_crop(img, screen_width, screen_height)\n    x, y = coords\n    cv2.rectangle(img, (x, y), (x+region_width-1, y+region_height-1), (0,0,255), 3)\n    cv2.imwrite(output_path, img)\n    # print removed for quieter operation\n\ndef draw_largest_region(image_path, center, size, output_path='output.png', screen_width=None, screen_height=None, screen_mode=\"fill\"):\n    img = cv2.imread(image_path)\n    if img is None:\n        raise FileNotFoundError(f\"Image not found: {image_path}\")\n    orig_h, orig_w = img.shape[:2]\n    if screen_width is not None and screen_height is not None:\n        scale_w = screen_width / orig_w\n        scale_h = screen_height / orig_h\n        if screen_mode == \"fill\":\n            scale = max(scale_w, scale_h)\n        else:\n            scale = min(scale_w, scale_h)\n        new_w = int(orig_w * scale)\n        new_h = int(orig_h * scale)\n        img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)\n        img = center_crop(img, screen_width, screen_height)\n    cx, cy = center\n    region_w, region_h = size\n    x1 = cx - region_w // 2\n    y1 = cy - region_h // 2\n    x2 = cx + region_w // 2 - 1\n    y2 = cy + region_h // 2 - 1\n    cv2.rectangle(img, (x1, y1), (x2, y2), (255,0,0), 3)\n    cv2.imwrite(output_path, img)\n    # print removed for quieter operation\n\ndef get_dominant_color(image_path, x, y, w, h, screen_width=None, screen_height=None, screen_mode=\"fill\"):\n    img = cv2.imread(image_path)\n    if img is None:\n        raise FileNotFoundError(f\"Image not found: {image_path}\")\n    orig_h, orig_w = img.shape[:2]\n    if screen_width is not None and screen_height is not None:\n        scale_w = screen_width / orig_w\n        scale_h = screen_height / orig_h\n        if screen_mode == \"fill\":\n            scale = max(scale_w, scale_h)\n        else:\n            scale = min(scale_w, scale_h)\n        new_w = int(orig_w * scale)\n        new_h = int(orig_h * scale)\n        img = cv2.resize(img, (new_w, new_h), interpolation=cv2.INTER_LANCZOS4)\n        img = center_crop(img, screen_width, screen_height)\n    # Ensure region is within bounds\n    x = max(0, x)\n    y = max(0, y)\n    w = max(1, min(w, img.shape[1] - x))\n    h = max(1, min(h, img.shape[0] - y))\n    region = img[y:y+h, x:x+w]\n    if region.size == 0 or region.shape[0] == 0 or region.shape[1] == 0:\n        return [0, 0, 0]\n    region = region.reshape((-1, 3))\n    # Filter out black pixels (optional, improves accuracy for some images)\n    non_black = region[np.any(region > 10, axis=1)]\n    if non_black.shape[0] == 0:\n        non_black = region\n    region = np.float32(non_black)\n    if region.shape[0] < 3:\n        return [int(x) for x in np.mean(region, axis=0)]\n    # K-means to find dominant color\n    criteria = (cv2.TERM_CRITERIA_EPS + cv2.TERM_CRITERIA_MAX_ITER, 10, 1.0)\n    K = min(3, region.shape[0])\n    _, labels, centers = cv2.kmeans(region, K, None, criteria, 10, cv2.KMEANS_RANDOM_CENTERS)\n    counts = np.bincount(labels.flatten())\n    dominant = centers[np.argmax(counts)]\n    # Reverse from BGR to RGB\n    return [int(x) for x in reversed(dominant)]\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Find least busy region in an image and output a JSON. Made for determining a suitable position for a wallpaper widget.\")\n    parser.add_argument(\"image_path\", help=\"Path to the input image\")\n    parser.add_argument(\"--width\", type=int, default=300, help=\"Region width\")\n    parser.add_argument(\"--height\", type=int, default=200, help=\"Region height\")\n    parser.add_argument(\"-v\", \"--visual-output\", action=\"store_true\", help=\"Output image with rectangle\")\n    parser.add_argument(\"--screen-width\", type=int, default=1920, help=\"Screen width for wallpaper scaling\")\n    parser.add_argument(\"--screen-height\", type=int, default=1080, help=\"Screen height for wallpaper scaling\")\n    parser.add_argument(\"--stride\", type=int, default=10, help=\"Step size for sliding window (higher is faster, less precise)\")\n    parser.add_argument(\"--screen-mode\", choices=[\"fill\", \"fit\"], default=\"fill\", help=\"Wallpaper scaling mode: 'fill' (default) or 'fit'\")\n    parser.add_argument(\"--verbose\", action=\"store_true\", help=\"Print verbose output\")\n    parser.add_argument(\"-l\", \"--largest-region\", action=\"store_true\", help=\"Find the largest region under the variance threshold and output its center\")\n    parser.add_argument(\"-t\", \"--variance-threshold\", type=float, default=1000.0, help=\"Variance threshold for largest region mode\")\n    parser.add_argument(\"--aspect-ratio\", type=float, default=1.78, help=\"Aspect ratio (width/height) for largest region mode\")\n    parser.add_argument(\"--horizontal-padding\", \"-hp\", type=int, default=50, help=\"Minimum horizontal distance from region to image edge\")\n    parser.add_argument(\"--vertical-padding\", \"-vp\", type=int, default=50, help=\"Minimum vertical distance from region to image edge\")\n    parser.add_argument(\"--busiest\", action=\"store_true\", help=\"Find the busiest region instead of the least busy\")\n    args = parser.parse_args()\n\n    if args.largest_region:\n        center, size, var = find_largest_region(\n            args.image_path,\n            screen_width=args.screen_width,\n            screen_height=args.screen_height,\n            verbose=args.verbose,\n            stride=args.stride,\n            screen_mode=args.screen_mode,\n            threshold=args.variance_threshold,\n            aspect_ratio=args.aspect_ratio,\n            horizontal_padding=args.horizontal_padding,\n            vertical_padding=args.vertical_padding\n        )\n        if center:\n            if args.visual_output:\n                draw_largest_region(args.image_path, center, size, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)\n            # Extract dominant color\n            cx, cy = center\n            region_w, region_h = size\n            x1 = cx - region_w // 2\n            y1 = cy - region_h // 2\n            dominant_color = get_dominant_color(\n                args.image_path, x1, y1, region_w, region_h,\n                screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode\n            )\n            dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)\n            print(json.dumps({\n                \"center_x\": center[0],\n                \"center_y\": center[1],\n                \"width\": size[0],\n                \"height\": size[1],\n                \"variance\": var,\n                \"dominant_color\": dominant_color_hex\n            }))\n        else:\n            print(json.dumps({\"error\": \"No region found under the threshold.\"}))\n        return\n\n    coords, variance = find_least_busy_region(\n        args.image_path,\n        region_width=args.width,\n        region_height=args.height,\n        screen_width=args.screen_width,\n        screen_height=args.screen_height,\n        verbose=args.verbose,\n        stride=args.stride,\n        screen_mode=args.screen_mode,\n        horizontal_padding=args.horizontal_padding,\n        vertical_padding=args.vertical_padding,\n        busiest=args.busiest\n    )\n    if args.visual_output:\n        draw_region(args.image_path, coords, region_width=args.width, region_height=args.height, screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode)\n    # Output JSON with center point\n    center_x = coords[0] + args.width // 2\n    center_y = coords[1] + args.height // 2\n    dominant_color = get_dominant_color(\n        args.image_path, coords[0], coords[1], args.width, args.height,\n        screen_width=args.screen_width, screen_height=args.screen_height, screen_mode=args.screen_mode\n    )\n    dominant_color_hex = '#{:02x}{:02x}{:02x}'.format(*dominant_color)\n    print(json.dumps({\n        \"center_x\": center_x,\n        \"center_y\": center_y,\n        \"width\": args.width,\n        \"height\": args.height,\n        \"variance\": variance,\n        \"dominant_color\": dominant_color_hex\n    }))\n\nif __name__ == \"__main__\":\n    main()\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/images/text-color-venv.sh",
    "content": "#!/usr/bin/env bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n\"$SCRIPT_DIR/text_color.py\" \"$@\"\ndeactivate\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/images/text_color.py",
    "content": "#!/usr/bin/env python3\n# Disclaimer: This script was ai-generated and went through minimal revision.\n\nimport cv2\nimport numpy as np\nimport json\nimport sys\n\ndef to_hex(color):\n    return \"#{:02x}{:02x}{:02x}\".format(int(color[0]), int(color[1]), int(color[2]))\n\ndef get_color_from_stdin():\n    # Read raw bytes from stdin\n    input_data = sys.stdin.buffer.read()\n    if not input_data:\n        return {\"error\": \"No data received via stdin\"}\n\n    # Convert bytes to numpy array and decode to image\n    nparr = np.frombuffer(input_data, np.uint8)\n    img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)\n    \n    if img is None:\n        return {\"error\": \"Could not decode image data\"}\n    \n    img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    h, w, _ = img_rgb.shape\n\n    # 1. Sample corner pixels (The background anchors)\n    corners = np.array([\n        img_rgb[0, 0],\n        img_rgb[0, w-1],\n        img_rgb[h-1, 0],\n        img_rgb[h-1, w-1]\n    ])\n\n    # 2. Determine single dominant background\n    # Using median handles noise/gradients better than a simple average\n    bg_color = np.median(corners, axis=0).astype(int)\n\n    # 3. Find the Text Color\n    pixels = img_rgb.reshape(-1, 3).astype(int)\n    distances = np.linalg.norm(pixels - bg_color, axis=1)\n    \n    # Take the 95th percentile of pixels furthest from background\n    threshold = np.percentile(distances, 95)\n    text_pixels = pixels[distances >= threshold]\n    \n    if len(text_pixels) == 0:\n        text_color = [255, 255, 255] # Fallback\n    else:\n        text_color = np.median(text_pixels, axis=0).astype(int)\n\n    return {\n        \"background\": to_hex(bg_color),\n        \"text\": to_hex(text_color)\n    }\n\nif __name__ == \"__main__\":\n    result = get_color_from_stdin()\n    print(json.dumps(result))"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/keyring/is_unlocked.sh",
    "content": "#!/usr/bin/env bash\nlocked_state=$(busctl --user get-property org.freedesktop.secrets \\\n    /org/freedesktop/secrets/collection/login \\\n    org.freedesktop.Secret.Collection Locked)\nif [[ \"${locked_state}\" == \"b false\" ]]; then\n    echo 'Keyring is unlocked' >&2\n    exit 0\nelse\n    echo 'Keyring is locked' >&2\n    exit 1\nfi\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/keyring/try_lookup.sh",
    "content": "#!/usr/bin/env bash\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\ndata=$(secret-tool lookup 'application' 'illogical-impulse')\nif [[ -z \"$data\" ]]; then\n    if \"${SCRIPT_DIR}/is_unlocked.sh\"; then\n        echo 'not found'\n        exit 1\n    else \n        echo 'locked'\n        exit 2\n    fi\nfi\necho \"$data\"\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/keyring/unlock.sh",
    "content": "#!/usr/bin/env bash\n# Based on https://unix.stackexchange.com/a/602935\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\n# Skip if already unlocked\nif \"${SCRIPT_DIR}/is_unlocked.sh\"; then\n    exit 1\nfi\n\n# Prompt for password if not provided\nif [[ -z \"${UNLOCK_PASSWORD}\" ]]; then\n    echo -n 'Login password: ' >&2\n    read -s UNLOCK_PASSWORD || return\nfi\n\n# Unlock\nkillall -q -u \"$(whoami)\" gnome-keyring-daemon\neval $(echo -n \"${UNLOCK_PASSWORD}\" \\\n           | gnome-keyring-daemon --daemonize --login \\\n           | sed -e 's/^/export /')\nunset UNLOCK_PASSWORD\necho '' >&2\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/musicRecognition/recognize-music.sh",
    "content": "#!/bin/bash\n\nINTERVAL=2\nTOTAL_DURATION=30\nSOURCE_TYPE=\"monitor\"  # monitor | input\nFIFO=$(mktemp -u /tmp/songrec_out_XXXXXX)\n\nwhile getopts \"i:t:s:\" opt; do\n  case $opt in\n    i) INTERVAL=$OPTARG ;;\n    t) TOTAL_DURATION=$OPTARG ;;\n    s) SOURCE_TYPE=$OPTARG ;;\n    *) exit 1 ;;\n  esac\ndone\n\nif ! command -v songrec >/dev/null 2>&1; then\n    exit 1\nfi\n\nif [ \"$SOURCE_TYPE\" = \"monitor\" ]; then\n    AUDIO_DEVICE=$(pactl get-default-sink).monitor\nelif [ \"$SOURCE_TYPE\" = \"input\" ]; then\n    AUDIO_DEVICE=$(pactl info | grep \"Default Source:\" | awk '{print $3}' || true)\nelse\n    echo \"Invalid source type\"\n    exit 1\nfi\n\nif [ -z \"$AUDIO_DEVICE\" ] || ! pactl list short sources | grep -q \"$AUDIO_DEVICE\"; then\n    exit 1\nfi\n\nmkfifo \"$FIFO\"\n\ncleanup() {\n    kill \"$SONGREC_PID\" 2>/dev/null || true\n    wait \"$SONGREC_PID\" 2>/dev/null\n    rm -f \"$FIFO\"\n}\ntrap cleanup EXIT\n\nsongrec listen --audio-device \"$AUDIO_DEVICE\" --request-interval \"$INTERVAL\" --json --disable-mpris > \"$FIFO\" &\nSONGREC_PID=$!\n\n( sleep \"$TOTAL_DURATION\" && kill \"$SONGREC_PID\" 2>/dev/null ) &\n\nwhile IFS= read -r line; do\n    if echo \"$line\" | grep -q '\"matches\": \\['; then\n        echo \"$line\"\n        exit 0\n    fi\ndone < \"$FIFO\"\n\nexit 0\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/thumbnails/generate-thumbnails-magick.sh",
    "content": "#!/usr/bin/env bash\n\n# Generate thumbnails for files using ImageMagick, following Freedesktop spec\n# Usage:\n#   ./generate-thumbnails-magick.sh --file <path>\n#   ./generate-thumbnails-magick.sh --directory <path>\n\nset -e\n\n# Thumbnail sizes mapping\nget_thumbnail_size() {\n    case \"$1\" in\n        normal) echo 128 ;;\n        large) echo 256 ;;\n        x-large) echo 512 ;;\n        xx-large) echo 1024 ;;\n        *) echo 128 ;;\n    esac\n}\n\nusage() {\n    echo \"Usage: $0 --file <path> | --directory <path>\"\n    exit 1\n}\n\nmd5() {\n    # Calculate md5 hash of the file's absolute path\n    echo -n \"$1\" | md5sum | awk '{print $1}'\n}\n\nurlencode() {\n    # Percent-encode a string for use in a URI, but do not encode slashes\n    local str=\"$1\"\n    local encoded=\"\"\n    local c\n    for ((i=0; i<${#str}; i++)); do\n        c=\"${str:$i:1}\"\n        case \"$c\" in\n            [a-zA-Z0-9.~_-]|/|'('|')'|'*') encoded+=\"$c\" ;;\n            *) printf -v hex '%%%02X' \"'${c}'\"; encoded+=\"$hex\" ;;\n        esac\n    done\n    echo \"$encoded\"\n}\n\ngenerate_thumbnail() {\n    local src=\"$1\"\n    local abs_path\n    abs_path=\"$(realpath \"$src\")\"\n    # Skip files with multiple frames (GIFs, videos, etc.)\n    case \"${abs_path,,}\" in\n        *.gif|*.mp4|*.webm|*.mkv|*.avi|*.mov)\n            return\n            ;;\n    esac\n    local encoded_path\n    encoded_path=\"$(urlencode \"$abs_path\")\"\n    local uri\n    uri=\"file://$encoded_path\"\n    local hash\n    hash=\"$(md5 \"$uri\")\"\n    local out=\"$CACHE_DIR/$hash.png\"\n    mkdir -p \"$CACHE_DIR\"\n    if [ -f \"$out\" ]; then\n        return\n    fi\n    magick \"$abs_path\" -resize \"${THUMBNAIL_SIZE}x${THUMBNAIL_SIZE}\" \"$out\"\n}\n\n# Parse arguments\nSIZE_NAME=\"normal\"\nMODE=\"\"\nTARGET=\"\"\nwhile [[ $# -gt 0 ]]; do\n    case \"$1\" in\n        --file|-f)\n            MODE=\"file\"\n            TARGET=\"$2\"\n            shift 2\n            ;;\n        --directory|-d)\n            MODE=\"dir\"\n            TARGET=\"$2\"\n            shift 2\n            ;;\n        --size|-s)\n            SIZE_NAME=\"$2\"\n            shift 2\n            ;;\n        *)\n            usage\n            ;;\n    esac\n    # Only one mode allowed\n    [[ -n \"$MODE\" ]] && break\ndone\n\nTHUMBNAIL_SIZE=\"$(get_thumbnail_size \"$SIZE_NAME\")\"\nCACHE_DIR=\"$HOME/.cache/thumbnails/$SIZE_NAME\"\n\nif [ -z \"$MODE\" ] || [ -z \"$TARGET\" ]; then\n    usage\nfi\n\ncase \"$MODE\" in\n    file)\n        if [ ! -f \"$TARGET\" ]; then\n            echo \"File not found: $TARGET\"\n            exit 2\n        fi\n        generate_thumbnail \"$TARGET\"\n        ;;\n    dir)\n        if [ ! -d \"$TARGET\" ]; then\n            echo \"Directory not found: $TARGET\"\n            exit 2\n        fi\n        for f in \"$TARGET\"/*; do\n            [ -f \"$f\" ] || continue\n            generate_thumbnail \"$f\" &\n        done\n        wait\n        ;;\n    *)\n        usage\n        ;;\nesac\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/thumbnails/thumbgen-venv.sh",
    "content": "#!/usr/bin/env bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\nGIO_USE_VFS=local \"$SCRIPT_DIR/thumbgen.py\" \"$@\"\nTHUMBGEN_EXIT_CODE=$?\ndeactivate\n\nexit $THUMBGEN_EXIT_CODE\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/thumbnails/thumbgen.py",
    "content": "#!/usr/bin/env python3\n\n# From https://github.com/difference-engine/thumbnail-generator-ubuntu (MIT License)\n# Since the script is small and the maintainers seem inactive to accept my PR (#11) I decided to just copy it over.\n# When it gets merged and the python package gets updated we can just use it\n\nimport os\nimport sys\nfrom multiprocessing import Pool\nfrom pathlib import Path\nfrom typing import List, Union\n\nimport click\nimport gi\nfrom loguru import logger\nfrom tqdm import tqdm\n\ngi.require_version(\"GnomeDesktop\", \"4.0\")\nfrom gi.repository import Gio, GnomeDesktop  # isort:skip\n\nthumbnail_size_map = {\n    \"normal\": GnomeDesktop.DesktopThumbnailSize.NORMAL,\n    \"large\": GnomeDesktop.DesktopThumbnailSize.LARGE,\n    \"x-large\": GnomeDesktop.DesktopThumbnailSize.XLARGE,\n    \"xx-large\": GnomeDesktop.DesktopThumbnailSize.XXLARGE,\n}\n\nfactory = None\nlogger.remove()\nlogger.add(sys.stdout, level=\"INFO\")\nlogger.add(\"/tmp/thumbgen.log\", level=\"DEBUG\", rotation=\"100 MB\")\n\ndef make_thumbnail(fpath: str) -> bool:\n    mtime = os.path.getmtime(fpath)\n    # Use Gio to determine the URI and mime type\n    f = Gio.file_new_for_path(str(fpath))\n    uri = f.get_uri()\n    info = f.query_info(\"standard::content-type\", Gio.FileQueryInfoFlags.NONE, None)\n    mime_type = info.get_content_type()\n\n    if factory.lookup(uri, mtime) is not None:\n        logger.debug(\"FRESH       {}\".format(uri))\n        return False\n\n    if not factory.can_thumbnail(uri, mime_type, mtime):\n        logger.debug(\"UNSUPPORTED {}\".format(uri))\n        return False\n\n    thumbnail = factory.generate_thumbnail(uri, mime_type)\n    if thumbnail is None:\n        logger.debug(\"ERROR       {}\".format(uri))\n        return False\n\n    logger.debug(\"OK          {}\".format(uri))\n    factory.save_thumbnail(thumbnail, uri, mtime)\n    return True\n\n\n@logger.catch()\ndef thumbnail_folder(*, dir_path: Path, workers: int, only_images: bool, recursive: bool, machine_progress: bool = False) -> None:\n    all_files = get_all_files(dir_path=dir_path, recursive=recursive)\n    if only_images:\n        all_files = get_all_images(all_files=all_files)\n    all_files = [str(fpath) for fpath in all_files]\n    if machine_progress:\n        completed = 0\n        total = len(all_files)\n        with Pool(processes=workers) as p:\n            for result in p.imap(make_thumbnail, all_files):\n                completed += 1\n                print(f\"PROGRESS {completed}/{total} FILE {all_files[completed-1]}\")\n                sys.stdout.flush()\n    else:\n        with Pool(processes=workers) as p:\n            list(tqdm(p.imap(make_thumbnail, all_files), total=len(all_files)))\n\n\ndef get_all_images(*, all_files: List[Path]) -> List[Path]:\n    img_suffixes = [\".jpg\", \".jpeg\", \".png\", \".gif\"]\n    all_images = [fpath for fpath in all_files if fpath.suffix in img_suffixes]\n    print(\"Found {} images\".format(len(all_images)))\n    return all_images\n\n\ndef get_all_files(*, dir_path: Path, recursive: bool) -> List[Path]:\n    if not (dir_path.exists() and dir_path.is_dir()):\n        raise ValueError(\"{} doesn't exist or isn't a valid directory!\".format(dir_path.resolve()))\n    if recursive:\n        all_files = dir_path.rglob(\"*\")\n    else:\n        all_files = dir_path.glob(\"*\")\n    all_files = [fpath for fpath in all_files if fpath.is_file()]\n    print(\"Found {} files in the directory: {}\".format(len(all_files), dir_path.resolve()))\n    return all_files\n\n@click.command()\n@click.option(\n    \"-d\", \"--img_dirs\", required=True, help='directories to generate thumbnails seperated by space, eg: \"dir1/dir2 dir3\"'\n)\n@click.option(\n    \"-s\", \"--size\", default=\"normal\", type=click.Choice([\"normal\", \"large\", \"x-large\", \"xx-large\"]), help=\"Thumbnail size: normal, large, x-large, xx-large\"\n)\n@click.option(\"-w\", \"--workers\", default=1, help=\"no of cpus to use for processing\")\n@click.option(\n    \"-i\", \"--only_images\", is_flag=True, default=False, help=\"Whether to only look for images to be thumbnailed\"\n)\n@click.option(\"-r\", \"--recursive\", is_flag=True, default=False, help=\"Whether to recursively look for files\")\n@click.option(\"--machine_progress\", is_flag=True, default=False, help=\"Print machine-readable progress lines instead of a progress bar\")\ndef main(img_dirs: str, size: str, workers: str, only_images: bool, recursive: bool, machine_progress: bool) -> None:\n    img_dirs = [Path(img_dir) for img_dir in img_dirs.split()]\n    global factory\n    factory = GnomeDesktop.DesktopThumbnailFactory.new(thumbnail_size_map[size])\n    for img_dir in img_dirs:\n        thumbnail_folder(dir_path=img_dir, workers=workers, only_images=only_images, recursive=recursive, machine_progress=machine_progress)\n    print(\"Thumbnail Generation Completed!\")\n\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "dots/.config/quickshell/ii/scripts/videos/record.sh",
    "content": "#!/usr/bin/env bash\n\nCONFIG_FILE=\"$HOME/.config/illogical-impulse/config.json\"\nJSON_PATH=\".screenRecord.savePath\"\n\nCUSTOM_PATH=$(jq -r \"$JSON_PATH\" \"$CONFIG_FILE\" 2>/dev/null)\n\nRECORDING_DIR=\"\"\n\nif [[ -n \"$CUSTOM_PATH\" ]]; then\n    RECORDING_DIR=\"$CUSTOM_PATH\"\nelse\n    RECORDING_DIR=\"$HOME/Videos\" # Use default path\nfi\n\ngetdate() {\n    date '+%Y-%m-%d_%H.%M.%S'\n}\ngetaudiooutput() {\n    pactl list sources | grep 'Name' | grep 'monitor' | cut -d ' ' -f2\n}\ngetactivemonitor() {\n    hyprctl monitors -j | jq -r '.[] | select(.focused == true) | .name'\n}\n\nmkdir -p \"$RECORDING_DIR\"\ncd \"$RECORDING_DIR\" || exit\n\n# parse --region <value> without modifying $@ so other flags like --fullscreen still work\nARGS=(\"$@\")\nMANUAL_REGION=\"\"\nSOUND_FLAG=0\nFULLSCREEN_FLAG=0\nfor ((i=0;i<${#ARGS[@]};i++)); do\n    if [[ \"${ARGS[i]}\" == \"--region\" ]]; then\n        if (( i+1 < ${#ARGS[@]} )); then\n            MANUAL_REGION=\"${ARGS[i+1]}\"\n        else\n            notify-send \"Recording cancelled\" \"No region specified for --region\" -a 'Recorder' & disown\n            exit 1\n        fi\n    elif [[ \"${ARGS[i]}\" == \"--sound\" ]]; then\n        SOUND_FLAG=1\n    elif [[ \"${ARGS[i]}\" == \"--fullscreen\" ]]; then\n        FULLSCREEN_FLAG=1\n    fi\ndone\n\nif pgrep wf-recorder > /dev/null; then\n    notify-send \"Recording Stopped\" \"Stopped\" -a 'Recorder' &\n    pkill wf-recorder &\nelse\n    if [[ $FULLSCREEN_FLAG -eq 1 ]]; then\n        notify-send \"Starting recording\" 'recording_'\"$(getdate)\"'.mp4' -a 'Recorder' & disown\n        if [[ $SOUND_FLAG -eq 1 ]]; then\n            wf-recorder -o \"$(getactivemonitor)\" --pixel-format yuv420p -f './recording_'\"$(getdate)\"'.mp4' -t --audio=\"$(getaudiooutput)\"\n        else\n            wf-recorder -o \"$(getactivemonitor)\" --pixel-format yuv420p -f './recording_'\"$(getdate)\"'.mp4' -t\n        fi\n    else\n        # If a manual region was provided via --region, use it; otherwise run slurp as before.\n        if [[ -n \"$MANUAL_REGION\" ]]; then\n            region=\"$MANUAL_REGION\"\n        else\n            if ! region=\"$(slurp 2>&1)\"; then\n                notify-send \"Recording cancelled\" \"Selection was cancelled\" -a 'Recorder' & disown\n                exit 1\n            fi\n        fi\n\n        notify-send \"Starting recording\" 'recording_'\"$(getdate)\"'.mp4' -a 'Recorder' & disown\n        if [[ $SOUND_FLAG -eq 1 ]]; then\n            wf-recorder --pixel-format yuv420p -f './recording_'\"$(getdate)\"'.mp4' -t --geometry \"$region\" --audio=\"$(getaudiooutput)\"\n        else\n            wf-recorder --pixel-format yuv420p -f './recording_'\"$(getdate)\"'.mp4' -t --geometry \"$region\"\n        fi\n    fi\nfi"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Ai.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common.functions as CF\nimport qs.modules.common\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport QtQuick\nimport qs.services.ai\n\n/**\n * Basic service to handle LLM chats. Supports Google's and OpenAI's API formats.\n * Supports Gemini and OpenAI models.\n * Limitations:\n * - For now functions only work with Gemini API format\n */\nSingleton {\n    id: root\n\n    property Component aiMessageComponent: AiMessageData {}\n    property Component aiModelComponent: AiModel {}\n    property Component geminiApiStrategy: GeminiApiStrategy {}\n    property Component openaiApiStrategy: OpenAiApiStrategy {}\n    property Component mistralApiStrategy: MistralApiStrategy {}\n    readonly property string interfaceRole: \"interface\"\n    readonly property string apiKeyEnvVarName: \"API_KEY\"\n\n    signal responseFinished()\n\n    property string systemPrompt: {\n        let prompt = Config.options?.ai?.systemPrompt ?? \"\";\n        for (let key in root.promptSubstitutions) {\n            // prompt = prompt.replaceAll(key, root.promptSubstitutions[key]);\n            // QML/JS doesn't support replaceAll, so use split/join\n            prompt = prompt.split(key).join(root.promptSubstitutions[key]);\n        }\n        return prompt;\n    }\n    // property var messages: []\n    property var messageIDs: []\n    property var messageByID: ({})\n    readonly property var apiKeys: KeyringStorage.keyringData?.apiKeys ?? {}\n    readonly property var apiKeysLoaded: KeyringStorage.loaded\n    readonly property bool currentModelHasApiKey: {\n        const model = models[currentModelId];\n        if (!model || !model.requires_key) return true;\n        if (!apiKeysLoaded) return false;\n        const key = apiKeys[model.key_id];\n        return (key?.length > 0);\n    }\n    property var postResponseHook\n    property real temperature: Persistent.states?.ai?.temperature ?? 0.5\n    property QtObject tokenCount: QtObject {\n        property int input: -1\n        property int output: -1\n        property int total: -1\n    }\n\n    function idForMessage(message) {\n        // Generate a unique ID using timestamp and random value\n        return Date.now().toString(36) + Math.random().toString(36).substr(2, 8);\n    }\n\n    function safeModelName(modelName) {\n        return modelName.replace(/:/g, \"_\").replace(/ /g, \"-\").replace(/\\//g, \"-\")\n    }\n\n    property list<var> defaultPrompts: []\n    property list<var> userPrompts: []\n    property list<var> promptFiles: [...defaultPrompts, ...userPrompts]\n    property list<var> savedChats: []\n\n    property var promptSubstitutions: {\n        \"{DISTRO}\": SystemInfo.distroName,\n        \"{DATETIME}\": `${DateTime.time}, ${DateTime.collapsedCalendarFormat}`,\n        \"{WINDOWCLASS}\": ToplevelManager.activeToplevel?.appId ?? \"Unknown\",\n        \"{DE}\": `${SystemInfo.desktopEnvironment} (${SystemInfo.windowingSystem})` \n    }\n\n    // Gemini: https://ai.google.dev/gemini-api/docs/function-calling\n    // OpenAI: https://platform.openai.com/docs/guides/function-calling\n    property string currentTool: Config?.options.ai.tool ?? \"search\"\n    property var tools: {\n        \"gemini\": {\n            \"functions\": [{\"functionDeclarations\": [\n                {\n                    \"name\": \"switch_to_search_mode\",\n                    \"description\": \"Search the web\",\n                },\n                {\n                    \"name\": \"get_shell_config\",\n                    \"description\": \"Get the desktop shell config file contents\",\n                },\n                {\n                    \"name\": \"set_shell_config\",\n                    \"description\": \"Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.\",\n                    \"parameters\": {\n                        \"type\": \"object\",\n                        \"properties\": {\n                            \"key\": {\n                                \"type\": \"string\",\n                                \"description\": \"The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.\",\n                            },\n                            \"value\": {\n                                \"type\": \"string\",\n                                \"description\": \"The value to set, e.g. `true`\"\n                            }\n                        },\n                        \"required\": [\"key\", \"value\"]\n                    }\n                },\n                {\n                    \"name\": \"run_shell_command\",\n                    \"description\": \"Run a shell command in bash and get its output. Use this only for quick commands that don't require user interaction. For commands that require interaction, ask the user to run manually instead.\",\n                    \"parameters\": {\n                        \"type\": \"object\",\n                        \"properties\": {\n                            \"command\": {\n                                \"type\": \"string\",\n                                \"description\": \"The bash command to run\",\n                            },\n                        },\n                        \"required\": [\"command\"]\n                    }\n                },\n            ]}],\n            \"search\": [{\n                \"google_search\": {}\n            }],\n            \"none\": []\n        },\n        \"openai\": {\n            \"functions\": [\n                {\n                    \"type\": \"function\",\n                    \"function\": {\n                        \"name\": \"get_shell_config\",\n                        \"description\": \"Get the desktop shell config file contents\",\n                        \"parameters\": {}\n                    },\n                },\n                {\n                    \"type\": \"function\",\n                    \"function\": {\n                        \"name\": \"set_shell_config\",\n                        \"description\": \"Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.\",\n                        \"parameters\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"key\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.\",\n                                },\n                                \"value\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The value to set, e.g. `true`\"\n                                }\n                            },\n                            \"required\": [\"key\", \"value\"]\n                        }\n                    }\n                },\n                {\n                    \"type\": \"function\",\n                    \"function\": {\n                        \"name\": \"run_shell_command\",\n                        \"description\": \"Run a shell command in bash and get its output. Use this only for quick commands that don't require user interaction. For commands that require interaction, ask the user to run manually instead.\",\n                        \"parameters\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"command\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The bash command to run\",\n                                },\n                            },\n                            \"required\": [\"command\"]\n                        }\n                    },\n                },\n            ],\n            \"search\": [],\n            \"none\": [],\n        },\n        \"mistral\": {\n            \"functions\": [\n                {\n                    \"type\": \"function\",\n                    \"function\": {\n                        \"name\": \"get_shell_config\",\n                        \"description\": \"Get the desktop shell config file contents\",\n                        \"parameters\": {}\n                    },\n                },\n                {\n                    \"type\": \"function\",\n                    \"function\": {\n                        \"name\": \"set_shell_config\",\n                        \"description\": \"Set a field in the desktop graphical shell config file. Must only be used after `get_shell_config`.\",\n                        \"parameters\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"key\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The key to set, e.g. `bar.borderless`. MUST NOT BE GUESSED, use `get_shell_config` to see what keys are available before setting.\",\n                                },\n                                \"value\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The value to set, e.g. `true`\"\n                                }\n                            },\n                            \"required\": [\"key\", \"value\"]\n                        }\n                    }\n                },\n                {\n                    \"type\": \"function\",\n                    \"function\": {\n                        \"name\": \"run_shell_command\",\n                        \"description\": \"Run a shell command in bash and get its output. Use this only for quick commands that don't require user interaction. For commands that require interaction, ask the user to run manually instead.\",\n                        \"parameters\": {\n                            \"type\": \"object\",\n                            \"properties\": {\n                                \"command\": {\n                                    \"type\": \"string\",\n                                    \"description\": \"The bash command to run\",\n                                },\n                            },\n                            \"required\": [\"command\"]\n                        }\n                    },\n                },\n            ],\n            \"search\": [],\n            \"none\": [],\n        }\n    }\n    property list<var> availableTools: Object.keys(root.tools[models[currentModelId]?.api_format])\n    property var toolDescriptions: {\n        \"functions\": Translation.tr(\"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\"),\n        \"search\": Translation.tr(\"Gives the model search capabilities (immediately)\"),\n        \"none\": Translation.tr(\"Disable tools\")\n    }\n\n    // Model properties:\n    // - name: Name of the model\n    // - icon: Icon name of the model\n    // - description: Description of the model\n    // - endpoint: Endpoint of the model\n    // - model: Model name of the model\n    // - requires_key: Whether the model requires an API key\n    // - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key.\n    // - key_get_link: Link to get an API key\n    // - key_get_description: Description of pricing and how to get an API key\n    // - api_format: The API format of the model. Can be \"openai\" or \"gemini\". Default is \"openai\".\n    // - extraParams: Extra parameters to be passed to the model. This is a JSON object.\n    property var models: Config.options.policies.ai === 2 ? {} : {\n        \"gemini-2.5-flash\": aiModelComponent.createObject(this, {\n            \"name\": \"Gemini 2.5 Flash\",\n            \"icon\": \"google-gemini-symbolic\",\n            \"description\": Translation.tr(\"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\"),\n            \"homepage\": \"https://aistudio.google.com\",\n            \"endpoint\": \"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:streamGenerateContent\",\n            \"model\": \"gemini-2.5-flash\",\n            \"requires_key\": true,\n            \"key_id\": \"gemini\",\n            \"key_get_link\": \"https://aistudio.google.com/app/apikey\",\n            \"key_get_description\": Translation.tr(\"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\"),\n            \"api_format\": \"gemini\",\n        }),\n        \"gemini-3-flash\": aiModelComponent.createObject(this, {\n            \"name\": \"Gemini 3 Flash\",\n            \"icon\": \"google-gemini-symbolic\",\n            \"description\": Translation.tr(\"Online | Google's model\\nPro-level intelligence at the speed and pricing of Flash.\"),\n            \"homepage\": \"https://aistudio.google.com\",\n            \"endpoint\": \"https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:streamGenerateContent\",\n            \"model\": \"gemini-3-flash-preview\",\n            \"requires_key\": true,\n            \"key_id\": \"gemini\",\n            \"key_get_link\": \"https://aistudio.google.com/app/apikey\",\n            \"key_get_description\": Translation.tr(\"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\"),\n            \"api_format\": \"gemini\",\n        }),\n        \"mistral-medium-3\": aiModelComponent.createObject(this, {\n            \"name\": \"Mistral Medium 3\",\n            \"icon\": \"mistral-symbolic\",\n            \"description\": Translation.tr(\"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\").arg(\"Mistral\"),\n            \"homepage\": \"https://mistral.ai/news/mistral-medium-3\",\n            \"endpoint\": \"https://api.mistral.ai/v1/chat/completions\",\n            \"model\": \"mistral-medium-2505\",\n            \"requires_key\": true,\n            \"key_id\": \"mistral\",\n            \"key_get_link\": \"https://console.mistral.ai/api-keys\",\n            \"key_get_description\": Translation.tr(\"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\"),\n            \"api_format\": \"mistral\",\n        }),\n    }\n    property var modelList: Object.keys(root.models)\n    property var currentModelId: Persistent.states?.ai?.model || modelList[0]\n\n    property var apiStrategies: {\n        \"openai\": openaiApiStrategy.createObject(this),\n        \"gemini\": geminiApiStrategy.createObject(this),\n        \"mistral\": mistralApiStrategy.createObject(this),\n    }\n    property ApiStrategy currentApiStrategy: apiStrategies[models[currentModelId]?.api_format || \"openai\"]\n\n    function addUserModels() {\n        (Config?.options.ai?.extraModels ?? []).forEach(model => {\n            const safeModelName = root.safeModelName(model[\"model\"]);\n            root.addModel(safeModelName, model)\n        });\n    }\n\n    Connections {\n        target: Config\n        function onReadyChanged() {\n            if (!Config.ready) return;\n            root.addUserModels()\n        }\n    }\n\n    property string requestScriptFilePath: \"/tmp/quickshell/ai/request.sh\"\n    property string pendingFilePath: \"\"\n\n    Component.onCompleted: {\n        setModel(currentModelId, false, false); // Do necessary setup for model\n        root.addUserModels() // Config onReadyChanged above might not fire if config is loaded before this service\n    }\n\n    function guessModelLogo(model) {\n        if (model.includes(\"llama\")) return \"ollama-symbolic\";\n        if (model.includes(\"gemma\")) return \"google-gemini-symbolic\";\n        if (model.includes(\"deepseek\")) return \"deepseek-symbolic\";\n        if (/^phi\\d*:/i.test(model)) return \"microsoft-symbolic\";\n        return \"ollama-symbolic\";\n    }\n\n    function guessModelName(model) {\n        const replaced = model.replace(/-/g, ' ').replace(/:/g, ' ');\n        let words = replaced.split(' ');\n        words[words.length - 1] = words[words.length - 1].replace(/(\\d+)b$/, (_, num) => `${num}B`)\n        words = words.map((word) => {\n            return (word.charAt(0).toUpperCase() + word.slice(1))\n        });\n        if (words[words.length - 1] === \"Latest\") words.pop();\n        else words[words.length - 1] = `(${words[words.length - 1]})`; // Surround the last word with square brackets\n        const result = words.join(' ');\n        return result;\n    }\n\n    function addModel(modelName, data) {\n        root.models = Object.assign({}, root.models, {\n            [modelName]: aiModelComponent.createObject(this, data)\n        });\n    }\n\n    Process {\n        id: getOllamaModels\n        running: true\n        command: [\"bash\", \"-c\", `${Directories.scriptPath}/ai/show-installed-ollama-models.sh`.replace(/file:\\/\\//, \"\")]\n        stdout: SplitParser {\n            onRead: data => {\n                try {\n                    if (data.length === 0) return;\n                    const dataJson = JSON.parse(data);\n                    root.modelList = [...root.modelList, ...dataJson];\n                    dataJson.forEach(model => {\n                        const safeModelName = root.safeModelName(model);\n                        root.addModel(safeModelName, {\n                            \"name\": guessModelName(model),\n                            \"icon\": guessModelLogo(model),\n                            \"description\": Translation.tr(\"Local Ollama model | %1\").arg(model),\n                            \"homepage\": `https://ollama.com/library/${model}`,\n                            \"endpoint\": \"http://localhost:11434/v1/chat/completions\",\n                            \"model\": model,\n                            \"requires_key\": false,\n                        })\n                    });\n\n                    root.modelList = Object.keys(root.models);\n\n                } catch (e) {\n                    console.log(\"Could not fetch Ollama models:\", e);\n                }\n            }\n        }\n    }\n\n    Process {\n        id: getDefaultPrompts\n        running: true\n        command: [\"ls\", \"-1\", Directories.defaultAiPrompts]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (text.length === 0) return;\n                root.defaultPrompts = text.split(\"\\n\")\n                    .filter(fileName => fileName.endsWith(\".md\") || fileName.endsWith(\".txt\"))\n                    .map(fileName => `${Directories.defaultAiPrompts}/${fileName}`)\n            }\n        }\n    }\n\n    Process {\n        id: getUserPrompts\n        running: true\n        command: [\"ls\", \"-1\", Directories.userAiPrompts]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (text.length === 0) return;\n                root.userPrompts = text.split(\"\\n\")\n                    .filter(fileName => fileName.endsWith(\".md\") || fileName.endsWith(\".txt\"))\n                    .map(fileName => `${Directories.userAiPrompts}/${fileName}`)\n            }\n        }\n    }\n\n    Process {\n        id: getSavedChats\n        running: true\n        command: [\"ls\", \"-1\", Directories.aiChats]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (text.length === 0) return;\n                root.savedChats = text.split(\"\\n\")\n                    .filter(fileName => fileName.endsWith(\".json\"))\n                    .map(fileName => `${Directories.aiChats}/${fileName}`)\n            }\n        }\n    }\n\n    FileView {\n        id: promptLoader\n        watchChanges: false;\n        onLoadedChanged: {\n            if (!promptLoader.loaded) return;\n            Config.options.ai.systemPrompt = promptLoader.text();\n            root.addMessage(Translation.tr(\"Loaded the following system prompt\\n\\n---\\n\\n%1\").arg(Config.options.ai.systemPrompt), root.interfaceRole);\n        }\n    }\n\n    function printPrompt() {\n        root.addMessage(Translation.tr(\"The current system prompt is\\n\\n---\\n\\n%1\").arg(Config.options.ai.systemPrompt), root.interfaceRole);\n    }\n\n    function loadPrompt(filePath) {\n        promptLoader.path = \"\" // Unload\n        promptLoader.path = filePath; // Load\n        promptLoader.reload();\n    }\n\n    function addMessage(message, role) {\n        if (message.length === 0) return;\n        const aiMessage = aiMessageComponent.createObject(root, {\n            \"role\": role,\n            \"content\": message,\n            \"rawContent\": message,\n            \"thinking\": false,\n            \"done\": true,\n        });\n        const id = idForMessage(aiMessage);\n        root.messageIDs = [...root.messageIDs, id];\n        root.messageByID[id] = aiMessage;\n    }\n\n    function removeMessage(index) {\n        if (index < 0 || index >= messageIDs.length) return;\n        const id = root.messageIDs[index];\n        root.messageIDs.splice(index, 1);\n        root.messageIDs = [...root.messageIDs];\n        delete root.messageByID[id];\n    }\n\n    function addApiKeyAdvice(model) {\n        root.addMessage(\n            Translation.tr('To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \"get\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3')\n                .arg(model.name).arg(model.key_get_link).arg(model.key_get_description ?? Translation.tr(\"<i>No further instruction provided</i>\")).arg(\"/key\"), \n            Ai.interfaceRole\n        );\n    }\n\n    function getModel() {\n        return models[currentModelId];\n    }\n\n    function setModel(modelId, feedback = true, setPersistentState = true) {\n        if (!modelId) modelId = \"\"\n        modelId = modelId.toLowerCase()\n        if (modelList.indexOf(modelId) !== -1) {\n            const model = models[modelId]\n            // See if policy prevents online models\n            if (Config.options.policies.ai === 2 && !model.endpoint.includes(\"localhost\")) {\n                root.addMessage(\n                    Translation.tr(\"Online models disallowed\\n\\nControlled by `policies.ai` config option\"),\n                    root.interfaceRole\n                );\n                return;\n            }\n            if (setPersistentState) Persistent.states.ai.model = modelId;\n            if (feedback) root.addMessage(Translation.tr(\"Model set to %1\").arg(model.name), root.interfaceRole);\n            if (model.requires_key) {\n                // If key not there show advice\n                if (root.apiKeysLoaded && (!root.apiKeys[model.key_id] || root.apiKeys[model.key_id].length === 0)) {\n                    root.addApiKeyAdvice(model)\n                }\n            }\n        } else {\n            if (feedback) root.addMessage(Translation.tr(\"Invalid model. Supported: \\n```\\n\") + modelList.join(\"\\n```\\n```\\n\"), Ai.interfaceRole) + \"\\n```\"\n        }\n    }\n\n    function setTool(tool) {\n        if (!root.tools[models[currentModelId]?.api_format] || !(tool in root.tools[models[currentModelId]?.api_format])) {\n            root.addMessage(Translation.tr(\"Invalid tool. Supported tools:\\n- %1\").arg(root.availableTools.join(\"\\n- \")), root.interfaceRole);\n            return false;\n        }\n        Config.options.ai.tool = tool;\n        return true;\n    }\n    \n    function getTemperature() {\n        return root.temperature;\n    }\n\n    function setTemperature(value) {\n        if (value == NaN || value < 0 || value > 2) {\n            root.addMessage(Translation.tr(\"Temperature must be between 0 and 2\"), Ai.interfaceRole);\n            return;\n        }\n        Persistent.states.ai.temperature = value;\n        root.temperature = value;\n        root.addMessage(Translation.tr(\"Temperature set to %1\").arg(value), Ai.interfaceRole);\n    }\n\n    function setApiKey(key) {\n        const model = models[currentModelId];\n        if (!model.requires_key) {\n            root.addMessage(Translation.tr(\"%1 does not require an API key\").arg(model.name), Ai.interfaceRole);\n            return;\n        }\n        if (!key || key.length === 0) {\n            const model = models[currentModelId];\n            root.addApiKeyAdvice(model)\n            return;\n        }\n        KeyringStorage.setNestedField([\"apiKeys\", model.key_id], key.trim());\n        root.addMessage(Translation.tr(\"API key set for %1\").arg(model.name), Ai.interfaceRole);\n    }\n\n    function printApiKey() {\n        const model = models[currentModelId];\n        if (model.requires_key) {\n            const key = root.apiKeys[model.key_id];\n            if (key) {\n                root.addMessage(Translation.tr(\"API key:\\n\\n```txt\\n%1\\n```\").arg(key), Ai.interfaceRole);\n            } else {\n                root.addMessage(Translation.tr(\"No API key set for %1\").arg(model.name), Ai.interfaceRole);\n            }\n        } else {\n            root.addMessage(Translation.tr(\"%1 does not require an API key\").arg(model.name), Ai.interfaceRole);\n        }\n    }\n\n    function printTemperature() {\n        root.addMessage(Translation.tr(\"Temperature: %1\").arg(root.temperature), Ai.interfaceRole);\n    }\n\n    function clearMessages() {\n        root.messageIDs = [];\n        root.messageByID = ({});\n        root.tokenCount.input = -1;\n        root.tokenCount.output = -1;\n        root.tokenCount.total = -1;\n    }\n\n    FileView {\n        id: requesterScriptFile\n    }\n\n    Process {\n        id: requester\n        property list<string> baseCommand: [\"bash\"]\n        property AiMessageData message\n        property ApiStrategy currentStrategy\n\n        function markDone() {\n            requester.message.done = true;\n            if (root.postResponseHook) {\n                root.postResponseHook();\n                root.postResponseHook = null; // Reset hook after use\n            }\n            root.saveChat(\"lastSession\")\n            root.responseFinished()\n        }\n\n        function makeRequest() {\n            const model = models[currentModelId];\n\n            // Fetch API keys if needed\n            if (model?.requires_key && !KeyringStorage.loaded) KeyringStorage.fetchKeyringData();\n            \n            requester.currentStrategy = root.currentApiStrategy;\n            requester.currentStrategy.reset(); // Reset strategy state\n\n            /* Put API key in environment variable */\n            if (model.requires_key) requester.environment[`${root.apiKeyEnvVarName}`] = root.apiKeys ? (root.apiKeys[model.key_id] ?? \"\") : \"\"\n\n            /* Build endpoint, request data */\n            const endpoint = root.currentApiStrategy.buildEndpoint(model);\n            const messageArray = root.messageIDs.map(id => root.messageByID[id]);\n            const filteredMessageArray = messageArray.filter(message => message.role !== Ai.interfaceRole);\n            const data = root.currentApiStrategy.buildRequestData(model, filteredMessageArray, root.systemPrompt, root.temperature, root.tools[model.api_format][root.currentTool], root.pendingFilePath);\n            // console.log(\"[Ai] Request data: \", JSON.stringify(data, null, 2));\n\n            let requestHeaders = {\n                \"Content-Type\": \"application/json\",\n            }\n            \n            /* Create local message object */\n            requester.message = root.aiMessageComponent.createObject(root, {\n                \"role\": \"assistant\",\n                \"model\": currentModelId,\n                \"content\": \"\",\n                \"rawContent\": \"\",\n                \"thinking\": true,\n                \"done\": false,\n            });\n            const id = idForMessage(requester.message);\n            root.messageIDs = [...root.messageIDs, id];\n            root.messageByID[id] = requester.message;\n\n            /* Build header string for curl */ \n            let headerString = Object.entries(requestHeaders)\n                .filter(([k, v]) => v && v.length > 0)\n                .map(([k, v]) => `-H '${k}: ${v}'`)\n                .join(' ');\n\n            // console.log(\"Request headers: \", JSON.stringify(requestHeaders));\n            // console.log(\"Header string: \", headerString);\n\n            /* Get authorization header from strategy */\n            const authHeader = requester.currentStrategy.buildAuthorizationHeader(root.apiKeyEnvVarName);\n            \n            /* Script shebang */\n            const scriptShebang = \"#!/usr/bin/env bash\\n\";\n\n            /* Create extra setup when there's an attached file */\n            let scriptFileSetupContent = \"\"\n            if (root.pendingFilePath && root.pendingFilePath.length > 0) {\n                requester.message.localFilePath = root.pendingFilePath;\n                scriptFileSetupContent = requester.currentStrategy.buildScriptFileSetup(root.pendingFilePath);\n                root.pendingFilePath = \"\"\n            }\n\n            /* Create command string */\n            let scriptRequestContent = \"\"\n            scriptRequestContent += `curl --no-buffer \"${endpoint}\"`\n                + ` ${headerString}`\n                + (authHeader ? ` ${authHeader}` : \"\")\n                + ` --data '${CF.StringUtils.shellSingleQuoteEscape(JSON.stringify(data))}'`\n                + \"\\n\"\n            \n            /* Send the request */\n            const scriptContent = requester.currentStrategy.finalizeScriptContent(scriptShebang + scriptFileSetupContent + scriptRequestContent)\n            const shellScriptPath = CF.FileUtils.trimFileProtocol(root.requestScriptFilePath)\n            requesterScriptFile.path = Qt.resolvedUrl(shellScriptPath)\n            requesterScriptFile.setText(scriptContent)\n            requester.command = baseCommand.concat([shellScriptPath]);\n            requester.running = true\n        }\n\n        stdout: SplitParser {\n            onRead: data => {\n                if (data.length === 0) return;\n                if (requester.message.thinking) requester.message.thinking = false;\n                // console.log(\"[Ai] Raw response line: \", data);\n\n                // Handle response line\n                try {\n                    const result = requester.currentStrategy.parseResponseLine(data, requester.message);\n                    // console.log(\"[Ai] Parsed response result: \", JSON.stringify(result, null, 2));\n\n                    if (result.functionCall) {\n                        requester.message.functionCall = result.functionCall;\n                        root.handleFunctionCall(result.functionCall.name, result.functionCall.args, requester.message);\n                    }\n                    if (result.tokenUsage) {\n                        root.tokenCount.input = result.tokenUsage.input;\n                        root.tokenCount.output = result.tokenUsage.output;\n                        root.tokenCount.total = result.tokenUsage.total;\n                    }\n                    if (result.finished) {\n                        requester.markDone();\n                    }\n                    \n                } catch (e) {\n                    console.log(\"[AI] Could not parse response: \", e);\n                    requester.message.rawContent += data;\n                    requester.message.content += data;\n                }\n            }\n        }\n\n        onExited: (exitCode, exitStatus) => {\n            const result = requester.currentStrategy.onRequestFinished(requester.message);\n            \n            if (result.finished) {\n                requester.markDone();\n            } else if (!requester.message.done) {\n                requester.markDone();\n            }\n\n            // Handle error responses\n            if (requester.message.content.includes(\"API key not valid\")) {\n                root.addApiKeyAdvice(models[requester.message.model]);\n            }\n        }\n    }\n\n    function sendUserMessage(message) {\n        if (message.length === 0) return;\n        root.addMessage(message, \"user\");\n        requester.makeRequest();\n    }\n\n    function attachFile(filePath: string) {\n        root.pendingFilePath = CF.FileUtils.trimFileProtocol(filePath);\n    }\n\n    function regenerate(messageIndex) {\n        if (messageIndex < 0 || messageIndex >= messageIDs.length) return;\n        const id = root.messageIDs[messageIndex];\n        const message = root.messageByID[id];\n        if (message.role !== \"assistant\") return;\n        // Remove all messages after this one\n        for (let i = root.messageIDs.length - 1; i >= messageIndex; i--) {\n            root.removeMessage(i);\n        }\n        requester.makeRequest();\n    }\n\n    function createFunctionOutputMessage(name, output, includeOutputInChat = true) {\n        return aiMessageComponent.createObject(root, {\n            \"role\": \"user\",\n            \"content\": `[[ Output of ${name} ]]${includeOutputInChat ? (\"\\n\\n<think>\\n\" + output + \"\\n</think>\") : \"\"}`,\n            \"rawContent\": `[[ Output of ${name} ]]${includeOutputInChat ? (\"\\n\\n<think>\\n\" + output + \"\\n</think>\") : \"\"}`,\n            \"functionName\": name,\n            \"functionResponse\": output,\n            \"thinking\": false,\n            \"done\": true,\n            // \"visibleToUser\": false,\n        });\n    }\n\n    function addFunctionOutputMessage(name, output) {\n        const aiMessage = createFunctionOutputMessage(name, output);\n        const id = idForMessage(aiMessage);\n        root.messageIDs = [...root.messageIDs, id];\n        root.messageByID[id] = aiMessage;\n    }\n\n    function rejectCommand(message: AiMessageData) {\n        if (!message.functionPending) return;\n        message.functionPending = false; // User decided, no more \"thinking\"\n        addFunctionOutputMessage(message.functionName, Translation.tr(\"Command rejected by user\"))\n    }\n\n    function approveCommand(message: AiMessageData) {\n        if (!message.functionPending) return;\n        message.functionPending = false; // User decided, no more \"thinking\"\n\n        const responseMessage = createFunctionOutputMessage(message.functionName, \"\", false);\n        const id = idForMessage(responseMessage);\n        root.messageIDs = [...root.messageIDs, id];\n        root.messageByID[id] = responseMessage;\n\n        commandExecutionProc.message = responseMessage;\n        commandExecutionProc.baseMessageContent = responseMessage.content;\n        commandExecutionProc.shellCommand = message.functionCall.args.command;\n        commandExecutionProc.running = true; // Start the command execution\n    }\n\n    Process {\n        id: commandExecutionProc\n        property string shellCommand: \"\"\n        property AiMessageData message\n        property string baseMessageContent: \"\"\n        command: [\"bash\", \"-c\", shellCommand]\n        stdout: SplitParser {\n            onRead: (output) => {\n                commandExecutionProc.message.functionResponse += output + \"\\n\\n\";\n                const updatedContent = commandExecutionProc.baseMessageContent + `\\n\\n<think>\\n<tt>${commandExecutionProc.message.functionResponse}</tt>\\n</think>`;\n                commandExecutionProc.message.rawContent = updatedContent;\n                commandExecutionProc.message.content = updatedContent;\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            commandExecutionProc.message.functionResponse += `[[ Command exited with code ${exitCode} (${exitStatus}) ]]\\n`;\n            requester.makeRequest(); // Continue\n        }\n    }\n\n    function handleFunctionCall(name, args: var, message: AiMessageData) {\n        if (name === \"switch_to_search_mode\") {\n            const modelId = root.currentModelId;\n            root.currentTool = \"search\"\n            root.postResponseHook = () => { root.currentTool = \"functions\" }\n            addFunctionOutputMessage(name, Translation.tr(\"Switched to search mode. Continue with the user's request.\"))\n            requester.makeRequest();\n        } else if (name === \"get_shell_config\") {\n            const configJson = CF.ObjectUtils.toPlainObject(Config.options)\n            addFunctionOutputMessage(name, JSON.stringify(configJson));\n            requester.makeRequest();\n        } else if (name === \"set_shell_config\") {\n            if (!args.key || !args.value) {\n                addFunctionOutputMessage(name, Translation.tr(\"Invalid arguments. Must provide `key` and `value`.\"));\n                return;\n            }\n            const key = args.key;\n            const value = args.value;\n            Config.setNestedValue(key, value);\n        } else if (name === \"run_shell_command\") {\n            if (!args.command || args.command.length === 0) {\n                addFunctionOutputMessage(name, Translation.tr(\"Invalid arguments. Must provide `command`.\"));\n                return;\n            }\n            const contentToAppend = `\\n\\n**Command execution request**\\n\\n\\`\\`\\`command\\n${args.command}\\n\\`\\`\\``;\n            message.rawContent += contentToAppend;\n            message.content += contentToAppend;\n            message.functionPending = true; // Use thinking to indicate the command is waiting for approval\n        }\n        else root.addMessage(Translation.tr(\"Unknown function call: %1\").arg(name), \"assistant\");\n    }\n\n    function chatToJson() {\n        return root.messageIDs.map(id => {\n            const message = root.messageByID[id]\n            return ({\n                \"role\": message.role,\n                \"rawContent\": message.rawContent,\n                \"fileMimeType\": message.fileMimeType,\n                \"fileUri\": message.fileUri,\n                \"localFilePath\": message.localFilePath,\n                \"model\": message.model,\n                \"thinking\": false,\n                \"done\": true,\n                \"annotations\": message.annotations,\n                \"annotationSources\": message.annotationSources,\n                \"functionName\": message.functionName,\n                \"functionCall\": message.functionCall,\n                \"functionResponse\": message.functionResponse,\n                \"visibleToUser\": message.visibleToUser,\n            })\n        })\n    }\n\n    FileView {\n        id: chatSaveFile\n        property string chatName: \"\"\n        path: chatName.length > 0 ? `${Directories.aiChats}/${chatName}.json` : \"\"\n        blockLoading: true // Prevent race conditions\n    }\n\n    /**\n     * Saves chat to a JSON list of message objects.\n     * @param chatName name of the chat\n     */\n    function saveChat(chatName) {\n        chatSaveFile.chatName = chatName.trim()\n        const saveContent = JSON.stringify(root.chatToJson())\n        chatSaveFile.setText(saveContent)\n        getSavedChats.running = true;\n    }\n\n    /**\n     * Loads chat from a JSON list of message objects.\n     * @param chatName name of the chat\n     */\n    function loadChat(chatName) {\n        try {\n            chatSaveFile.chatName = chatName.trim()\n            chatSaveFile.reload()\n            const saveContent = chatSaveFile.text()\n            // console.log(saveContent)\n            const saveData = JSON.parse(saveContent)\n            root.clearMessages()\n            root.messageIDs = saveData.map((_, i) => {\n                return i\n            })\n            // console.log(JSON.stringify(messageIDs))\n            for (let i = 0; i < saveData.length; i++) {\n                const message = saveData[i];\n                root.messageByID[i] = root.aiMessageComponent.createObject(root, {\n                    \"role\": message.role,\n                    \"rawContent\": message.rawContent,\n                    \"content\": message.rawContent,\n                    \"fileMimeType\": message.fileMimeType,\n                    \"fileUri\": message.fileUri,\n                    \"localFilePath\": message.localFilePath,\n                    \"model\": message.model,\n                    \"thinking\": message.thinking,\n                    \"done\": message.done,\n                    \"annotations\": message.annotations,\n                    \"annotationSources\": message.annotationSources,\n                    \"functionName\": message.functionName,\n                    \"functionCall\": message.functionCall,\n                    \"functionResponse\": message.functionResponse,\n                    \"visibleToUser\": message.visibleToUser,\n                });\n            }\n        } catch (e) {\n            console.log(\"[AI] Could not load chat: \", e);\n        } finally {\n            getSavedChats.running = true;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/AppSearch.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport Quickshell\n\n/**\n * - Eases fuzzy searching for applications by name\n * - Guesses icon name for window class name\n */\nSingleton {\n    id: root\n    property bool sloppySearch: Config.options?.search.sloppy ?? false\n    property real scoreThreshold: 0.2\n    property var substitutions: ({\n        \"code-url-handler\": \"visual-studio-code\",\n        \"Code\": \"visual-studio-code\",\n        \"gnome-tweaks\": \"org.gnome.tweaks\",\n        \"pavucontrol-qt\": \"pavucontrol\",\n        \"wps\": \"wps-office2019-kprometheus\",\n        \"wpsoffice\": \"wps-office2019-kprometheus\",\n        \"footclient\": \"foot\",\n    })\n    property var regexSubstitutions: [\n        {\n            \"regex\": /^steam_app_(\\d+)$/,\n            \"replace\": \"steam_icon_$1\"\n        },\n        {\n            \"regex\": /Minecraft.*/,\n            \"replace\": \"minecraft\"\n        },\n        {\n            \"regex\": /.*polkit.*/,\n            \"replace\": \"system-lock-screen\"\n        },\n        {\n            \"regex\": /gcr.prompter/,\n            \"replace\": \"system-lock-screen\"\n        }\n    ]\n\n    // Deduped list to fix double icons\n    readonly property list<DesktopEntry> list: Array.from(DesktopEntries.applications.values)\n        .filter((app, index, self) => \n            index === self.findIndex((t) => (\n                t.id === app.id\n            ))\n    )\n    \n    readonly property var preppedNames: list.map(a => ({\n        name: Fuzzy.prepare(`${a.name} `),\n        entry: a\n    }))\n\n    readonly property var preppedIcons: list.map(a => ({\n        name: Fuzzy.prepare(`${a.icon} `),\n        entry: a\n    }))\n\n    function fuzzyQuery(search: string): var { // Idk why list<DesktopEntry> doesn't work\n        if (root.sloppySearch) {\n            const results = list.map(obj => ({\n                entry: obj,\n                score: Levendist.computeScore(obj.name.toLowerCase(), search.toLowerCase())\n            })).filter(item => item.score > root.scoreThreshold)\n                .sort((a, b) => b.score - a.score)\n            return results\n                .map(item => item.entry)\n        }\n\n        return Fuzzy.go(search, preppedNames, {\n            all: true,\n            key: \"name\"\n        }).map(r => {\n            return r.obj.entry\n        });\n    }\n\n    function iconExists(iconName) {\n        if (!iconName || iconName.length == 0) return false;\n        return (Quickshell.iconPath(iconName, true).length > 0) \n            && !iconName.includes(\"image-missing\");\n    }\n\n    function getReverseDomainNameAppName(str) {\n        return str.split('.').slice(-1)[0]\n    }\n\n    function getKebabNormalizedAppName(str) {\n        return str.toLowerCase().replace(/\\s+/g, \"-\");\n    }\n\n    function getUndescoreToKebabAppName(str) {\n        return str.toLowerCase().replace(/_/g, \"-\");\n    }\n\n    function guessIcon(str) {\n        if (!str || str.length == 0) return \"image-missing\";\n\n        // Quickshell's desktop entry lookup\n        const entry = DesktopEntries.byId(str);\n        if (entry) return entry.icon;\n\n        // Normal substitutions\n        if (substitutions[str]) return substitutions[str];\n        if (substitutions[str.toLowerCase()]) return substitutions[str.toLowerCase()];\n\n        // Regex substitutions\n        for (let i = 0; i < regexSubstitutions.length; i++) {\n            const substitution = regexSubstitutions[i];\n            const replacedName = str.replace(\n                substitution.regex,\n                substitution.replace,\n            );\n            if (replacedName != str) return replacedName;\n        }\n\n        // Icon exists -> return as is\n        if (iconExists(str)) return str;\n\n\n        // Simple guesses\n        const lowercased = str.toLowerCase();\n        if (iconExists(lowercased)) return lowercased;\n\n        const reverseDomainNameAppName = getReverseDomainNameAppName(str);\n        if (iconExists(reverseDomainNameAppName)) return reverseDomainNameAppName;\n\n        const lowercasedDomainNameAppName = reverseDomainNameAppName.toLowerCase();\n        if (iconExists(lowercasedDomainNameAppName)) return lowercasedDomainNameAppName;\n\n        const kebabNormalizedGuess = getKebabNormalizedAppName(str);\n        if (iconExists(kebabNormalizedGuess)) return kebabNormalizedGuess;\n\n        const undescoreToKebabGuess = getUndescoreToKebabAppName(str);\n        if (iconExists(undescoreToKebabGuess)) return undescoreToKebabGuess;\n\n        // Search in desktop entries\n        const iconSearchResults = Fuzzy.go(str, preppedIcons, {\n            all: true,\n            key: \"name\"\n        }).map(r => {\n            return r.obj.entry\n        });\n        if (iconSearchResults.length > 0) {\n            const guess = iconSearchResults[0].icon\n            if (iconExists(guess)) return guess;\n        }\n\n        const nameSearchResults = root.fuzzyQuery(str);\n        if (nameSearchResults.length > 0) {\n            const guess = nameSearchResults[0].icon\n            if (iconExists(guess)) return guess;\n        }\n\n        // Quickshell's desktop entry lookup\n        const heuristicEntry = DesktopEntries.heuristicLookup(str);\n        if (heuristicEntry) return heuristicEntry.icon;\n\n        // Give up\n        return \"application-x-executable\";\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Audio.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.Pipewire\n\n/**\n * A nice wrapper for default Pipewire audio sink and source.\n */\nSingleton {\n    id: root\n\n    // Misc props\n    property bool ready: Pipewire.defaultAudioSink?.ready ?? false\n    property PwNode sink: Pipewire.defaultAudioSink\n    property PwNode source: Pipewire.defaultAudioSource\n    readonly property real hardMaxValue: 2.00 // People keep joking about setting volume to 5172% so...\n    property string audioTheme: Config.options.sounds.theme\n    property real value: sink?.audio.volume ?? 0\n    \n    function friendlyDeviceName(node) {\n        return (node.nickname || node.description || Translation.tr(\"Unknown\"));\n    }\n    function appNodeDisplayName(node) {\n        return (node.properties[\"application.name\"] || node.description || node.name)\n    }\n\n    // Lists\n    function correctType(node, isSink) {\n        return (node.isSink === isSink) && node.audio\n    }\n    function appNodes(isSink) {\n        return Pipewire.nodes.values.filter((node) => { // Should be list<PwNode> but it breaks ScriptModel\n            return root.correctType(node, isSink) && node.isStream\n        })\n    }\n    function devices(isSink) {\n        return Pipewire.nodes.values.filter(node => {\n            return root.correctType(node, isSink) && !node.isStream\n        })\n    }\n    readonly property list<var> outputAppNodes: root.appNodes(true)\n    readonly property list<var> inputAppNodes: root.appNodes(false)\n    readonly property list<var> outputDevices: root.devices(true)\n    readonly property list<var> inputDevices: root.devices(false)\n\n    // Signals\n    signal sinkProtectionTriggered(string reason);\n\n    // Controls\n    function toggleMute() {\n        Audio.sink.audio.muted = !Audio.sink.audio.muted\n    }\n\n    function toggleMicMute() {\n        Audio.source.audio.muted = !Audio.source.audio.muted\n    }\n\n    function incrementVolume() {\n        const currentVolume = Audio.value;\n        const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;\n        Audio.sink.audio.volume = Math.min(1, Audio.sink.audio.volume + step);\n    }\n    \n    function decrementVolume() {\n        const currentVolume = Audio.value;\n        const step = currentVolume < 0.1 ? 0.01 : 0.02 || 0.2;\n        Audio.sink.audio.volume -= step;\n    }\n\n    function setDefaultSink(node) {\n        Pipewire.preferredDefaultAudioSink = node;\n    }\n\n    function setDefaultSource(node) {\n        Pipewire.preferredDefaultAudioSource = node;\n    }\n\n    // Internals\n    PwObjectTracker {\n        objects: [sink, source]\n    }\n\n    Connections { // Protection against sudden volume changes\n        target: sink?.audio ?? null\n        property bool lastReady: false\n        property real lastVolume: 0\n        function onVolumeChanged() {\n            if (!Config.options.audio.protection.enable) return;\n            const newVolume = sink.audio.volume;\n            // when resuming from suspend, we should not write volume to avoid pipewire volume reset issues\n            if (isNaN(newVolume) || newVolume === undefined || newVolume === null) {\n                lastReady = false;\n                lastVolume = 0;\n                return;\n            }\n            if (!lastReady) {\n                lastVolume = newVolume;\n                lastReady = true;\n                return;\n            }\n            const maxAllowedIncrease = Config.options.audio.protection.maxAllowedIncrease / 100; \n            const maxAllowed = Config.options.audio.protection.maxAllowed / 100;\n\n            if (newVolume - lastVolume > maxAllowedIncrease) {\n                sink.audio.volume = lastVolume;\n                root.sinkProtectionTriggered(Translation.tr(\"Illegal increment\"));\n            } else if (newVolume > maxAllowed || newVolume > root.hardMaxValue) {\n                root.sinkProtectionTriggered(Translation.tr(\"Exceeded max allowed\"));\n                sink.audio.volume = Math.min(lastVolume, maxAllowed);\n            }\n            lastVolume = sink.audio.volume;\n        }\n    }\n\n    function playSystemSound(soundName) {\n        const ogaPath = `/usr/share/sounds/${root.audioTheme}/stereo/${soundName}.oga`;\n        const oggPath = `/usr/share/sounds/${root.audioTheme}/stereo/${soundName}.ogg`;\n\n        // Try playing .oga first\n        let command = [\n            \"ffplay\",\n            \"-nodisp\",\n            \"-autoexit\",\n            ogaPath\n        ];\n        Quickshell.execDetached(command);\n\n        // Also try playing .ogg (ffplay will just fail silently if file doesn't exist)\n        command = [\n            \"ffplay\",\n            \"-nodisp\",\n            \"-autoexit\",\n            oggPath\n        ];\n        Quickshell.execDetached(command);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Battery.qml",
    "content": "pragma Singleton\n\nimport qs.services\nimport qs.modules.common\nimport Quickshell\nimport Quickshell.Services.UPower\nimport QtQuick\nimport Quickshell.Io\n\nSingleton {\n    id: root\n    property bool available: UPower.displayDevice.isLaptopBattery\n    property var chargeState: UPower.displayDevice.state\n    property bool isCharging: chargeState == UPowerDeviceState.Charging\n    property bool isPluggedIn: isCharging || chargeState == UPowerDeviceState.PendingCharge\n    property real percentage: UPower.displayDevice?.percentage ?? 1\n    readonly property bool allowAutomaticSuspend: Config.options.battery.automaticSuspend\n    readonly property bool soundEnabled: Config.options.sounds.battery\n\n    property bool isLow: available && (percentage <= Config.options.battery.low / 100)\n    property bool isCritical: available && (percentage <= Config.options.battery.critical / 100)\n    property bool isSuspending: available && (percentage <= Config.options.battery.suspend / 100)\n    property bool isFull: available && (percentage >= Config.options.battery.full / 100)\n\n    property bool isLowAndNotCharging: isLow && !isCharging\n    property bool isCriticalAndNotCharging: isCritical && !isCharging\n    property bool isSuspendingAndNotCharging: allowAutomaticSuspend && isSuspending && !isCharging\n    property bool isFullAndCharging: isFull && isCharging\n\n    property real energyRate: UPower.displayDevice.changeRate\n    property real timeToEmpty: UPower.displayDevice.timeToEmpty\n    property real timeToFull: UPower.displayDevice.timeToFull\n\n    property real health: (function() {\n        const devList = UPower.devices.values;\n        for (let i = 0; i < devList.length; ++i) {\n            const dev = devList[i];\n            if (dev.isLaptopBattery && dev.healthSupported) {\n                const health = dev.healthPercentage;\n                if (health === 0) {\n                    return 0.01;\n                } else if (health < 1) {\n                    return health * 100;\n                } else {\n                    return health;\n                }\n            }\n        }\n        return 0;\n    })()\n\n\n    onIsLowAndNotChargingChanged: {\n        if (!root.available || !isLowAndNotCharging) return;\n        Quickshell.execDetached([\n            \"notify-send\", \n            Translation.tr(\"Low battery\"), \n            Translation.tr(\"Consider plugging in your device\"), \n            \"-u\", \"critical\",\n            \"-a\", \"Shell\",\n            \"--hint=int:transient:1\",\n        ])\n\n        if (root.soundEnabled) Audio.playSystemSound(\"dialog-warning\");\n    }\n\n    onIsCriticalAndNotChargingChanged: {\n        if (!root.available || !isCriticalAndNotCharging) return;\n        Quickshell.execDetached([\n            \"notify-send\", \n            Translation.tr(\"Critically low battery\"), \n            Translation.tr(\"Please charge!\\nAutomatic suspend triggers at %1%\").arg(Config.options.battery.suspend), \n            \"-u\", \"critical\",\n            \"-a\", \"Shell\",\n            \"--hint=int:transient:1\",\n        ]);\n\n        if (root.soundEnabled) Audio.playSystemSound(\"suspend-error\");\n    }\n\n    onIsSuspendingAndNotChargingChanged: {\n        if (root.available && isSuspendingAndNotCharging) {\n            Quickshell.execDetached([\"bash\", \"-c\", `systemctl suspend || loginctl suspend`]);\n        }\n    }\n\n    onIsFullAndChargingChanged: {\n        if (!root.available || !isFullAndCharging) return;\n        Quickshell.execDetached([\n            \"notify-send\",\n            Translation.tr(\"Battery full\"),\n            Translation.tr(\"Please unplug the charger\"),\n            \"-a\", \"Shell\",\n            \"--hint=int:transient:1\",\n        ]);\n\n        if (root.soundEnabled) Audio.playSystemSound(\"complete\");\n    }\n\n    onIsPluggedInChanged: {\n        if (!root.available || !root.soundEnabled) return;\n        if (isPluggedIn) {\n            Audio.playSystemSound(\"power-plug\")\n        } else {\n            Audio.playSystemSound(\"power-unplug\")\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/BluetoothStatus.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport Quickshell\nimport Quickshell.Bluetooth\nimport Quickshell.Io\nimport QtQuick\n\nSingleton {\n    id: root\n\n    readonly property bool available: Bluetooth.adapters.values.length > 0\n    readonly property bool enabled: Bluetooth.defaultAdapter?.enabled ?? false\n    readonly property BluetoothDevice firstActiveDevice: Bluetooth.defaultAdapter?.devices.values.find(device => device.connected) ?? null\n    readonly property int activeDeviceCount: Bluetooth.defaultAdapter?.devices.values.filter(device => device.connected).length ?? 0\n    readonly property bool connected: Bluetooth.devices.values.some(d => d.connected)\n\n    function sortFunction(a, b) {\n        // Ones with meaningful names before MAC addresses\n        const macRegex = /^([0-9A-Fa-f]{2}-){5}[0-9A-Fa-f]{2}$/;\n        const aIsMac = macRegex.test(a.name);\n        const bIsMac = macRegex.test(b.name);\n        if (aIsMac !== bIsMac)\n            return aIsMac ? 1 : -1;\n\n        // Alphabetical by name\n        return a.name.localeCompare(b.name);\n    }\n    property list<var> connectedDevices: Bluetooth.devices.values.filter(d => d.connected).sort(sortFunction)\n    property list<var> pairedButNotConnectedDevices: Bluetooth.devices.values.filter(d => d.paired && !d.connected).sort(sortFunction)\n    property list<var> unpairedDevices: Bluetooth.devices.values.filter(d => !d.paired && !d.connected).sort(sortFunction)\n    property list<var> friendlyDeviceList: [\n        ...connectedDevices,\n        ...pairedButNotConnectedDevices,\n        ...unpairedDevices\n    ]\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Booru.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.services\nimport Quickshell;\nimport QtQuick;\n\n/**\n * A service for interacting with various booru APIs.\n */\nSingleton {\n    id: root\n    property Component booruResponseDataComponent: BooruResponseData {}\n\n    signal tagSuggestion(string query, var suggestions)\n    signal responseFinished()\n\n    property string failMessage: Translation.tr(\"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\")\n    property var responses: []\n    property int runningRequests: 0\n    property var defaultUserAgent: Config.options?.networking?.userAgent || \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36\"\n    property var providerList: Object.keys(providers).filter(provider => provider !== \"system\" && providers[provider].api)\n    property var providers: {\n        \"system\": { \"name\": Translation.tr(\"System\") },\n        \"yandere\": {\n            \"name\": \"yande.re\",\n            \"url\": \"https://yande.re\",\n            \"api\": \"https://yande.re/post.json\",\n            \"description\": Translation.tr(\"All-rounder | Good quality, decent quantity\"),\n            \"mapFunc\": (response) => {\n                return response.map(item => {\n                    return {\n                        \"id\": item.id,\n                        \"width\": item.width,\n                        \"height\": item.height,\n                        \"aspect_ratio\": item.width / item.height,\n                        \"tags\": item.tags,\n                        \"rating\": item.rating,\n                        \"is_nsfw\": (item.rating != 's'),\n                        \"md5\": item.md5,\n                        \"preview_url\": item.preview_url,\n                        \"sample_url\": item.sample_url ?? item.file_url,\n                        \"file_url\": item.file_url,\n                        \"file_ext\": item.file_ext,\n                        \"source\": getWorkingImageSource(item.source) ?? item.file_url,\n                    }\n                })\n            },\n            \"tagSearchTemplate\": \"https://yande.re/tag.json?order=count&limit=10&name={{query}}*\",\n            \"tagMapFunc\": (response) => {\n                return response.map(item => {\n                    return {\n                        \"name\": item.name,\n                        \"count\": item.count\n                    }\n                })\n            }\n        },\n        \"konachan\": {\n            \"name\": \"Konachan\",\n            \"url\": \"https://konachan.net\",\n            \"api\": \"https://konachan.net/post.json\",\n            \"description\": Translation.tr(\"For desktop wallpapers | Good quality\"),\n            \"mapFunc\": (response) => {\n                return response.map(item => {\n                    return {\n                        \"id\": item.id,\n                        \"width\": item.width,\n                        \"height\": item.height,\n                        \"aspect_ratio\": item.width / item.height,\n                        \"tags\": item.tags,\n                        \"rating\": item.rating,\n                        \"is_nsfw\": (item.rating != 's'),\n                        \"md5\": item.md5,\n                        \"preview_url\": item.preview_url,\n                        \"sample_url\": item.sample_url ?? item.file_url,\n                        \"file_url\": item.file_url,\n                        \"file_ext\": item.file_ext,\n                        \"source\": getWorkingImageSource(item.source) ?? item.file_url,\n                    }\n                })\n            },\n            \"tagSearchTemplate\": \"https://konachan.net/tag.json?order=count&limit=10&name={{query}}*\",\n            \"tagMapFunc\": (response) => {\n                return response.map(item => {\n                    return {\n                        \"name\": item.name,\n                        \"count\": item.count\n                    }\n                })\n            }\n        },\n        \"zerochan\": {\n            \"name\": \"Zerochan\",\n            \"url\": \"https://www.zerochan.net\",\n            \"api\": \"https://www.zerochan.net/?json\",\n            \"description\": Translation.tr(\"Clean stuff | Excellent quality, no NSFW\"),\n            \"mapFunc\": (response) => {\n                response = response.items\n                return response.map(item => {\n                    return {\n                        \"id\": item.id,\n                        \"width\": item.width,\n                        \"height\": item.height,\n                        \"aspect_ratio\": item.width / item.height,\n                        \"tags\": item.tags.join(\" \"),\n                        \"rating\": \"safe\", // Zerochan doesn't have nsfw\n                        \"is_nsfw\": false,\n                        \"md5\": item.md5,\n                        \"preview_url\": item.thumbnail,\n                        \"sample_url\": item.thumbnail,\n                        \"file_url\": item.thumbnail,\n                        \"file_ext\": \"avif\",\n                        \"source\": getWorkingImageSource(item.source) ?? item.thumbnail,\n                        \"character\": item.tag\n                    }\n                })\n            }\n        },\n        \"danbooru\": {\n            \"name\": \"Danbooru\",\n            \"url\": \"https://danbooru.donmai.us\",\n            \"api\": \"https://danbooru.donmai.us/posts.json\",\n            \"description\": Translation.tr(\"The popular one | Best quantity, but quality can vary wildly\"),\n            \"mapFunc\": (response) => {\n                return response.map(item => {\n                    return {\n                        \"id\": item.id,\n                        \"width\": item.image_width,\n                        \"height\": item.image_height,\n                        \"aspect_ratio\": item.image_width / item.image_height,\n                        \"tags\": item.tag_string,\n                        \"rating\": item.rating,\n                        \"is_nsfw\": (item.rating != 's'),\n                        \"md5\": item.md5,\n                        \"preview_url\": item.preview_file_url,\n                        \"sample_url\": item.file_url ?? item.large_file_url,\n                        \"file_url\": item.large_file_url,\n                        \"file_ext\": item.file_ext,\n                        \"source\": getWorkingImageSource(item.source) ?? item.file_url,\n                    }\n                })\n            },\n            \"tagSearchTemplate\": \"https://danbooru.donmai.us/tags.json?limit=10&search[name_matches]={{query}}*\",\n            \"tagMapFunc\": (response) => {\n                return response.map(item => {\n                    return {\n                        \"name\": item.name,\n                        \"count\": item.post_count\n                    }\n                })\n            }\n        },\n        \"gelbooru\": {\n            \"name\": \"Gelbooru\",\n            \"url\": \"https://gelbooru.com\",\n            \"api\": \"https://gelbooru.com/index.php?page=dapi&s=post&q=index&json=1\",\n            \"description\": Translation.tr(\"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\"),\n            \"mapFunc\": (response) => {\n                response = response.post\n                return response.map(item => {\n                    return {\n                        \"id\": item.id,\n                        \"width\": item.width,\n                        \"height\": item.height,\n                        \"aspect_ratio\": item.width / item.height,\n                        \"tags\": item.tags,\n                        \"rating\": item.rating.replace('general', 's').charAt(0),\n                        \"is_nsfw\": (item.rating != 's'),\n                        \"md5\": item.md5,\n                        \"preview_url\": item.preview_url,\n                        \"sample_url\": item.sample_url ?? item.file_url,\n                        \"file_url\": item.file_url,\n                        \"file_ext\": item.file_url.split('.').pop(),\n                        \"source\": getWorkingImageSource(item.source) ?? item.file_url,\n                    }\n                })\n            },\n            \"tagSearchTemplate\": \"https://gelbooru.com/index.php?page=dapi&s=tag&q=index&json=1&orderby=count&limit=10&name_pattern={{query}}%\",\n            \"tagMapFunc\": (response) => {\n                return response.tag.map(item => {\n                    return {\n                        \"name\": item.name,\n                        \"count\": item.count\n                    }\n                })\n            }\n        },\n        \"waifu.im\": {\n            \"name\": \"waifu.im\",\n            \"url\": \"https://waifu.im\",\n            \"api\": \"https://api.waifu.im/search\",\n            \"description\": Translation.tr(\"Waifus only | Excellent quality, limited quantity\"),\n            \"mapFunc\": (response) => {\n                response = response.images\n                return response.map(item => {\n                    return {\n                        \"id\": item.image_id,\n                        \"width\": item.width,\n                        \"height\": item.height,\n                        \"aspect_ratio\": item.width / item.height,\n                        \"tags\": item.tags.map(tag => {return tag.name}).join(\" \"),\n                        \"rating\": item.is_nsfw ? \"e\" : \"s\",\n                        \"is_nsfw\": item.is_nsfw,\n                        \"md5\": item.md5,\n                        \"preview_url\": item.sample_url ?? item.url, // preview_url just says access denied (maybe i fucked up and sent too many requests idk)\n                        \"sample_url\": item.url,\n                        \"file_url\": item.url,\n                        \"file_ext\": item.extension,\n                        \"source\": getWorkingImageSource(item.source) ?? item.url,\n                    }\n                })\n            },\n            \"tagSearchTemplate\": \"https://api.waifu.im/tags\",\n            \"tagMapFunc\": (response) => {\n                return [...response.versatile.map(item => {return {\"name\": item}}), \n                    ...response.nsfw.map(item => {return {\"name\": item}})]\n            }\n        },\n        \"t.alcy.cc\": {\n            \"name\": \"Alcy\",\n            \"url\": \"https://t.alcy.cc\",\n            \"api\": \"https://t.alcy.cc/\",\n            \"description\": Translation.tr(\"Large images | God tier quality, no NSFW.\"),\n            \"fixedTags\": [\n                {\n                    \"name\": \"ycy\",\n                    \"count\": \"General\"\n                },\n                {\n                    \"name\": \"moez\",\n                    \"count\": \"Moe\"\n                },\n                {\n                    \"name\": \"ysz\",\n                    \"count\": \"Genshin Impact\"\n                },\n                {\n                    \"name\": \"fj\",\n                    \"count\": \"Landscape\"\n                },\n                {\n                    \"name\": \"bd\",\n                    \"count\": \"Girl on white background\"\n                },\n                {\n                    \"name\": \"xhl\",\n                    \"count\": \"Shiggy\"\n                },\n            ],\n            \"manualParseFunc\": (responseText) => {\n                // Alcy just returns image links, each on a new line\n                const lines = responseText.trim().split('\\n');\n                return lines.map(line => {\n                    return {\n                        \"id\": Qt.md5(line),\n                        // Alcy doesn't provide dimensions and images are often of god resolution\n                        \"width\": 1000,\n                        \"height\": 1000,\n                        \"aspect_ratio\": 1,\n                        \"tags\": \"[no tags]\",\n                        \"rating\": \"s\",\n                        \"is_nsfw\": false,\n                        \"md5\": Qt.md5(line),\n                        \"preview_url\": line,\n                        \"sample_url\": line,\n                        \"file_url\": line,\n                        \"file_ext\": line.split('.').pop(),\n                        \"source\": \"\",\n                    }\n                });\n            },\n        }\n    }\n    property var currentProvider: Persistent.states.booru.provider\n\n    function getWorkingImageSource(url) {\n        if (url.includes('pximg.net')) {\n            return `https://www.pixiv.net/en/artworks/${url.substring(url.lastIndexOf('/') + 1).replace(/_p\\d+\\.(png|jpg|jpeg|gif)$/, '')}`;\n        }\n        return url;\n    }\n    \n    function setProvider(provider) {\n        provider = provider.toLowerCase()\n        if (providerList.indexOf(provider) !== -1) {\n            Persistent.states.booru.provider = provider\n            root.addSystemMessage(Translation.tr(\"Provider set to \") + providers[provider].name\n                + (provider == \"zerochan\" ? Translation.tr(\". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\") : \"\"))\n        } else {\n            root.addSystemMessage(Translation.tr(\"Invalid API provider. Supported: \\n- \") + providerList.join(\"\\n- \"))\n        }\n    }\n\n    function clearResponses() {\n        responses = []\n    }\n\n    function addSystemMessage(message) {\n        responses = [...responses, root.booruResponseDataComponent.createObject(null, {\n            \"provider\": \"system\",\n            \"tags\": [],\n            \"page\": -1,\n            \"images\": [],\n            \"message\": `${message}`\n        })]\n    }\n\n    function constructRequestUrl(tags, nsfw=true, limit=20, page=1) {\n        var provider = providers[currentProvider]\n        var baseUrl = provider.api\n        var url = baseUrl\n        var tagString = tags.join(\" \")\n        if (!nsfw && !([\"zerochan\", \"waifu.im\", \"t.alcy.cc\"].includes(currentProvider))) {\n            if (currentProvider == \"gelbooru\") \n                tagString += \" rating:general\";\n            else \n                tagString += \" rating:safe\";\n        }\n        var params = []\n        // Tags & limit\n        if (currentProvider === \"zerochan\") {\n            params.push(\"c=\" + tagString) // zerochan doesn't have search in api, so we use color\n            params.push(\"l=\" + limit)\n            params.push(\"s=\" + \"fav\")\n            params.push(\"t=\" + 1)\n            params.push(\"p=\" + page)\n        }\n        else if (currentProvider === \"waifu.im\") {\n            var tagsArray = tagString.split(\" \");\n            tagsArray.forEach(tag => {\n                params.push(\"included_tags=\" + encodeURIComponent(tag));\n            });\n            params.push(\"limit=\" + Math.min(limit, 30)) // Only admin can do > 30\n            params.push(\"is_nsfw=\" + (nsfw ? \"null\" : \"false\")) // null is random\n        }\n        else if (currentProvider === \"t.alcy.cc\") {\n            url += tagString\n            params.push(\"json\")\n            params.push(\"quantity=\" + limit)\n        }\n        else {\n            params.push(\"tags=\" + encodeURIComponent(tagString))\n            params.push(\"limit=\" + limit)\n            if (currentProvider == \"gelbooru\") {\n                params.push(\"pid=\" + page)\n            }\n            else {\n                params.push(\"page=\" + page)\n            }\n        }\n        if (baseUrl.indexOf(\"?\") === -1) {\n            url += \"?\" + params.join(\"&\")\n        } else {\n            url += \"&\" + params.join(\"&\")\n        }\n        return url\n    }\n\n    function makeRequest(tags, nsfw=false, limit=20, page=1) {\n        var url = constructRequestUrl(tags, nsfw, limit, page)\n        console.log(\"[Booru] Making request to \" + url)\n\n        const newResponse = root.booruResponseDataComponent.createObject(null, {\n            \"provider\": currentProvider,\n            \"tags\": tags,\n            \"page\": page,\n            \"images\": [],\n            \"message\": \"\"\n        })\n\n        var xhr = new XMLHttpRequest()\n        xhr.open(\"GET\", url)\n        xhr.onreadystatechange = function() {\n            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n                try {\n                    // console.log(\"[Booru] Raw response: \" + xhr.responseText)\n                    const provider = providers[currentProvider]\n                    let response;\n                    if (provider.manualParseFunc) {\n                        response = provider.manualParseFunc(xhr.responseText)\n                    } else {\n                        response = JSON.parse(xhr.responseText)\n                        response = provider.mapFunc(response)\n                    }\n                    // console.log(\"[Booru] Mapped response: \" + JSON.stringify(response))\n                    newResponse.images = response\n                    newResponse.message = response.length > 0 ? \"\" : root.failMessage\n                    \n                } catch (e) {\n                    console.log(\"[Booru] Failed to parse response: \" + e)\n                    newResponse.message = root.failMessage\n                } finally {\n                    root.runningRequests--;\n                    root.responses = [...root.responses, newResponse]\n                }\n            }\n            else if (xhr.readyState === XMLHttpRequest.DONE) {\n                console.log(\"[Booru] Request failed with status: \" + xhr.status)\n                newResponse.message = root.failMessage\n                root.runningRequests--;\n                root.responses = [...root.responses, newResponse]\n            }\n            root.responseFinished()\n        }\n\n        try {\n            // Required for danbooru\n            if (currentProvider == \"danbooru\") {\n                xhr.setRequestHeader(\"User-Agent\", defaultUserAgent)\n            }\n            else if (currentProvider == \"zerochan\") {\n                const userAgent = Config.options?.sidebar?.booru?.zerochan?.username ? `Desktop sidebar booru viewer - username: ${Config.options.sidebar.booru.zerochan.username}` : defaultUserAgent\n                xhr.setRequestHeader(\"User-Agent\", userAgent)\n            }\n            root.runningRequests++;\n            xhr.send()\n        } catch (error) {\n            console.log(\"Could not set User-Agent:\", error)\n        } \n    }\n\n    property var currentTagRequest: null\n    function triggerTagSearch(query) {\n        if (currentTagRequest) {\n            currentTagRequest.abort();\n        }\n\n        var provider = providers[currentProvider]\n        if (provider.fixedTags) {\n            root.tagSuggestion(query, provider.fixedTags)\n            return provider.fixedTags;\n        } else if (!provider.tagSearchTemplate) {\n            return\n        }\n        var url = provider.tagSearchTemplate.replace(\"{{query}}\", encodeURIComponent(query))\n\n        var xhr = new XMLHttpRequest()\n        currentTagRequest = xhr\n        xhr.open(\"GET\", url)\n        xhr.onreadystatechange = function() {\n            if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {\n                currentTagRequest = null\n                try {\n                    // console.log(\"[Booru] Raw response: \" + xhr.responseText)\n                    var response = JSON.parse(xhr.responseText)\n                    response = provider.tagMapFunc(response)\n                    // console.log(\"[Booru] Mapped response: \" + JSON.stringify(response))\n                    root.tagSuggestion(query, response)\n                } catch (e) {\n                    console.log(\"[Booru] Failed to parse response: \" + e)\n                }\n            }\n            else if (xhr.readyState === XMLHttpRequest.DONE) {\n                console.log(\"[Booru] Request failed with status: \" + xhr.status)\n            }\n        }\n\n        try {\n            // Required for danbooru\n            if (currentProvider == \"danbooru\") {\n                xhr.setRequestHeader(\"User-Agent\", defaultUserAgent)\n            }\n            xhr.send()\n        } catch (error) {\n            console.log(\"Could not set User-Agent:\", error)\n        } \n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/BooruResponseData.qml",
    "content": "import qs.modules.common\nimport QtQuick;\n\n/**\n * A booru response.\n */\nQtObject {\n    property string provider\n    property var tags\n    property var page\n    property var images\n    property string message\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Brightness.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\n// From https://github.com/caelestia-dots/shell with modifications.\n// License: GPLv3\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\nimport QtQuick\n\n/**\n * For managing brightness of monitors. Supports both brightnessctl and ddcutil.\n */\nSingleton {\n    id: root\n    signal brightnessChanged()\n\n    property var ddcMonitors: []\n    readonly property list<BrightnessMonitor> monitors: Quickshell.screens.map(screen => monitorComp.createObject(root, {\n        screen\n    }))\n\n    function getMonitorForScreen(screen: ShellScreen): var {\n        return monitors.find(m => m.screen === screen);\n    }\n\n    function increaseBrightness(): void {\n        // if gamma is not yet 100, first increase gamma\n        if (Hyprsunset.gamma !== 100) {\n            Hyprsunset.setGamma(Hyprsunset.gamma + 5);\n            return;\n        }\n\n        const focusedName = Hyprland.focusedMonitor.name;\n        const monitor = monitors.find(m => focusedName === m.screen.name);\n        if (monitor)\n            monitor.setBrightness(monitor.brightness + 0.05);\n    }\n\n    function decreaseBrightness(): void {\n        const focusedName = Hyprland.focusedMonitor.name;\n        const monitor = monitors.find(m => focusedName === m.screen.name);\n        if (monitor && monitor.brightness > 0) \n            monitor.setBrightness(monitor.brightness - 0.05);\n        // if brightness is 0, then decrease gamma\n        else {\n            Hyprsunset.setGamma(Hyprsunset.gamma - 5);\n        }\n    }\n\n    reloadableId: \"brightness\"\n\n    onMonitorsChanged: {\n        ddcMonitors = [];\n        ddcProc.running = true;\n    }\n\n    function initializeMonitor(i: int): void {\n        if (i >= monitors.length)\n            return;\n        monitors[i].initialize();\n    }\n\n    function ddcDetectFinished(): void {\n        initializeMonitor(0);\n    }\n\n    Process {\n        id: ddcProc\n\n        command: [\"ddcutil\", \"detect\", \"--brief\"]\n        stdout: SplitParser {\n            splitMarker: \"\\n\\n\"\n            onRead: data => {\n                if (data.startsWith(\"Display \")) {\n                    const lines = data.split(\"\\n\").map(l => l.trim());\n                    root.ddcMonitors.push({\n                        name: lines.find(l => l.startsWith(\"DRM connector:\")).split(\"-\").slice(1).join('-'),\n                        busNum: lines.find(l => l.startsWith(\"I2C bus:\")).split(\"/dev/i2c-\")[1]\n                    });\n                }\n            }\n        }\n        onExited: root.ddcDetectFinished()\n    }\n\n    Process {\n        id: setProc\n    }\n\n    component BrightnessMonitor: QtObject {\n        id: monitor\n\n        required property ShellScreen screen\n        property bool isDdc\n        property string busNum\n        property int rawMaxBrightness: 100\n        property real brightness\n        property real brightnessMultiplier: 1.0\n        property real multipliedBrightness: Math.max(0, Math.min(1, brightness * (Config.options.light.antiFlashbang.enable ? brightnessMultiplier : 1)))\n        property bool ready: false\n        property bool animateChanges: !monitor.isDdc\n\n        onBrightnessChanged: {\n            if (!monitor.ready) return;\n            root.brightnessChanged();\n        }\n\n        Behavior on multipliedBrightness {\n            enabled: monitor.animateChanges\n            NumberAnimation {\n                duration: 200\n                easing.type: Easing.BezierSpline\n                easing.bezierCurve: Appearance.animationCurves.expressiveEffects\n            }\n        }\n        onMultipliedBrightnessChanged: {\n            if (monitor.animationEnabled) syncBrightness();\n            else setTimer.restart();\n        }\n\n        function initialize() {\n            monitor.ready = false;\n            const match = root.ddcMonitors.find(m => m.name === screen.name && !root.monitors.slice(0, root.monitors.indexOf(this)).some(mon => mon.busNum === m.busNum));\n            isDdc = !!match;\n            busNum = match?.busNum ?? \"\";\n            initProc.command = isDdc ? [\"ddcutil\", \"-b\", busNum, \"getvcp\", \"10\", \"--brief\"] : [\"sh\", \"-c\", `echo \"a b c $(brightnessctl g) $(brightnessctl m)\"`];\n            initProc.running = true;\n        }\n\n        readonly property Process initProc: Process {\n            stdout: SplitParser {\n                onRead: data => {\n                    const [, , , current, max] = data.split(\" \");\n                    monitor.rawMaxBrightness = parseInt(max);\n                    monitor.brightness = parseInt(current) / monitor.rawMaxBrightness;\n                    monitor.ready = true;\n                }\n            }\n            onExited: (exitCode, exitStatus) => {\n                initializeMonitor(root.monitors.indexOf(monitor) + 1);\n            }\n        }\n\n        // We need a delay for DDC monitors because they can be quite slow and might act weird with rapid changes\n        property var setTimer: Timer {\n            id: setTimer\n            interval: monitor.isDdc ? 300 : 0\n            onTriggered: {\n                syncBrightness();\n            }\n        }\n\n        function syncBrightness() {\n            const brightnessValue = Math.max(monitor.multipliedBrightness, 0);\n            if (isDdc) {\n                const rawValueRounded = Math.max(Math.floor(brightnessValue * monitor.rawMaxBrightness), 1);\n                setProc.exec([\"ddcutil\", \"-b\", busNum, \"setvcp\", \"10\", rawValueRounded]);\n            } else {\n                const valuePercentNumber = Math.floor(brightnessValue * 100);\n                let valuePercent = `${valuePercentNumber}%`;\n                if (valuePercentNumber == 0) valuePercent = \"1\"; // Prevent fully black\n                setProc.exec([\"brightnessctl\", \"--class\", \"backlight\", \"s\", valuePercent, \"--quiet\"])\n            }\n        }\n\n        function setBrightness(value: real): void {\n            value = Math.max(0, Math.min(1, value));\n            monitor.brightness = value;\n        }\n\n        function setBrightnessMultiplier(value: real): void {\n            monitor.brightnessMultiplier = value;\n        }\n    }\n\n    Component {\n        id: monitorComp\n\n        BrightnessMonitor {}\n    }\n\n    // Anti-flashbang\n    property int workspaceAnimationDelay: 500\n    property int contentSwitchDelay: 30\n    property string screenshotDir: \"/tmp/quickshell/brightness/antiflashbang\"\n    function brightnessMultiplierForLightness(x: real): real {\n        // I hand picked some values and fitted an exponential curve for this\n        // 6.600135 + 216.360356 * e^(-0.0811129189x)\n        // Division by 100 is to normalize to [0, 1]\n        return (6.600135 + 216.360356 * Math.pow(Math.E, -0.0811129189 * x)) / 100.0;\n    }\n    Variants {\n        model: Quickshell.screens\n        Scope {\n            id: screenScope\n            required property var modelData\n            property string screenName: modelData.name\n            property string screenshotPath: `${root.screenshotDir}/screenshot-${screenName}.png`\n            Connections {\n                enabled: Config.options.light.antiFlashbang.enable && Appearance.m3colors.darkmode\n                target: Hyprland\n                function onRawEvent(event) {\n                    if ([\"activewindowv2\", \"windowtitlev2\"].includes(event.name)) {\n                        screenshotTimer.interval = root.contentSwitchDelay;\n                        screenshotTimer.restart();\n                    } else if ([\"workspacev2\"].includes(event.name)) {\n                        screenshotTimer.interval = root.workspaceAnimationDelay;\n                        screenshotTimer.restart();\n                    }\n                }\n            }\n\n            Timer {\n                id: screenshotTimer\n                interval: 700 // This is what I have for a Hyprland ws anim\n                onTriggered: {\n                    screenshotProc.running = false;\n                    screenshotProc.running = true;\n                }\n            }\n\n            Process {\n                id: screenshotProc\n                command: [\"bash\", \"-c\",\n                    `mkdir -p '${StringUtils.shellSingleQuoteEscape(root.screenshotDir)}'`\n                    + ` && grim -o '${StringUtils.shellSingleQuoteEscape(screenScope.screenName)}' -`\n                    + ` | magick png:- -colorspace Gray -format \"%[fx:mean*100]\" info:`\n                ]\n                stdout: StdioCollector {\n                    id: lightnessCollector\n                    onStreamFinished: {\n                        Quickshell.execDetached([\"rm\", screenScope.screenshotPath]); // Cleanup\n                        const lightness = lightnessCollector.text\n                        const newMultiplier = root.brightnessMultiplierForLightness(parseFloat(lightness))\n                        Brightness.getMonitorForScreen(screenScope.modelData).setBrightnessMultiplier(newMultiplier)\n                    }\n                }\n            }\n        }\n    }\n\n    // External trigger points\n\n    IpcHandler {\n        target: \"brightness\"\n\n        function increment() {\n            onPressed: root.increaseBrightness()\n        }\n\n        function decrement() {\n            onPressed: root.decreaseBrightness()\n        }\n    }\n\n    GlobalShortcut {\n        name: \"brightnessIncrease\"\n        description: \"Increase brightness\"\n        onPressed: root.increaseBrightness()\n    }\n\n    GlobalShortcut {\n        name: \"brightnessDecrease\"\n        description: \"Decrease brightness\"\n        onPressed: root.decreaseBrightness()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Cliphist.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nSingleton {\n    id: root\n    // property string cliphistBinary: FileUtils.trimFileProtocol(`${Directories.home}/.cargo/bin/stash`)\n    property string cliphistBinary: \"cliphist\"\n    property real pasteDelay: 0.05\n    property string pressPasteCommand: \"ydotool key -d 1 29:1 47:1 47:0 29:0\"\n    property bool sloppySearch: Config.options?.search.sloppy ?? false\n    property real scoreThreshold: 0.2\n    property list<string> entries: []\n    readonly property var preparedEntries: entries.map(a => ({\n        name: Fuzzy.prepare(`${a.replace(/^\\s*\\S+\\s+/, \"\")}`),\n        entry: a\n    }))\n    function fuzzyQuery(search: string): var {\n        if (search.trim() === \"\") {\n            return entries;\n        }\n        if (root.sloppySearch) {\n            const results = entries.slice(0, 100).map(str => ({\n                entry: str,\n                score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase())\n            })).filter(item => item.score > root.scoreThreshold)\n                .sort((a, b) => b.score - a.score)\n            return results\n                .map(item => item.entry)\n        }\n\n        return Fuzzy.go(search, preparedEntries, {\n            all: true,\n            key: \"name\"\n        }).map(r => {\n            return r.obj.entry\n        });\n    }\n\n    function entryIsImage(entry) {\n        return !!(/^\\d+\\t\\[\\[.*binary data.*\\d+x\\d+.*\\]\\]$/.test(entry))\n    }\n\n    function refresh() {\n        readProc.buffer = []\n        readProc.running = true\n    }\n\n    function copy(entry) {\n        if (root.cliphistBinary.includes(\"cliphist\")) // Classic cliphist\n            Quickshell.execDetached([\"bash\", \"-c\", `printf '${StringUtils.shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy`]);\n        else { // Stash\n            const entryNumber = entry.split(\"\\t\")[0];\n            Quickshell.execDetached([\"bash\", \"-c\", `${root.cliphistBinary} decode ${entryNumber} | wl-copy`]);\n        }\n    }\n\n    function paste(entry) {\n        if (root.cliphistBinary.includes(\"cliphist\")) // Classic cliphist\n            Quickshell.execDetached([\"bash\", \"-c\", `printf '${StringUtils.shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && wl-paste`]);\n        else { // Stash\n            const entryNumber = entry.split(\"\\t\")[0];\n            Quickshell.execDetached([\"bash\", \"-c\", `${root.cliphistBinary} decode ${entryNumber} | wl-copy; ${root.pressPasteCommand}`]);\n        }\n    }\n\n    function superpaste(count, isImage = false) {\n        // Find entries\n        const targetEntries = entries.filter(entry => {\n            if (!isImage) return true;\n            return entryIsImage(entry);\n        }).slice(0, count)\n        const pasteCommands = [...targetEntries].reverse().map(entry => `printf '${StringUtils.shellSingleQuoteEscape(entry)}' | ${root.cliphistBinary} decode | wl-copy && sleep ${root.pasteDelay} && ${root.pressPasteCommand}`)\n        // Act\n        Quickshell.execDetached([\"bash\", \"-c\", pasteCommands.join(` && sleep ${root.pasteDelay} && `)]);\n    }\n\n    Process {\n        id: deleteProc\n        property string entry: \"\"\n        command: [\"bash\", \"-c\", `echo '${StringUtils.shellSingleQuoteEscape(deleteProc.entry)}' | ${root.cliphistBinary} delete`]\n        function deleteEntry(entry) {\n            deleteProc.entry = entry;\n            deleteProc.running = true;\n            deleteProc.entry = \"\";\n        }\n        onExited: (exitCode, exitStatus) => {\n            root.refresh();\n        }\n    }\n\n    function deleteEntry(entry) {\n        deleteProc.deleteEntry(entry);\n    }\n\n    Process {\n        id: wipeProc\n        command: [root.cliphistBinary, \"wipe\"]\n        onExited: (exitCode, exitStatus) => {\n            root.refresh();\n        }\n    }\n\n    function wipe() {\n        wipeProc.running = true;\n    }\n\n    Connections {\n        target: Quickshell\n        function onClipboardTextChanged() {\n            delayedUpdateTimer.restart()\n        }\n    }\n\n    Timer {\n        id: delayedUpdateTimer\n        interval: Config.options.hacks.arbitraryRaceConditionDelay\n        repeat: false\n        onTriggered: {\n            root.refresh()\n        }\n    }\n\n    Process {\n        id: readProc\n        property list<string> buffer: []\n\n        command: [root.cliphistBinary, \"list\"]\n\n        stdout: SplitParser {\n            onRead: (line) => {\n                readProc.buffer.push(line)\n            }\n        }\n\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode === 0) {\n                root.entries = readProc.buffer\n            } else {\n                console.error(\"[Cliphist] Failed to refresh with code\", exitCode, \"and status\", exitStatus)\n            }\n        }\n    }\n\n    IpcHandler {\n        target: \"cliphistService\"\n\n        function update(): void {\n            root.refresh()\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ConflictKiller.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nSingleton {\n    id: root\n\n    property string killDialogQmlPath: FileUtils.trimFileProtocol(Quickshell.shellPath(\"killDialog.qml\"))\n\n    function load() {\n        // dummy to force init\n    }\n\n    Connections {\n        target: Config\n        function onReadyChanged() {\n            if (Config.ready) checkConflictsProc.running = true\n        }\n    }\n\n    Process {\n        id: checkConflictsProc\n        command: [\"bash\", \"-c\", `echo \"$(pidof kded6);$(pidof mako dunst)\"`]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                const output = this.text;\n                const conflictingTrays = output.split(\";\")[0].trim().length > 0;\n                const conflictingNotifications = output.split(\";\")[1].trim().length > 0;\n                var openDialog = false;\n                if (conflictingTrays) {\n                    if (!Config.options.conflictKiller.autoKillTrays) openDialog = true;\n                    else Quickshell.execDetached([\"killall\", \"kded6\"])\n                }\n                if (conflictingNotifications) {\n                    if (!Config.options.conflictKiller.autoKillNotificationDaemons) openDialog = true;\n                    else Quickshell.execDetached([\"killall\", \"mako\", \"dunst\"])\n                }\n                if (openDialog) {\n                    Quickshell.execDetached([\"qs\", \"-p\", root.killDialogQmlPath])\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/DateTime.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport qs\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\n/**\n * A nice wrapper for date and time strings.\n */\nSingleton {\n    property var clock: SystemClock {\n        id: clock\n        precision: {\n            if (Config.options.time.secondPrecision || GlobalStates.screenLocked)\n                return SystemClock.Seconds;\n            return SystemClock.Minutes;\n        }\n    }\n    property string time: Qt.locale().toString(clock.date, Config.options?.time.format ?? \"hh:mm\")\n    property string shortDate: Qt.locale().toString(clock.date, Config.options?.time.shortDateFormat ?? \"dd/MM\")\n    property string date: Qt.locale().toString(clock.date, Config.options?.time.dateWithYearFormat ?? \"dd/MM/yyyy\")\n    property string longDate: Qt.locale().toString(clock.date, Config.options?.time.dateFormat ?? \"dddd, dd/MM\")\n    property string collapsedCalendarFormat: Qt.locale().toString(clock.date, \"dddd, MMMM dd\")\n    property string uptime: \"0h, 0m\"\n\n    Timer {\n        interval: 10\n        running: true\n        repeat: true\n        onTriggered: {\n            fileUptime.reload();\n            const textUptime = fileUptime.text();\n            const uptimeSeconds = Number(textUptime.split(\" \")[0] ?? 0);\n\n            // Convert seconds to days, hours, and minutes\n            const days = Math.floor(uptimeSeconds / 86400);\n            const hours = Math.floor((uptimeSeconds % 86400) / 3600);\n            const minutes = Math.floor((uptimeSeconds % 3600) / 60);\n\n            // Build the formatted uptime string\n            let formatted = \"\";\n            if (days > 0)\n                formatted += `${days}d`;\n            if (hours > 0)\n                formatted += `${formatted ? \", \" : \"\"}${hours}h`;\n            if (minutes > 0 || !formatted)\n                formatted += `${formatted ? \", \" : \"\"}${minutes}m`;\n            uptime = formatted;\n            interval = Config.options?.resources?.updateInterval ?? 3000;\n        }\n    }\n\n    FileView {\n        id: fileUptime\n\n        path: \"/proc/uptime\"\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/EasyEffects.qml",
    "content": "import qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Services.Pipewire\npragma Singleton\npragma ComponentBehavior: Bound\n\n/**\n * Handles EasyEffects active state and presets.\n */\nSingleton {\n    id: root\n\n    property bool available: false\n    property bool active: false\n\n    function fetchAvailability() {\n        fetchAvailabilityProc.running = true\n    }\n\n    function fetchActiveState() {\n        fetchActiveStateProc.running = true\n    }\n\n    function disable() {\n        root.active = false\n        Quickshell.execDetached([\"bash\", \"-c\", \"pkill easyeffects || flatpak pkill com.github.wwmm.easyeffects\"])\n    }\n\n    function enable() {\n        root.active = true\n        Quickshell.execDetached([\"bash\", \"-c\", \"easyeffects --hide-window --service-mode || flatpak run com.github.wwmm.easyeffects --hide-window --service-mode\"])\n    }\n\n    function toggle() {\n        if (root.active) {\n            root.disable()\n        } else {\n            root.enable()\n        }\n    }\n\n    Process {\n        id: fetchAvailabilityProc\n        running: true\n        command: [\"bash\", \"-c\", \"command -v easyeffects || flatpak info com.github.wwmm.easyeffects > /dev/null 2>&1\"]\n        onExited: (exitCode, exitStatus) => {\n            root.available = exitCode === 0\n        }\n    }\n\n    Process {\n        id: fetchActiveStateProc\n        running: true\n        command: [\"bash\", \"-c\", \"pidof easyeffects || flatpak ps | grep com.github.wwmm.easyeffects > /dev/null 2>&1\"]\n        onExited: (exitCode, exitStatus) => {\n            root.active = exitCode === 0\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Emojis.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\n/**\n * Emojis.\n */\nSingleton {\n    id: root\n    property string emojiScriptPath: `${Directories.config}/hypr/hyprland/scripts/fuzzel-emoji.sh`\n\tproperty string lineBeforeData: \"### DATA ###\"\n    property list<var> list\n    readonly property var preparedEntries: list.map(a => ({\n        name: Fuzzy.prepare(`${a}`),\n        entry: a\n    }))\n    function fuzzyQuery(search: string): var {\n        if (root.sloppySearch) {\n            const results = entries.slice(0, 100).map(str => ({\n                entry: str,\n                score: Levendist.computeTextMatchScore(str.toLowerCase(), search.toLowerCase())\n            })).filter(item => item.score > root.scoreThreshold)\n                .sort((a, b) => b.score - a.score)\n            return results\n                .map(item => item.entry)\n        }\n\n        return Fuzzy.go(search, preparedEntries, {\n            all: true,\n            key: \"name\"\n        }).map(r => {\n            return r.obj.entry\n        });\n    }\n\n    function load() {\n        emojiFileView.reload()\n    }\n\n    function updateEmojis(fileContent) {\n        const lines = fileContent.split(\"\\n\")\n        const dataIndex = lines.indexOf(root.lineBeforeData)\n        if (dataIndex === -1) {\n            console.warn(\"No data section found in emoji script file.\")\n            return\n        }\n        const emojis = lines.slice(dataIndex + 1).filter(line => line.trim() !== \"\")\n        root.list = emojis.map(line => line.trim())\n    }\n\n    FileView { \n        id: emojiFileView\n        path: Qt.resolvedUrl(root.emojiScriptPath)\n        onLoadedChanged: {\n            const fileContent = emojiFileView.text()\n            root.updateEmojis(fileContent)\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/FirstRunExperience.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport Quickshell\nimport Quickshell.Io\n\nSingleton {\n    id: root\n    property string firstRunFilePath: `${Directories.state}/user/first_run.txt`\n    property string firstRunFileContent: \"This file is just here to confirm you've been greeted :>\"\n    property string firstRunNotifSummary: \"Welcome!\"\n    property string firstRunNotifBody: \"Hit Super+/ for a list of keybinds\"\n    property string defaultWallpaperPath: FileUtils.trimFileProtocol(`${Directories.assetsPath}/images/default_wallpaper.png`)\n    property string welcomeQmlPath: FileUtils.trimFileProtocol(Quickshell.shellPath(\"welcome.qml\"))\n\n    function load() {\n        firstRunFileView.reload()\n    }\n\n    function enableNextTime() {\n        Quickshell.execDetached([\"rm\", \"-f\", root.firstRunFilePath])\n    }\n    function disableNextTime() {\n        Quickshell.execDetached([\"bash\", \"-c\", `echo '${root.firstRunFileContent}' > '${root.firstRunFilePath}'`])\n    }\n\n    function handleFirstRun() {\n        Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, root.defaultWallpaperPath])\n        Quickshell.execDetached([\"bash\", \"-c\", `qs -p '${root.welcomeQmlPath}'`])\n    }\n\n    FileView {\n        id: firstRunFileView\n        path: Qt.resolvedUrl(firstRunFilePath)\n        onLoadFailed: (error) => {\n            if (error == FileViewError.FileNotFound) {\n                firstRunFileView.setText(root.firstRunFileContent)\n                root.handleFirstRun()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/GlobalFocusGrab.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\n\n/**\n * Manages a HyprlandFocusGrab that's to be shared by all windows.\n * \"Persistent\" is for windows that should always be included but not closed on dismiss, like bar and onscreen keyboard.\n * \"Dismissable\" is for stuff like sidebars\n */ \nSingleton {\n    id: root\n\n    signal dismissed()\n\n    property list<var> persistent: []\n    property list<var> dismissable: []\n\n    function dismiss() {\n        root.dismissable = [];\n        root.dismissed();\n    }\n\n    Component.onCompleted: {\n        console.log(\"[GlobalFocusGrab] Initialized\");\n    }\n\n    function addPersistent(window) {\n        if (root.persistent.indexOf(window) === -1) {\n            root.persistent.push(window);\n        }\n    }\n\n    function removePersistent(window) {\n        var index = root.persistent.indexOf(window);\n        if (index !== -1) {\n            root.persistent.splice(index, 1);\n        }\n    }\n\n    function addDismissable(window) {\n        if (root.dismissable.indexOf(window) === -1) {\n            root.dismissable.push(window);\n        }\n    }\n\n    function removeDismissable(window) {\n        var index = root.dismissable.indexOf(window);\n        if (index !== -1) {\n            root.dismissable.splice(index, 1);\n        }\n    }\n\n    function hasActive(element) {\n        return element?.activeFocus || Array.from(\n            element?.children\n        ).some(\n            (child) => hasActive(child)\n        );\n    }\n\n    HyprlandFocusGrab {\n        id: grab\n        windows: root.dismissable.every(w => !w?.focusable) || root.dismissable.some(w => hasActive(w?.contentItem)) ? [...root.dismissable, ...root.persistent] : [...root.dismissable]\n        active: root.dismissable.length > 0\n        onCleared: () => {\n            root.dismiss();\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/GoogleCloud.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport QtQuick\nimport Quickshell\nimport qs.modules.common.utils\n\nSingleton {\n    id: root\n\n    property var keyContent: ({})\n    property string keyProjectId: keyContent?.project_id\n    property bool keyError: false\n    property bool keyReady: false\n    property string token: \"\"\n    property date tokenExpiry\n    property bool tokenError: false\n    property bool tokenReady: false\n    readonly property string projectId: keyProjectId\n\n    readonly property bool loaded: keyReady && tokenReady\n\n    readonly property string tokenForKeyScriptPath: Quickshell.shellPath(\"services/gCloud/token-from-key-venv.sh\")\n\n    function load() {\n        // Init load will be handled by Component.onCompleted\n        if (!tokenReady) return;\n        // We just reload if key expired\n        if (new Date() >= root.tokenExpiry) {\n            root.tokenReady = false;\n            root.keyReady = false;\n            loadKeyIfPossible();\n        }\n    }\n\n    function unready() {\n        root.keyReady = false;\n        root.tokenReady = false;\n        root.keyError = false;\n        root.tokenError = false;\n    }\n\n    function setKeyJson(str: string): bool {\n        try {\n            var keyData = JSON.parse(str)\n            root.unready();\n            KeyringStorage.setNestedField([\"googleCloud\", \"serviceAccountKey\"], keyData);\n            return true;\n        } catch(e) {\n            return false;\n        }\n    }\n\n    function getToken() {\n        if (root.keyError) {\n            root.tokenError = true;\n            root.tokenReady = true;\n            return;\n        }\n        tokenProc.runSequence([(() => { // prep token fetcher\n                tokenProc.environment.SERVICE_KEY_CONTENT = JSON.stringify(root.keyContent);\n                tokenProc.command = [ //\n                    \"bash\", \"-c\" //\n                    , `${tokenForKeyScriptPath} \"$SERVICE_KEY_CONTENT\"`];\n            }), //\n            [], // run token fetcher\n            ((out) => {\n                try {\n                    const data = JSON.parse(out)\n                    root.token = data.token\n                    // Js wants millis instead of seconds\n                    root.tokenExpiry = new Date(data.expiry * 1000) \n                    root.tokenError = false;\n                } catch(e) {\n                    root.tokenError = true;\n                    print(\"[GoogleCloud] Failed to parse token response: \" + e + \"\\n\" + out)\n                }\n                root.tokenReady = true;\n            }\n            )]);\n    }\n\n    function loadKeyIfPossible() {\n        if (KeyringStorage.loaded) {\n            root.keyContent = KeyringStorage.keyringData?.googleCloud?.serviceAccountKey;\n            if (!root.keyContent?.project_id) {\n                root.keyError = true;\n            } else {\n                root.keyError = false;\n                root.keyProjectId = root.keyContent.project_id;\n            }\n            root.keyReady = true;\n            root.getToken();\n        } else {\n            KeyringStorage.fetchKeyringData();\n        }\n    }\n\n    Component.onCompleted: {\n        loadKeyIfPossible();\n    }\n\n    Connections {\n        target: KeyringStorage\n        function onLoadedChanged() {\n            root.loadKeyIfPossible();\n        }\n        function onDataChanged() {\n            root.loadKeyIfPossible();\n        }\n    }\n\n    MultiTurnProcess {\n        id: tokenProc\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/HyprlandAntiFlashbangShader.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport QtQuick\nimport Quickshell\n\nimport qs.modules.common.models.hyprland\n\nSingleton {\n    id: root\n\n    readonly property string shaderPath: Quickshell.shellPath(\"services/hyprlandAntiFlashbangShader/anti-flashbang.glsl\")\n    property bool enabled: confOpt.value == shaderPath\n\n    function enable() {\n        HyprlandConfig.setMany({\n            \"decoration:screen_shader\": root.shaderPath,\n            \"debug:damage_tracking\": 1, // Turn off dmg tracking to prevent weird flashes. 1 = monitor only\n        });\n    }\n\n    function disable() {\n        HyprlandConfig.resetMany([\n            \"decoration:screen_shader\",\n            \"debug:damage_tracking\"\n        ]);\n    }\n\n    function toggle() {\n        if (root.enabled) disable()\n        else enable()\n    }\n    \n    HyprlandConfigOption {\n        id: confOpt\n        key: \"decoration:screen_shader\"\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/HyprlandConfig.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Hyprland\n\nimport qs.modules.common\nimport qs.modules.common.functions\n\n/**\n * Configs Hyprland\n */\nSingleton {\n    id: root\n    \n    signal reloaded()\n\n    readonly property string configuratorScriptPath: Quickshell.shellPath(\"scripts/hyprland/hyprconfigurator.py\")\n    readonly property string shellOverridesPath: FileUtils.trimFileProtocol(`${Directories.config}/hypr/hyprland/shellOverrides/main.lua`)\n\n    function set(key: string, value: var) {\n        Quickshell.execDetached([\"bash\", \"-c\", //\n            `${root.configuratorScriptPath} --file ${root.shellOverridesPath} --set \"${key}\" \"${value}\"` //\n        ])\n    }\n    \n    function setMany(entries: var) {\n        let args = \"\"\n        for (let key in entries) {\n            args += `--set \"${key}\" \"${entries[key]}\" `\n        }\n        Quickshell.execDetached([\"bash\", \"-c\", //\n            `${root.configuratorScriptPath} --file ${root.shellOverridesPath} ${args}` //\n        ])\n    }\n    \n    function reset(key: string) {\n        Quickshell.execDetached([\"bash\", \"-c\", //\n            `${root.configuratorScriptPath} --file ${root.shellOverridesPath} --reset \"${key}\"` //\n        ])\n    }\n    \n    function resetMany(keys: list<string>) {\n        let args = \"\"\n        for (let i = 0; i < keys.length; i++) {\n            args += `--reset \"${keys[i]}\" `\n        }\n        Quickshell.execDetached([\"bash\", \"-c\", //\n            `${root.configuratorScriptPath} --file ${root.shellOverridesPath} ${args}` //\n        ])\n    }\n\n    Connections {\n        target: Hyprland\n\n        function onRawEvent(event) {\n            if (event.name == \"configreloaded\") {\n                root.reloaded()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/HyprlandData.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Wayland\nimport Quickshell.Hyprland\n\n/**\n * Provides access to some Hyprland data not available in Quickshell.Hyprland.\n */\nSingleton {\n    id: root\n    property var windowList: []\n    property var addresses: []\n    property var windowByAddress: ({})\n    property var workspaces: []\n    property var workspaceIds: []\n    property var workspaceById: ({})\n    property var activeWorkspace: null\n    property var monitors: []\n    property var layers: ({})\n\n    // Convenient stuff\n\n    function toplevelsForWorkspace(workspace) {\n        return ToplevelManager.toplevels.values.filter(toplevel => {\n            const address = `0x${toplevel.HyprlandToplevel?.address}`;\n            var win = HyprlandData.windowByAddress[address];\n            return win?.workspace?.id === workspace;\n        })\n    }\n\n    function hyprlandClientsForWorkspace(workspace) {\n        return root.windowList.filter(win => win.workspace.id === workspace);\n    }\n\n    function clientForToplevel(toplevel) {\n        if (!toplevel || !toplevel.HyprlandToplevel) {\n            return null;\n        }\n        const address = `0x${toplevel?.HyprlandToplevel?.address}`;\n        return root.windowByAddress[address];\n    }\n\n    // Internals\n\n    function updateWindowList() {\n        getClients.running = true;\n    }\n\n    function updateLayers() {\n        getLayers.running = true;\n    }\n\n    function updateMonitors() {\n        getMonitors.running = true;\n    }\n\n    function updateWorkspaces() {\n        getWorkspaces.running = true;\n        getActiveWorkspace.running = true;\n    }\n\n    function updateAll() {\n        updateWindowList();\n        updateMonitors();\n        updateLayers();\n        updateWorkspaces();\n    }\n\n    function biggestWindowForWorkspace(workspaceId) {\n        const windowsInThisWorkspace = HyprlandData.windowList.filter(w => w.workspace.id == workspaceId);\n        return windowsInThisWorkspace.reduce((maxWin, win) => {\n            const maxArea = (maxWin?.size?.[0] ?? 0) * (maxWin?.size?.[1] ?? 0);\n            const winArea = (win?.size?.[0] ?? 0) * (win?.size?.[1] ?? 0);\n            return winArea > maxArea ? win : maxWin;\n        }, null);\n    }\n\n    Component.onCompleted: {\n        updateAll();\n    }\n\n    Connections {\n        target: Hyprland\n\n        function onRawEvent(event) {\n            // console.log(\"Hyprland raw event:\", event.name);\n            if ([\"openlayer\", \"closelayer\", \"screencast\"].includes(event.name)) return;\n            updateAll()\n        }\n    }\n\n    Process {\n        id: getClients\n        command: [\"hyprctl\", \"clients\", \"-j\"]\n        stdout: StdioCollector {\n            id: clientsCollector\n            onStreamFinished: {\n                root.windowList = JSON.parse(clientsCollector.text)\n                let tempWinByAddress = {};\n                for (var i = 0; i < root.windowList.length; ++i) {\n                    var win = root.windowList[i];\n                    tempWinByAddress[win.address] = win;\n                }\n                root.windowByAddress = tempWinByAddress;\n                root.addresses = root.windowList.map(win => win.address);\n            }\n        }\n    }\n\n    Process {\n        id: getMonitors\n        command: [\"hyprctl\", \"monitors\", \"-j\"]\n        stdout: StdioCollector {\n            id: monitorsCollector\n            onStreamFinished: {\n                root.monitors = JSON.parse(monitorsCollector.text);\n            }\n        }\n    }\n\n    Process {\n        id: getLayers\n        command: [\"hyprctl\", \"layers\", \"-j\"]\n        stdout: StdioCollector {\n            id: layersCollector\n            onStreamFinished: {\n                root.layers = JSON.parse(layersCollector.text);\n            }\n        }\n    }\n\n    Process {\n        id: getWorkspaces\n        command: [\"hyprctl\", \"workspaces\", \"-j\"]\n        stdout: StdioCollector {\n            id: workspacesCollector\n            onStreamFinished: {\n                var rawWorkspaces = JSON.parse(workspacesCollector.text);\n                // Filter out invalid workspace ids (e.g. lock-screen temp workspace 2147483647 - N)\n                root.workspaces = rawWorkspaces.filter(ws => ws.id >= 1 && ws.id <= 100);\n                let tempWorkspaceById = {};\n                for (var i = 0; i < root.workspaces.length; ++i) {\n                    var ws = root.workspaces[i];\n                    tempWorkspaceById[ws.id] = ws;\n                }\n                root.workspaceById = tempWorkspaceById;\n                root.workspaceIds = root.workspaces.map(ws => ws.id);\n            }\n        }\n    }\n\n    Process {\n        id: getActiveWorkspace\n        command: [\"hyprctl\", \"activeworkspace\", \"-j\"]\n        stdout: StdioCollector {\n            id: activeWorkspaceCollector\n            onStreamFinished: {\n                root.activeWorkspace = JSON.parse(activeWorkspaceCollector.text);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/HyprlandKeybinds.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\n/**\n * A service that provides access to Hyprland keybinds.\n * Uses the `get_keybinds.py` script to parse comments in config files in a certain format and convert to JSON.\n */\nSingleton {\n    id: root\n    property var keybinds: []\n    property var keybindCategories: []\n\n    Connections {\n        target: Hyprland\n\n        function onRawEvent(event) {\n            if (event.name == \"configreloaded\") {\n                getKeybinds.running = true\n            }\n        }\n    }\n\n    Process {\n        id: getKeybinds\n        running: true\n        command: [\"hyprctl\", \"binds\", \"-j\"]\n        \n        stdout: StdioCollector {\n            onStreamFinished: {\n                try {\n                    root.keybinds = JSON.parse(text)\n                    var groups = []\n                    for (var i = 0; i < root.keybinds.length; i++) {\n                        var bind = root.keybinds[i].description\n                        var group = bind.substring(0, bind.indexOf(\":\"))\n                        if (!groups.includes(group) && group.length > 0) {\n                            groups.push(group)\n                        }\n                    }\n                    root.keybindCategories = groups\n                } catch (e) {\n                    console.error(\"[CheatsheetKeybinds] Error parsing keybinds:\", e)\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/HyprlandXkb.qml",
    "content": "pragma Singleton\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\nimport qs.modules.common\n\n/**\n * Exposes the active Hyprland Xkb keyboard layout name and code for indicators.\n */\nSingleton {\n    id: root\n    // You can read these\n    property list<string> layoutCodes: []\n    property var cachedLayoutCodes: ({})\n    property string currentLayoutName: \"\"\n    property string currentLayoutCode: \"\"\n    // For the service\n    property var baseLayoutFilePath: \"/usr/share/X11/xkb/rules/base.lst\"\n    property bool needsLayoutRefresh: false\n\n    // Update the layout code according to the layout name (Hyprland gives the name not the code)\n    onCurrentLayoutNameChanged: root.updateLayoutCode()\n    function updateLayoutCode() {\n        if (cachedLayoutCodes.hasOwnProperty(currentLayoutName)) {\n            root.currentLayoutCode = cachedLayoutCodes[currentLayoutName];\n        } else {\n            getLayoutProc.running = true;\n        }\n    }\n\n    // Get the layout code from the base.lst file by grabbing the line with the current layout name\n    Process {\n        id: getLayoutProc\n        command: [\"cat\", root.baseLayoutFilePath]\n\n        stdout: StdioCollector {\n            id: layoutCollector\n\n            onStreamFinished: {\n                const lines = layoutCollector.text.split(\"\\n\");\n                const targetDescription = root.currentLayoutName;\n                const foundLine = lines.find(line => {\n                    // Skip comment lines and empty lines\n                    if (!line.trim() || line.trim().startsWith('!'))\n                        return false;\n\n                    // Match layout: (whitespace + ) key + whitespace + description\n                    const matchLayout = line.match(/^\\s*(\\S+)\\s+(.+)$/);\n                    if (matchLayout && matchLayout[2] === targetDescription) {\n                        root.cachedLayoutCodes[matchLayout[2]] = matchLayout[1];\n                        root.currentLayoutCode = matchLayout[1];\n                        return true;\n                    }\n\n                    // Match variant: (whitespace + ) variant + whitespace + key + whitespace + description\n                    const matchVariant = line.match(/^\\s*(\\S+)\\s+(\\S+)\\s+(.+)$/);\n                    if (matchVariant && matchVariant[3] === targetDescription) {\n                        const complexLayout = matchVariant[2] + matchVariant[1];\n                        root.cachedLayoutCodes[matchVariant[3]] = complexLayout;\n                        root.currentLayoutCode = complexLayout;\n                        return true;\n                    }\n                    \n                    return false;\n                });\n                // console.log(\"[HyprlandXkb] Found line:\", foundLine);\n                // console.log(\"[HyprlandXkb] Layout:\", root.currentLayoutName, \"| Code:\", root.currentLayoutCode);\n                // console.log(\"[HyprlandXkb] Cached layout codes:\", JSON.stringify(root.cachedLayoutCodes, null, 2));\n            }\n        }\n    }\n\n    // Find out available layouts and current active layout. Should only be necessary on init\n    Process {\n        id: fetchLayoutsProc\n        running: true\n        command: [\"hyprctl\", \"-j\", \"devices\"]\n\n        stdout: StdioCollector {\n            id: devicesCollector\n            onStreamFinished: {\n                const parsedOutput = JSON.parse(devicesCollector.text);\n                const hyprlandKeyboard = parsedOutput[\"keyboards\"].find(kb => kb.main === true);\n                root.layoutCodes = hyprlandKeyboard[\"layout\"].split(\",\");\n                root.currentLayoutName = hyprlandKeyboard[\"active_keymap\"];\n                // console.log(\"[HyprlandXkb] Fetched | Layouts (multiple: \" + (root.layoutCodes.length > 1) + \"): \"\n                //     + root.layoutCodes.join(\", \") + \" | Active: \" + root.currentLayoutName);\n            }\n        }\n    }\n\n    // Update the layout name when it changes\n    Connections {\n        target: Hyprland\n        function onRawEvent(event) {\n            if (event.name === \"activelayout\") {\n                if (root.needsLayoutRefresh) {\n                    root.needsLayoutRefresh = false;\n                    fetchLayoutsProc.running = true;\n                }\n\n                // If there's only one layout, the updated layout is always the same\n                if (root.layoutCodes.length <= 1) return;\n\n                // Update when layout might have changed\n                const dataString = event.data;\n                root.currentLayoutName = dataString.substring(dataString.indexOf(\",\") + 1);\n\n                // Update layout for on-screen keyboard (osk)\n                Config.options.osk.layout = root.currentLayoutName.split(\" (\")[0];\n            } else if (event.name == \"configreloaded\") {\n                // Mark layout code list to be updated when config is reloaded\n                root.needsLayoutRefresh = true;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Hyprsunset.qml",
    "content": "pragma Singleton\n\nimport QtQuick\nimport qs.modules.common\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\n/**\n * Simple hyprsunset service with automatic mode.\n * In theory we don't need this because hyprsunset has a config file, but it somehow doesn't work.\n * It should also be possible to control it via hyprctl, but it doesn't work consistently either so we're just killing and launching.\n */\nSingleton {\n    id: root\n    signal gammaChangeAttempt()\n\n    readonly property real gammaLowerLimit: 25\n\n    property string from: Config.options?.light?.night?.from ?? \"19:00\" \n    property string to: Config.options?.light?.night?.to ?? \"06:30\"\n    property bool automatic: Config.options?.light?.night?.automatic && (Config?.ready ?? true)\n    property int colorTemperature: Config.options?.light?.night?.colorTemperature ?? 5000\n    property int defaultColorTemperature: 6000\n    property int gamma: 100\n    property bool shouldBeOn\n    property bool firstEvaluation: true\n    property bool temperatureActive: false\n\n    property int fromHour: Number(from.split(\":\")[0])\n    property int fromMinute: Number(from.split(\":\")[1])\n    property int toHour: Number(to.split(\":\")[0])\n    property int toMinute: Number(to.split(\":\")[1])\n\n    property int clockHour: DateTime.clock.hours\n    property int clockMinute: DateTime.clock.minutes\n\n    property var manualActive\n    property int manualActiveHour\n    property int manualActiveMinute\n\n    onClockMinuteChanged: reEvaluate()\n    onAutomaticChanged: {\n        root.manualActive = undefined;\n        root.firstEvaluation = true;\n        reEvaluate();\n    }\n\n    function inBetween(t, from, to) {\n        if (from < to) {\n            return (t >= from && t <= to);\n        } else {\n            // Wrapped around midnight\n            return (t >= from || t <= to);\n        }\n    }\n\n    function reEvaluate() {\n        const t = clockHour * 60 + clockMinute;\n        const from = fromHour * 60 + fromMinute;\n        const to = toHour * 60 + toMinute;\n        const manualActive = manualActiveHour * 60 + manualActiveMinute;\n\n        if (root.manualActive !== undefined && (inBetween(from, manualActive, t) || inBetween(to, manualActive, t))) {\n            root.manualActive = undefined;\n        }\n        root.shouldBeOn = inBetween(t, from, to);\n        if (firstEvaluation) {\n            firstEvaluation = false;\n            root.ensureState();\n        }\n    }\n\n    onShouldBeOnChanged: ensureState()\n    function ensureState() {\n        // console.log(\"[Hyprsunset] Ensuring state:\", root.shouldBeOn, \"Automatic mode:\", root.automatic);\n        if (!root.automatic || root.manualActive !== undefined)\n            return;\n        if (root.shouldBeOn) {\n            root.enableTemperature();\n        } else {\n            root.disableTemperature();\n        }\n    }\n\n    function startHyprsunset() {\n        Quickshell.execDetached([\"bash\", \"-c\", `pidof hyprsunset || hyprsunset`]);\n    }\n\n    function load() {\n        root.startHyprsunset();\n        root.ensureState();\n    }\n\n    Timer {\n        id: updateHyprsunset\n        interval: 100\n        repeat: false\n        onTriggered: {\n            root.ensureState();\n            root.setGamma(root.gamma);\n        }\n    }\n\n    function enableTemperature() {\n        root.temperatureActive = true;\n\n        // console.log(\"[Hyprsunset] Enabling\");\n        root.startHyprsunset();\n        Quickshell.execDetached([\"bash\", \"-c\", `hyprctl hyprsunset temperature ${root.colorTemperature}`]);\n    }\n\n    function disableTemperature() {\n        root.temperatureActive = false;\n        // console.log(\"[Hyprsunset] Disabling\");\n        Quickshell.execDetached([\"bash\", \"-c\", `hyprctl hyprsunset temperature ${root.defaultColorTemperature}`]);\n    }\n\n    function setGamma(gamma) {\n        root.gamma = Math.max(root.gammaLowerLimit, Math.min(100, gamma));\n\n        root.gammaChangeAttempt();\n\n        root.startHyprsunset();\n        Quickshell.execDetached([\"bash\", \"-c\", `hyprctl hyprsunset gamma ${root.gamma}`]);\n    }\n\n    function fetchState() {\n        fetchProc.running = true;\n    }\n\n    Process {\n        id: fetchProc\n        running: true\n        command: [\"bash\", \"-c\", \"hyprctl hyprsunset temperature\"]\n        stdout: StdioCollector {\n            id: stateCollector\n            onStreamFinished: {\n                const output = stateCollector.text.trim();\n                if (output.length == 0 || output.startsWith(\"Couldn't\"))\n                    root.temperatureActive = false;\n                else\n                    root.temperatureActive = (output != root.defaultColorTemperature); // 6000 is the default when off\n                // console.log(\"[Hyprsunset] Fetched state:\", output, \"->\", root.temperatureActive);\n            }\n        }\n    }\n\n    function toggleTemperature(active = undefined) {\n        if (root.manualActive === undefined) {\n            root.manualActive = root.temperatureActive;\n            root.manualActiveHour = root.clockHour;\n            root.manualActiveMinute = root.clockMinute;\n        }\n\n        root.manualActive = active !== undefined ? active : !root.manualActive;\n        if (root.manualActive) {\n            root.enableTemperature();\n        } else {\n            root.disableTemperature();\n        }\n    }\n\n    // Change temp\n    Connections {\n        target: Config.options.light.night\n        function onColorTemperatureChanged() {\n            if (!root.temperatureActive) return;\n            Quickshell.execDetached([\"hyprctl\", \"hyprsunset\", \"temperature\", `${Config.options.light.night.colorTemperature}`]);\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Idle.qml",
    "content": "pragma Singleton\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Wayland\n\n/**\n * A nice wrapper for date and time strings.\n */\nSingleton {\n    id: root\n\n    property alias inhibit: idleInhibitor.enabled\n    inhibit: false\n\n    Connections {\n        target: Persistent\n        function onReadyChanged() {\n            if (!Persistent.isNewHyprlandInstance) {\n                root.inhibit = Persistent.states.idle.inhibit;\n            } else {\n                Persistent.states.idle.inhibit = root.inhibit;\n            }\n        }\n    }\n\n    function toggleInhibit(active = null) {\n        if (active !== null) {\n            root.inhibit = active;\n        } else {\n            root.inhibit = !root.inhibit;\n        }\n        Persistent.states.idle.inhibit = root.inhibit;\n    }\n\n    IdleInhibitor {\n        id: idleInhibitor\n        window: PanelWindow {\n            // Inhibitor requires a \"visible\" surface\n            // Actually not lol\n            implicitWidth: 0\n            implicitHeight: 0\n            color: \"transparent\"\n            // Just in case...\n            anchors {\n                right: true\n                bottom: true\n            }\n            // Make it not interactable\n            mask: Region {\n                item: null\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/KeyringStorage.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs\nimport qs.modules.common\nimport qs.modules.common.functions\nimport Quickshell;\nimport Quickshell.Io;\nimport QtQuick;\n\n/**\n * For storing sensitive data in the keyring.\n * Use this for small data only, since it stores a JSON of the contents directly and doesn't use a database.\n */\nSingleton {\n    id: root\n\n    signal dataChanged()\n\n    property bool loaded: false\n    property var keyringData: ({})\n    \n    property var properties: {\n        \"application\": \"illogical-impulse\",\n        \"explanation\": Translation.tr(\"For storing API keys and other sensitive information\"),\n    }\n    property var propertiesAsArgs: Object.keys(root.properties).reduce(\n        function(arr, key) {\n            return arr.concat([key, root.properties[key]]);\n        }, []\n    )\n    property string keyringLabel: Translation.tr(\"%1 Safe Storage\").arg(\"illogical-impulse\")\n\n    function setNestedField(path, value) {\n        if (!root.keyringData) root.keyringData = {};\n        let keys = path;\n        let obj = root.keyringData;\n        let parents = [obj];\n\n        // Traverse and collect parent objects\n        for (let i = 0; i < keys.length - 1; ++i) {\n            if (!obj[keys[i]] || typeof obj[keys[i]] !== \"object\") {\n                obj[keys[i]] = {};\n            }\n            obj = obj[keys[i]];\n            parents.push(obj);\n        }\n\n        // Set the value at the innermost key\n        obj[keys[keys.length - 1]] = value;\n\n        // Reassign each parent object from the bottom up to trigger change notifications\n        for (let i = keys.length - 2; i >= 0; --i) {\n            let parent = parents[i];\n            let key = keys[i];\n            // Shallow clone to change object identity (spread replaced with Object.assign)\n            parent[key] = Object.assign({}, parent[key]);\n        }\n\n        // Finally, reassign root.keyringData to trigger top-level change\n        root.keyringData = Object.assign({}, root.keyringData);\n\n        saveKeyringData();\n    }\n\n    function fetchKeyringData() {\n        // console.log(\"[KeyringStorage] Fetching keyring data...\");\n        // console.log(\"[KeyringStorage] getData command:'\" + getData.command.join(\"' '\") + \"'\");\n        getData.running = true;\n    }\n\n    function saveKeyringData() {\n        saveData.stdinEnabled = true;\n        saveData.running = true;\n    }\n\n    Process {\n        id: saveData\n        command: [\n            \"secret-tool\", \"store\", \"--label=\" + keyringLabel,\n            ...propertiesAsArgs,\n        ]\n        onRunningChanged: {\n            if (saveData.running) {\n                // console.log(\"[KeyringStorage] Saving with command: '\" + saveData.command.join(\"' '\") + \"'\");\n                saveData.write(JSON.stringify(root.keyringData));\n                root.dataChanged()\n                stdinEnabled = false // End input stream\n            }\n        }\n    }\n\n    Process {\n        id: getData\n        command: [ // We need to use echo for a newline so splitparser does parse\n            \"bash\", \"-c\", `${Directories.scriptPath}/keyring/try_lookup.sh 2> /dev/null`,\n        ]\n        stdout: StdioCollector {\n            id: keyringDataOutputCollector\n            onStreamFinished: {\n                const data = keyringDataOutputCollector.text;\n                if (data.length === 0 || !data.startsWith(\"{\")) return;\n                try {\n                    root.keyringData = JSON.parse(data);\n                    // console.log(\"[KeyringStorage] Keyring data fetched:\", JSON.stringify(root.keyringData));\n                } catch (e) {\n                    console.error(\"[KeyringStorage] Failed to get keyring data, reinitializing.\");\n                    root.keyringData = {};\n                    saveKeyringData()\n                }\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            // console.log(\"[KeyringStorage] Keyring data fetch process exited with code:\", exitCode);\n            if (exitCode === 1) {\n                console.error(\"[KeyringStorage] Entry not found, initializing.\");\n                root.keyringData = {};\n                saveKeyringData()\n            }\n            if (exitCode !== 2) {\n                root.loaded = true;\n            }\n        }\n    }\n    \n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/LatexRenderer.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common.functions\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\n\n/**\n * Renders LaTeX snippets with MicroTeX.\n * For every request:\n *   1. Hash it\n *   2. Check if the hash is already processed\n *   3. If not, render it with MicroTeX and mark as processed\n */\nSingleton {\n    id: root\n    \n    readonly property var renderPadding: 4 // This is to prevent cutoff in the rendered images\n\n    property list<string> processedHashes: []\n    property var processedExpressions: ({})\n    property var renderedImagePaths: ({})\n    property string microtexBinaryDir: \"/opt/MicroTeX\"\n    property string microtexBinaryName: \"LaTeX\"\n    property string latexOutputPath: Directories.latexOutput\n\n    signal renderFinished(string hash, string imagePath)\n\n    /**\n    * Requests rendering of a LaTeX expression.\n    * Returns the [hash, isNew]\n    */\n    function requestRender(expression) {\n        // 1. Hash it and initialize necessary variables\n        const hash = Qt.md5(expression)\n        const imagePath = `${latexOutputPath}/${hash}.svg`\n        \n        // 2. Check if the hash is already processed\n        if (processedHashes.includes(hash)) {\n            // console.log(\"Already processed: \" + hash)\n            renderFinished(hash, imagePath)\n            return [hash, false]\n        } else {\n            root.processedHashes.push(hash)\n            root.processedExpressions[hash] = expression\n            // console.log(\"Rendering expression: \" + expression)\n        }\n\n        // 3. If not, render it with MicroTeX and mark as processed\n        // console.log(`[LatexRenderer] Rendering expression: ${expression} with hash: ${hash}`)\n        // console.log(`                to file: ${imagePath}`)\n        // console.log(`                with command: cd ${microtexBinaryDir} && ./${microtexBinaryName} -headless -input=${StringUtils.shellSingleQuoteEscape(expression)} -output=${imagePath} -textsize=${Appearance.font.pixelSize.normal} -padding=${renderPadding} -background=${Appearance.m3colors.m3tertiary} -foreground=${Appearance.m3colors.m3onTertiary} -maxwidth=0.85`)\n        const processQml = `\n            import Quickshell.Io\n            Process {\n                id: microtexProcess${hash}\n                running: true\n                command: [ \"bash\", \"-c\", \n                    \"cd ${root.microtexBinaryDir} && ./${root.microtexBinaryName} -headless '-input=${StringUtils.shellSingleQuoteEscape(StringUtils.escapeBackslashes(expression))}' \"\n                    + \"'-output=${imagePath}' \" \n                    + \"'-textsize=${Appearance.font.pixelSize.normal}' \"\n                    + \"'-padding=${renderPadding}' \"\n                    // + \"'-background=${Appearance.m3colors.m3tertiary}' \"\n                    + \"'-foreground=${Appearance.colors.colOnLayer1}' \"\n                    + \"-maxwidth=0.85 \"\n                ]\n                // stdout: SplitParser {\n                //     onRead: data => { console.log(\"MicroTeX: \" + data) }\n                // }\n                onExited: (exitCode, exitStatus) => {\n                    // console.log(\"[LatexRenderer] MicroTeX process exited with code: \" + exitCode + \", status: \" + exitStatus)\n                    renderedImagePaths[\"${hash}\"] = \"${imagePath}\"\n                    root.renderFinished(\"${hash}\", \"${imagePath}\")\n                    microtexProcess${hash}.destroy()\n                }\n            }\n        `\n        // console.log(\"MicroTeX: \" + processQml)\n        Qt.createQmlObject(processQml, root, `MicroTeXProcess_${hash}`)\n        return [hash, true]\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/services/LauncherApps.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\n\nSingleton {\n    id: root\n\n    function isPinned(appId) {\n        return Config.options.launcher.pinnedApps.indexOf(appId) !== -1;\n    }\n\n    function togglePin(appId) {\n        if (root.isPinned(appId)) {\n            Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.filter(id => id !== appId)\n        } else {\n            Config.options.launcher.pinnedApps = Config.options.launcher.pinnedApps.concat([appId])\n        }\n    }\n\n    function moveToFront(appId) {\n        if (!root.isPinned(appId)) return;\n        const pinnedApps = Config.options.launcher.pinnedApps;\n        Config.options.launcher.pinnedApps = [appId].concat(pinnedApps.filter(id => id !== appId));\n    }\n\n    function moveLeft(appId) {\n        const pinnedApps = Config.options.launcher.pinnedApps;\n        const index = pinnedApps.indexOf(appId);\n        if (index === -1 || index === 0) return;\n        Config.options.launcher.pinnedApps = pinnedApps.slice(0, index - 1).concat([appId]).concat(pinnedApps[index - 1]).concat(pinnedApps.slice(index + 1));\n    }\n\n    function moveRight(appId) {\n        const pinnedApps = Config.options.launcher.pinnedApps;\n        const index = pinnedApps.indexOf(appId);\n        if (index === -1 || index === pinnedApps.length - 1) return;\n        Config.options.launcher.pinnedApps = pinnedApps.slice(0, index).concat(pinnedApps[index + 1]).concat([appId]).concat(pinnedApps.slice(index + 2));\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/LauncherSearch.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.functions\nimport QtQuick\nimport Qt.labs.folderlistmodel\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nSingleton {\n    id: root\n\n    property string query: \"\"\n\n    function ensurePrefix(prefix) {\n        if ([Config.options.search.prefix.action, Config.options.search.prefix.app, Config.options.search.prefix.clipboard, Config.options.search.prefix.emojis, Config.options.search.prefix.math, Config.options.search.prefix.shellCommand, Config.options.search.prefix.webSearch,].some(i => root.query.startsWith(i))) {\n            root.query = prefix + root.query.slice(1);\n        } else {\n            root.query = prefix + root.query;\n        }\n    }\n\n    // https://specifications.freedesktop.org/menu/latest/category-registry.html\n    property list<string> mainRegisteredCategories: [\"AudioVideo\", \"Development\", \"Education\", \"Game\", \"Graphics\", \"Network\", \"Office\", \"Science\", \"Settings\", \"System\", \"Utility\"]\n    property list<string> appCategories: DesktopEntries.applications.values.reduce((acc, entry) => {\n        for (const category of entry.categories) {\n            if (!acc.includes(category) && mainRegisteredCategories.includes(category)) {\n                acc.push(category);\n            }\n        }\n        return acc;\n    }, []).sort()\n\n    // Load user action scripts from ~/.config/illogical-impulse/actions/\n    // Uses FolderListModel to auto-reload when scripts are added/removed\n    property var userActionScripts: {\n        const actions = [];\n        for (let i = 0; i < userActionsFolder.count; i++) {\n            const fileName = userActionsFolder.get(i, \"fileName\");\n            const filePath = userActionsFolder.get(i, \"filePath\");\n            if (fileName && filePath) {\n                const actionName = fileName.replace(/\\.[^/.]+$/, \"\"); // strip extension\n                actions.push({\n                    action: actionName,\n                    execute: ((path) => (args) => {\n                        Quickshell.execDetached([path, ...(args ? args.split(\" \") : [])]);\n                    })(FileUtils.trimFileProtocol(filePath.toString()))\n                });\n            }\n        }\n        return actions;\n    }\n\n    FolderListModel {\n        id: userActionsFolder\n        folder: Qt.resolvedUrl(Directories.userActions)\n        showDirs: false\n        showHidden: false\n        sortField: FolderListModel.Name\n    }\n\n    property var searchActions: [\n        {\n            action: \"accentcolor\",\n            execute: args => {\n                Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--noswitch\", \"--color\", ...(args != '' ? [`${args}`] : [])]);\n            }\n        },\n        {\n            action: \"dark\",\n            execute: () => {\n                Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", \"dark\", \"--noswitch\"]);\n            }\n        },\n        {\n            action: \"konachanwallpaper\",\n            execute: () => {\n                Quickshell.execDetached([Quickshell.shellPath(\"scripts/colors/random/random_konachan_wall.sh\")]);\n            }\n        },\n        {\n            action: \"light\",\n            execute: () => {\n                Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", \"light\", \"--noswitch\"]);\n            }\n        },\n        {\n            action: \"superpaste\",\n            execute: args => {\n                if (!/^(\\d+)/.test(args.trim())) {\n                    // Invalid if doesn't start with numbers\n                    Quickshell.execDetached([\"notify-send\", Translation.tr(\"Superpaste\"), Translation.tr(\"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\").arg(Config.options.search.prefix.action), \"-a\", \"Shell\"]);\n                    return;\n                }\n                const syntaxMatch = /^(?:(\\d+)(i)?)/.exec(args.trim());\n                const count = syntaxMatch[1] ? parseInt(syntaxMatch[1]) : 1;\n                const isImage = !!syntaxMatch[2];\n                Cliphist.superpaste(count, isImage);\n            }\n        },\n        {\n            action: \"todo\",\n            execute: args => {\n                Todo.addTask(args);\n            }\n        },\n        {\n            action: \"wallpaper\",\n            execute: () => {\n                Hyprland.dispatch(`hl.dsp.global(\"quickshell:wallpaperSelectorToggle\")`)\n            }\n        },\n        {\n            action: \"wipeclipboard\",\n            execute: () => {\n                Cliphist.wipe();\n            }\n        },\n    ]\n\n    // Combined built-in and user actions\n    property var allActions: searchActions.concat(userActionScripts)\n\n    property string mathResult: \"\"\n    property bool clipboardWorkSafetyActive: {\n        const enabled = Config.options.workSafety.enable.clipboard;\n        const sensitiveNetwork = (StringUtils.stringListContainsSubstring(Network.networkName.toLowerCase(), Config.options.workSafety.triggerCondition.networkNameKeywords));\n        return enabled && sensitiveNetwork;\n    }\n\n    function containsUnsafeLink(entry) {\n        if (entry == undefined)\n            return false;\n        const unsafeKeywords = Config.options.workSafety.triggerCondition.linkKeywords;\n        return StringUtils.stringListContainsSubstring(entry.toLowerCase(), unsafeKeywords);\n    }\n\n    Timer {\n        id: nonAppResultsTimer\n        interval: Config.options.search.nonAppResultDelay\n        onTriggered: {\n            let expr = root.query;\n            if (expr.startsWith(Config.options.search.prefix.math)) {\n                expr = expr.slice(Config.options.search.prefix.math.length);\n            }\n            mathProc.calculateExpression(expr);\n        }\n    }\n\n    Process {\n        id: mathProc\n        property list<string> baseCommand: [\"qalc\", \"-t\"]\n        function calculateExpression(expression) {\n            mathProc.running = false;\n            mathProc.command = baseCommand.concat(expression);\n            mathProc.running = true;\n        }\n        stdout: SplitParser {\n            onRead: data => {\n                root.mathResult = data;\n            }\n        }\n    }\n\n    property list<var> results: {\n        // Search results are handled here\n        ////////////////// Skip? //////////////////\n        if (root.query == \"\")\n            return [];\n\n        ///////////// Special cases ///////////////\n        if (root.query.startsWith(Config.options.search.prefix.clipboard)) {\n            // Clipboard\n            const searchString = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.clipboard);\n            return Cliphist.fuzzyQuery(searchString).map((entry, index, array) => {\n                const mightBlurImage = Cliphist.entryIsImage(entry) && root.clipboardWorkSafetyActive;\n                let shouldBlurImage = mightBlurImage;\n                if (mightBlurImage) {\n                    shouldBlurImage = shouldBlurImage && (root.containsUnsafeLink(array[index - 1]) || root.containsUnsafeLink(array[index + 1]));\n                }\n                const type = `#${entry.match(/^\\s*(\\S+)/)?.[1] || \"\"}`;\n                return resultComp.createObject(null, {\n                    rawValue: entry,\n                    name: StringUtils.cleanCliphistEntry(entry),\n                    verb: \"\",\n                    type: type,\n                    execute: () => {\n                        Cliphist.copy(entry);\n                    },\n                    actions: [resultComp.createObject(null, {\n                            name: Translation.tr(\"Copy\"),\n                            iconName: \"content_copy\",\n                            iconType: LauncherSearchResult.IconType.Material,\n                            execute: () => {\n                                Cliphist.copy(entry);\n                            }\n                        }), resultComp.createObject(null, {\n                            name: Translation.tr(\"Delete\"),\n                            iconName: \"delete\",\n                            iconType: LauncherSearchResult.IconType.Material,\n                            execute: () => {\n                                Cliphist.deleteEntry(entry);\n                            }\n                        })],\n                    blurImage: shouldBlurImage\n                });\n            }).filter(Boolean);\n        } else if (root.query.startsWith(Config.options.search.prefix.emojis)) {\n            // Clipboard\n            const searchString = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.emojis);\n            return Emojis.fuzzyQuery(searchString).map(entry => {\n                const emoji = entry.match(/^\\s*(\\S+)/)?.[1] || \"\";\n                return resultComp.createObject(null, {\n                    rawValue: entry,\n                    name: entry.replace(/^\\s*\\S+\\s+/, \"\"),\n                    iconName: emoji,\n                    iconType: LauncherSearchResult.IconType.Text,\n                    verb: Translation.tr(\"Copy\"),\n                    type: Translation.tr(\"Emoji\"),\n                    execute: () => {\n                        Quickshell.clipboardText = entry.match(/^\\s*(\\S+)/)?.[1];\n                    }\n                });\n            }).filter(Boolean);\n        }\n\n        ////////////////// Init ///////////////////\n        nonAppResultsTimer.restart();\n        const mathResultObject = resultComp.createObject(null, {\n            name: root.mathResult,\n            verb: Translation.tr(\"Copy\"),\n            type: Translation.tr(\"Math result\"),\n            fontType: LauncherSearchResult.FontType.Monospace,\n            iconName: 'calculate',\n            iconType: LauncherSearchResult.IconType.Material,\n            execute: () => {\n                Quickshell.clipboardText = root.mathResult;\n            }\n        });\n        const appResultObjects = AppSearch.fuzzyQuery(StringUtils.cleanPrefix(root.query, Config.options.search.prefix.app)).map(entry => {\n            return resultComp.createObject(null, {\n                type: Translation.tr(\"App\"),\n                id: entry.id,\n                name: entry.name,\n                iconName: entry.icon,\n                iconType: LauncherSearchResult.IconType.System,\n                verb: Translation.tr(\"Open\"),\n                execute: () => {\n                    if (!entry.runInTerminal)\n                        entry.execute();\n                    else {\n                        // Probably needs more proper escaping, but this will do for now\n                        Quickshell.execDetached([\"bash\", '-c', `${Config.options.apps.terminal} -e '${StringUtils.shellSingleQuoteEscape(entry.command.join(' '))}'`]);\n                    }\n                },\n                comment: entry.comment,\n                runInTerminal: entry.runInTerminal,\n                genericName: entry.genericName,\n                keywords: entry.keywords,\n                actions: entry.actions.map(action => {\n                    return resultComp.createObject(null, {\n                        name: action.name,\n                        iconName: action.icon,\n                        iconType: LauncherSearchResult.IconType.System,\n                        execute: () => {\n                            if (!action.runInTerminal)\n                                action.execute();\n                            else {\n                                Quickshell.execDetached([\"bash\", '-c', `${Config.options.apps.terminal} -e '${StringUtils.shellSingleQuoteEscape(action.command.join(' '))}'`]);\n                            }\n                        }\n                    });\n                })\n            });\n        });\n        const commandResultObject = resultComp.createObject(null, {\n            name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.shellCommand).replace(\"file://\", \"\"),\n            verb: Translation.tr(\"Run\"),\n            type: Translation.tr(\"Command\"),\n            fontType: LauncherSearchResult.FontType.Monospace,\n            iconName: 'terminal',\n            iconType: LauncherSearchResult.IconType.Material,\n            execute: () => {\n                let cleanedCommand = root.query.replace(\"file://\", \"\");\n                cleanedCommand = StringUtils.cleanPrefix(cleanedCommand, Config.options.search.prefix.shellCommand);\n                if (cleanedCommand.startsWith(Config.options.search.prefix.shellCommand)) {\n                    cleanedCommand = cleanedCommand.slice(Config.options.search.prefix.shellCommand.length);\n                }\n                Quickshell.execDetached([\"bash\", \"-c\", root.query.startsWith('sudo') ? `${Config.options.apps.terminal} fish -C '${cleanedCommand}'` : cleanedCommand]);\n            }\n        });\n        const webSearchResultObject = resultComp.createObject(null, {\n            name: StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch),\n            verb: Translation.tr(\"Search\"),\n            type: Translation.tr(\"Web search\"),\n            iconName: 'travel_explore',\n            iconType: LauncherSearchResult.IconType.Material,\n            execute: () => {\n                let query = StringUtils.cleanPrefix(root.query, Config.options.search.prefix.webSearch);\n                let url = Config.options.search.engineBaseUrl + query;\n                for (let site of Config.options.search.excludedSites) {\n                    url += ` -site:${site}`;\n                }\n                Qt.openUrlExternally(url);\n            }\n        });\n        const launcherActionObjects = root.allActions.map(action => {\n            const actionString = `${Config.options.search.prefix.action}${action.action}`;\n            if (actionString.startsWith(root.query) || root.query.startsWith(actionString)) {\n                return resultComp.createObject(null, {\n                    name: root.query.startsWith(actionString) ? root.query : actionString,\n                    verb: Translation.tr(\"Run\"),\n                    type: Translation.tr(\"Action\"),\n                    iconName: 'settings_suggest',\n                    iconType: LauncherSearchResult.IconType.Material,\n                    execute: () => {\n                        action.execute(root.query.split(\" \").slice(1).join(\" \"));\n                    }\n                });\n            }\n            return null;\n        }).filter(Boolean);\n\n        //////// Prioritized by prefix /////////\n        let result = [];\n        const startsWithNumber = /^\\d/.test(root.query);\n        const startsWithMathPrefix = root.query.startsWith(Config.options.search.prefix.math);\n        const startsWithShellCommandPrefix = root.query.startsWith(Config.options.search.prefix.shellCommand);\n        const startsWithWebSearchPrefix = root.query.startsWith(Config.options.search.prefix.webSearch);\n        if (startsWithNumber || startsWithMathPrefix) {\n            result.push(mathResultObject);\n        } else if (startsWithShellCommandPrefix) {\n            result.push(commandResultObject);\n        } else if (startsWithWebSearchPrefix) {\n            result.push(webSearchResultObject);\n        }\n\n        //////////////// Apps //////////////////\n        result = result.concat(appResultObjects);\n\n        ////////// Launcher actions ////////////\n        result = result.concat(launcherActionObjects);\n\n        /// Math result, command, web search ///\n        if (Config.options.search.prefix.showDefaultActionsWithoutPrefix) {\n            if (!startsWithShellCommandPrefix)\n                result.push(commandResultObject);\n            if (!startsWithNumber && !startsWithMathPrefix)\n                result.push(mathResultObject);\n            if (!startsWithWebSearchPrefix)\n                result.push(webSearchResultObject);\n        }\n\n        return result;\n    }\n\n    Component {\n        id: resultComp\n        LauncherSearchResult {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/MaterialThemeLoader.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\n/**\n * Automatically reloads generated material colors.\n * It is necessary to run reapplyTheme() on startup because Singletons are lazily loaded.\n */\nSingleton {\n    id: root\n    property string filePath: Directories.generatedMaterialThemePath\n\n    function reapplyTheme() {\n        themeFileView.reload()\n    }\n\n    function applyColors(fileContent) {\n        const json = JSON.parse(fileContent)\n        for (const key in json) {\n            if (json.hasOwnProperty(key)) {\n                // Convert snake_case to CamelCase\n                const camelCaseKey = key.replace(/_([a-z])/g, (g) => g[1].toUpperCase())\n                const m3Key = `m3${camelCaseKey}`\n                Appearance.m3colors[m3Key] = json[key]\n            }\n        }\n        \n        Appearance.m3colors.darkmode = (Appearance.m3colors.m3background.hslLightness < 0.5)\n    }\n\n    function resetFilePathNextTime() {\n        resetFilePathNextWallpaperChange.enabled = true\n    }\n\n    Connections {\n        id: resetFilePathNextWallpaperChange\n        enabled: false\n        target: Config.options.background\n        function onWallpaperPathChanged() {\n            root.filePath = \"\"\n            root.filePath = Directories.generatedMaterialThemePath\n            resetFilePathNextWallpaperChange.enabled = false\n        }\n    }\n\n    Timer {\n        id: delayedFileRead\n        interval: Config.options?.hacks?.arbitraryRaceConditionDelay ?? 100\n        repeat: false\n        running: false\n        onTriggered: {\n            root.applyColors(themeFileView.text())\n        }\n    }\n\n\tFileView { \n        id: themeFileView\n        path: Qt.resolvedUrl(root.filePath)\n        watchChanges: true\n        onFileChanged: {\n            this.reload()\n            delayedFileRead.start()\n        }\n        onLoadedChanged: {\n            const fileContent = themeFileView.text()\n            root.applyColors(fileContent)\n        }\n        onLoadFailed: root.resetFilePathNextTime();\n    }\n\n    function toggleLightDark() {\n        const currentlyDark = Appearance.m3colors.darkmode;\n        Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", currentlyDark ? \"light\" : \"dark\", \"--noswitch\"]);\n    }\n\n    GlobalShortcut {\n        name: \"toggleLightDark\"\n        description: \"Toggles between dark theme and light theme\"\n\n        onPressed: {\n            root.toggleLightDark();\n        }\n    }\n\n    IpcHandler {\n        target: \"theme\"\n\n        function toggleLightDark(): void {\n            root.toggleLightDark();\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/MprisController.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\n// From https://git.outfoxxed.me/outfoxxed/nixnew\n// It does not have a license, but the author is okay with redistribution.\n\nimport QtQml.Models\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Services.Mpris\nimport qs.modules.common\n\n/**\n * A service that provides easy access to the active Mpris player.\n */\nSingleton {\n\tid: root;\n\tproperty list<MprisPlayer> players: Mpris.players.values.filter(player => isRealPlayer(player));\n\tproperty MprisPlayer trackedPlayer: null;\n\tproperty MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null;\n\tsignal trackChanged(reverse: bool);\n\n\tproperty bool __reverse: false;\n\n\tproperty var activeTrack;\n\n\treadonly property bool hasActivePlasmaIntegration: Mpris.players.values.some(\n\t\tp => p.dbusName?.startsWith('org.mpris.MediaPlayer2.plasma-browser-integration')\n\t)\n\tfunction isRealPlayer(player) {\n        if (!Config.options.media.filterDuplicatePlayers) {\n            return true;\n        }\n        return (\n            // Remove native browser buses only if plasma-browser-integration is actually active on D-Bus\n            !(hasActivePlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.firefox')) && !(hasActivePlasmaIntegration && player.dbusName.startsWith('org.mpris.MediaPlayer2.chromium')) &&\n            // playerctld just copies other buses and we don't need duplicates\n            !player.dbusName?.startsWith('org.mpris.MediaPlayer2.playerctld') &&\n            // Non-instance mpd bus\n            !(player.dbusName?.endsWith('.mpd') && !player.dbusName.endsWith('MediaPlayer2.mpd')));\n    }\n\n\t// Original stuff from fox below\n\tInstantiator {\n\t\tmodel: Mpris.players;\n\n\t\tConnections {\n\t\t\trequired property MprisPlayer modelData;\n\t\t\ttarget: modelData;\n\n\t\t\tComponent.onCompleted: {\n\t\t\t\tif (root.trackedPlayer == null || modelData.isPlaying) {\n\t\t\t\t\troot.trackedPlayer = modelData;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tComponent.onDestruction: {\n\t\t\t\tif (root.trackedPlayer == null || !root.trackedPlayer.isPlaying) {\n\t\t\t\t\tfor (const player of Mpris.players.values) {\n\t\t\t\t\t\tif (player.playbackState.isPlaying) {\n\t\t\t\t\t\t\troot.trackedPlayer = player;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (trackedPlayer == null && Mpris.players.values.length != 0) {\n\t\t\t\t\t\ttrackedPlayer = Mpris.players.values[0];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfunction onPlaybackStateChanged() {\n\t\t\t\tif (root.trackedPlayer !== modelData) root.trackedPlayer = modelData;\n\t\t\t}\n\t\t}\n\t}\n\n\tConnections {\n\t\ttarget: activePlayer\n\n\t\tfunction onPostTrackChanged() {\n\t\t\troot.updateTrack();\n\t\t}\n\n\t\tfunction onTrackArtUrlChanged() {\n\t\t\t// console.log(\"arturl:\", activePlayer.trackArtUrl)\n\t\t\t// root.updateTrack();\n\t\t\tif (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {\n\t\t\t\t// cantata likes to send cover updates *BEFORE* updating the track info.\n\t\t\t\t// as such, art url changes shouldn't be able to break the reverse animation\n\t\t\t\tconst r = root.__reverse;\n\t\t\t\troot.updateTrack();\n\t\t\t\troot.__reverse = r;\n\n\t\t\t}\n\t\t}\n\t}\n\n\tonActivePlayerChanged: this.updateTrack();\n\n\tfunction updateTrack() {\n\t\t//console.log(`update: ${this.activePlayer?.trackTitle ?? \"\"} : ${this.activePlayer?.trackArtists}`)\n\t\tthis.activeTrack = {\n\t\t\tuniqueId: this.activePlayer?.uniqueId ?? 0,\n\t\t\tartUrl: this.activePlayer?.trackArtUrl ?? \"\",\n\t\t\ttitle: this.activePlayer?.trackTitle || Translation.tr(\"Unknown Title\"),\n\t\t\tartist: this.activePlayer?.trackArtist || Translation.tr(\"Unknown Artist\"),\n\t\t\talbum: this.activePlayer?.trackAlbum || Translation.tr(\"Unknown Album\"),\n\t\t};\n\n\t\tthis.trackChanged(__reverse);\n\t\tthis.__reverse = false;\n\t}\n\n\tproperty bool isPlaying: this.activePlayer && this.activePlayer.isPlaying;\n\tproperty bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false;\n\tfunction togglePlaying() {\n\t\tif (this.canTogglePlaying) this.activePlayer.togglePlaying();\n\t}\n\n\tproperty bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false;\n\tfunction previous() {\n\t\tif (this.canGoPrevious) {\n\t\t\tthis.__reverse = true;\n\t\t\tthis.activePlayer.previous();\n\t\t}\n\t}\n\n\tproperty bool canGoNext: this.activePlayer?.canGoNext ?? false;\n\tfunction next() {\n\t\tif (this.canGoNext) {\n\t\t\tthis.__reverse = false;\n\t\t\tthis.activePlayer.next();\n\t\t}\n\t}\n\n\tproperty bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl;\n\n\tproperty bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl;\n\tproperty var loopState: this.activePlayer?.loopState ?? MprisLoopState.None;\n\tfunction setLoopState(loopState: var) {\n\t\tif (this.loopSupported) {\n\t\t\tthis.activePlayer.loopState = loopState;\n\t\t}\n\t}\n\n\tproperty bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl;\n\tproperty bool hasShuffle: this.activePlayer?.shuffle ?? false;\n\tfunction setShuffle(shuffle: bool) {\n\t\tif (this.shuffleSupported) {\n\t\t\tthis.activePlayer.shuffle = shuffle;\n\t\t}\n\t}\n\n\tfunction setActivePlayer(player: MprisPlayer) {\n\t\tconst targetPlayer = player ?? Mpris.players[0];\n\t\tconsole.log(`[Mpris] Active player ${targetPlayer} << ${activePlayer}`)\n\n\t\tif (targetPlayer && this.activePlayer) {\n\t\t\tthis.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer);\n\t\t} else {\n\t\t\t// always animate forward if going to null\n\t\t\tthis.__reverse = false;\n\t\t}\n\n\t\tthis.trackedPlayer = targetPlayer;\n\t}\n\n\tIpcHandler {\n\t\ttarget: \"mpris\"\n\n\t\tfunction pauseAll(): void {\n\t\t\tfor (const player of Mpris.players.values) {\n\t\t\t\tif (player.canPause) player.pause();\n\t\t\t}\n\t\t}\n\n\t\tfunction playPause(): void { root.togglePlaying(); }\n\t\tfunction previous(): void { root.previous(); }\n\t\tfunction next(): void { root.next(); }\n\t}\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Network.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\n// Took many bits from https://github.com/caelestia-dots/shell (GPLv3)\n\nimport Quickshell\nimport Quickshell.Io\nimport QtQuick\nimport qs.services.network\n\n/**\n * Network service with nmcli.\n */\nSingleton {\n    id: root\n\n    property bool wifi: true\n    property bool ethernet: false\n\n    property bool wifiEnabled: false\n    property bool wifiScanning: false\n    property bool wifiConnecting: connectProc.running\n    property WifiAccessPoint wifiConnectTarget\n    readonly property list<WifiAccessPoint> wifiNetworks: []\n    readonly property WifiAccessPoint active: wifiNetworks.find(n => n.active) ?? null\n    readonly property list<var> friendlyWifiNetworks: [...wifiNetworks].sort((a, b) => {\n        if (a.active && !b.active)\n            return -1;\n        if (!a.active && b.active)\n            return 1;\n        return b.strength - a.strength;\n    })\n    property string wifiStatus: \"disconnected\"\n\n    property string networkName: \"\"\n    property int networkStrength\n    property string materialSymbol: root.ethernet\n        ? \"lan\"\n        : (root.wifiEnabled && root.wifiStatus === \"connected\")\n            ? (\n                (root.active?.strength ?? 0) > 83 ? \"signal_wifi_4_bar\" :\n                (root.active?.strength ?? 0) > 67 ? \"network_wifi\" :\n                (root.active?.strength ?? 0) > 50 ? \"network_wifi_3_bar\" :\n                (root.active?.strength ?? 0) > 33 ? \"network_wifi_2_bar\" :\n                (root.active?.strength ?? 0) > 17 ? \"network_wifi_1_bar\" :\n                \"signal_wifi_0_bar\"\n            )\n            : (root.wifiStatus === \"connecting\")\n                ? \"signal_wifi_statusbar_not_connected\"\n                : (root.wifiStatus === \"disconnected\")\n                    ? \"wifi_find\"\n                    : (root.wifiStatus === \"disabled\")\n                        ? \"signal_wifi_off\"\n                        : \"signal_wifi_bad\"\n\n    // Control\n    function enableWifi(enabled = true): void {\n        const cmd = enabled ? \"on\" : \"off\";\n        enableWifiProc.exec([\"nmcli\", \"radio\", \"wifi\", cmd]);\n    }\n\n    function toggleWifi(): void {\n        enableWifi(!wifiEnabled);\n    }\n\n    function rescanWifi(): void {\n        wifiScanning = true;\n        rescanProcess.running = true;\n    }\n\n    function connectToWifiNetwork(accessPoint: WifiAccessPoint): void {\n        accessPoint.askingPassword = false;\n        root.wifiConnectTarget = accessPoint;\n        // We use this instead of `nmcli connection up SSID` because this also creates a connection profile\n        connectProc.exec([\"nmcli\", \"dev\", \"wifi\", \"connect\", accessPoint.ssid])\n\n    }\n\n    function disconnectWifiNetwork(): void {\n        if (active) disconnectProc.exec([\"nmcli\", \"connection\", \"down\", active.ssid]);\n    }\n\n    function openPublicWifiPortal() {\n        Quickshell.execDetached([\"xdg-open\", \"https://nmcheck.gnome.org/\"]) // From some StackExchange thread, seems to work\n    }\n\n    function changePassword(network: WifiAccessPoint, password: string, username = \"\"): void {\n        // TODO: enterprise wifi with username\n        network.askingPassword = false;\n        changePasswordProc.exec({\n            \"environment\": {\n                \"PASSWORD\": password,\n                \"SSID\": network.ssid\n            },\n            \"command\": [\"bash\", \"-c\", 'nmcli connection modify \"$SSID\" wifi-sec.psk \"$PASSWORD\"']\n        })\n    }\n\n    Process {\n        id: enableWifiProc\n    }\n\n    Process {\n        id: connectProc\n        environment: ({\n            LANG: \"C\",\n            LC_ALL: \"C\"\n        })\n        stdout: SplitParser {\n            onRead: line => {\n                // print(line)\n                getNetworks.running = true\n            }\n        }\n        stderr: SplitParser {\n            onRead: line => {\n                // print(\"err:\", line)\n                if (line.includes(\"Secrets were required\")) {\n                    root.wifiConnectTarget.askingPassword = true\n                }\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            root.wifiConnectTarget.askingPassword = (exitCode !== 0)\n            root.wifiConnectTarget = null\n        }\n    }\n\n    Process {\n        id: disconnectProc\n        stdout: SplitParser {\n            onRead: getNetworks.running = true\n        }\n    }\n\n    Process {\n        id: changePasswordProc\n        onExited: { // Re-attempt connection after changing password\n            connectProc.running = false\n            connectProc.running = true\n        }\n    }\n\n    Process {\n        id: rescanProcess\n        command: [\"nmcli\", \"dev\", \"wifi\", \"list\", \"--rescan\", \"yes\"]\n        stdout: SplitParser {\n            onRead: {\n                wifiScanning = false;\n                getNetworks.running = true;\n            }\n        }\n    }\n\n    // Status update\n    function update() {\n        updateConnectionType.startCheck();\n        wifiStatusProcess.running = true\n        updateNetworkName.running = true;\n        updateNetworkStrength.running = true;\n    }\n\n    Process {\n        id: subscriber\n        running: true\n        command: [\"nmcli\", \"monitor\"]\n        stdout: SplitParser {\n            onRead: root.update()\n        }\n    }\n\n    Process {\n        id: updateConnectionType\n        property string buffer\n        command: [\"sh\", \"-c\", \"nmcli -t -f TYPE,STATE d status && nmcli -t -f CONNECTIVITY g\"]\n        running: true\n        function startCheck() {\n            buffer = \"\";\n            updateConnectionType.running = true;\n        }\n        stdout: SplitParser {\n            onRead: data => {\n                updateConnectionType.buffer += data + \"\\n\";\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            const lines = updateConnectionType.buffer.trim().split('\\n');\n            const connectivity = lines.pop() // none, limited, full\n            let hasEthernet = false;\n            let hasWifi = false;\n            let wifiStatus = \"disconnected\";\n            lines.forEach(line => {\n                if (line.includes(\"ethernet\") && line.includes(\"connected\"))\n                    hasEthernet = true;\n                else if (line.includes(\"wifi:\")) {\n                    if (line.includes(\"disconnected\")) {\n                        wifiStatus = \"disconnected\"\n                    }\n                    else if (line.includes(\"connected\")) {\n                        hasWifi = true;\n                        wifiStatus = \"connected\"\n\n                        if (connectivity === \"limited\") {\n                            hasWifi = false;\n                            wifiStatus = \"limited\"\n                        }\n                    }\n                    else if (line.includes(\"connecting\")) {\n                        wifiStatus = \"connecting\"\n                    }\n                    else if (line.includes(\"unavailable\")) {\n                        wifiStatus = \"disabled\"\n                    }\n                }\n            });\n            root.wifiStatus = wifiStatus;\n            root.ethernet = hasEthernet;\n            root.wifi = hasWifi;\n        }\n    }\n\n    Process {\n        id: updateNetworkName\n        command: [\"sh\", \"-c\", \"nmcli -t -f NAME c show --active | head -1\"]\n        running: true\n        stdout: SplitParser {\n            onRead: data => {\n                root.networkName = data;\n            }\n        }\n    }\n\n    Process {\n        id: updateNetworkStrength\n        running: true\n        command: [\"sh\", \"-c\", \"nmcli -f IN-USE,SIGNAL,SSID device wifi | awk '/^\\\\*/{if (NR!=1) {print $2}}'\"]\n        stdout: SplitParser {\n            onRead: data => {\n                root.networkStrength = parseInt(data);\n            }\n        }\n    }\n\n    Process {\n        id: wifiStatusProcess\n        command: [\"nmcli\", \"radio\", \"wifi\"]\n        Component.onCompleted: running = true\n        environment: ({\n            LANG: \"C\",\n            LC_ALL: \"C\"\n        })\n        stdout: StdioCollector {\n            onStreamFinished: {\n                root.wifiEnabled = text.trim() === \"enabled\";\n            }\n        }\n    }\n\n    Process {\n        id: getNetworks\n        running: true\n        command: [\"nmcli\", \"-g\", \"ACTIVE,SIGNAL,FREQ,SSID,BSSID,SECURITY\", \"d\", \"w\"]\n        environment: ({\n            LANG: \"C\",\n            LC_ALL: \"C\"\n        })\n        stdout: StdioCollector {\n            onStreamFinished: {\n                const PLACEHOLDER = \"STRINGWHICHHOPEFULLYWONTBEUSED\";\n                const rep = new RegExp(\"\\\\\\\\:\", \"g\");\n                const rep2 = new RegExp(PLACEHOLDER, \"g\");\n\n                const allNetworks = text.trim().split(\"\\n\").map(n => {\n                    const net = n.replace(rep, PLACEHOLDER).split(\":\");\n                    return {\n                        active: net[0] === \"yes\",\n                        strength: parseInt(net[1]),\n                        frequency: parseInt(net[2]),\n                        ssid: net[3],\n                        bssid: net[4]?.replace(rep2, \":\") ?? \"\",\n                        security: net[5] || \"\"\n                    };\n                }).filter(n => n.ssid && n.ssid.length > 0);\n\n                // Group networks by SSID and prioritize connected ones\n                const networkMap = new Map();\n                for (const network of allNetworks) {\n                    const existing = networkMap.get(network.ssid);\n                    if (!existing) {\n                        networkMap.set(network.ssid, network);\n                    } else {\n                        // Prioritize active/connected networks\n                        if (network.active && !existing.active) {\n                            networkMap.set(network.ssid, network);\n                        } else if (!network.active && !existing.active) {\n                            // If both are inactive, keep the one with better signal\n                            if (network.strength > existing.strength) {\n                                networkMap.set(network.ssid, network);\n                            }\n                        }\n                        // If existing is active and new is not, keep existing\n                    }\n                }\n\n                const wifiNetworks = Array.from(networkMap.values());\n\n                const rNetworks = root.wifiNetworks;\n\n                const destroyed = rNetworks.filter(rn => !wifiNetworks.find(n => n.frequency === rn.frequency && n.ssid === rn.ssid && n.bssid === rn.bssid));\n                for (const network of destroyed)\n                    rNetworks.splice(rNetworks.indexOf(network), 1).forEach(n => n.destroy());\n\n                for (const network of wifiNetworks) {\n                    const match = rNetworks.find(n => n.frequency === network.frequency && n.ssid === network.ssid && n.bssid === network.bssid);\n                    if (match) {\n                        match.lastIpcObject = network;\n                    } else {\n                        rNetworks.push(apComp.createObject(root, {\n                            lastIpcObject: network\n                        }));\n                    }\n                }\n            }\n        }\n    }\n\n    Component {\n        id: apComp\n\n        WifiAccessPoint {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Notifications.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport qs\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Services.Notifications\n\n/**\n * Provides extra features not in Quickshell.Services.Notifications:\n *  - Persistent storage\n *  - Popup notifications, with timeout\n *  - Notification groups by app\n */\nSingleton {\n\tid: root\n    component Notif: QtObject {\n        id: wrapper\n        required property int notificationId // Could just be `id` but it conflicts with the default prop in QtObject\n        property Notification notification\n        property list<var> actions: notification?.actions.map((action) => ({\n            \"identifier\": action.identifier,\n            \"text\": action.text,\n        })) ?? []\n        property bool popup: false\n        property bool isTransient: notification?.hints.transient ?? false\n        property string appIcon: notification?.appIcon ?? \"\"\n        property string appName: notification?.appName ?? \"\"\n        property string body: notification?.body ?? \"\"\n        property string image: notification?.image ?? \"\"\n        property string summary: notification?.summary ?? \"\"\n        property double time\n        property string urgency: notification?.urgency.toString() ?? \"normal\"\n        property Timer timer\n\n        onNotificationChanged: {\n            if (notification === null) {\n                root.discardNotification(notificationId);\n            }\n        }\n    }\n\n    function notifToJSON(notif) {\n        return {\n            \"notificationId\": notif.notificationId,\n            \"actions\": notif.actions,\n            \"appIcon\": notif.appIcon,\n            \"appName\": notif.appName,\n            \"body\": notif.body,\n            \"image\": notif.image,\n            \"summary\": notif.summary,\n            \"time\": notif.time,\n            \"urgency\": notif.urgency,\n        }\n    }\n    function notifToString(notif) {\n        return JSON.stringify(notifToJSON(notif), null, 2);\n    }\n\n    component NotifTimer: Timer {\n        required property int notificationId\n        interval: 7000\n        running: true\n        onTriggered: () => {\n            const index = root.list.findIndex((notif) => notif.notificationId === notificationId);\n            const notifObject = root.list[index];\n            print(\"[Notifications] Notification timer triggered for ID: \" + notificationId + \", transient: \" + notifObject?.isTransient);\n            if (notifObject.isTransient) root.discardNotification(notificationId);\n            else root.timeoutNotification(notificationId);\n            destroy()\n        }\n    }\n\n    property bool silent: false\n    property int unread: 0\n    property var filePath: Directories.notificationsPath\n    property list<Notif> list: []\n    property var popupList: list.filter((notif) => notif.popup);\n    property bool popupInhibited: (GlobalStates?.sidebarRightOpen ?? false) || silent\n    property var latestTimeForApp: ({})\n    Component {\n        id: notifComponent\n        Notif {}\n    }\n    Component {\n        id: notifTimerComponent\n        NotifTimer {}\n    }\n\n    function stringifyList(list) {\n        return JSON.stringify(list.map((notif) => notifToJSON(notif)), null, 2);\n    }\n    \n    onListChanged: {\n        // Update latest time for each app\n        root.list.forEach((notif) => {\n            if (!root.latestTimeForApp[notif.appName] || notif.time > root.latestTimeForApp[notif.appName]) {\n                root.latestTimeForApp[notif.appName] = Math.max(root.latestTimeForApp[notif.appName] || 0, notif.time);\n            }\n        });\n        // Remove apps that no longer have notifications\n        Object.keys(root.latestTimeForApp).forEach((appName) => {\n            if (!root.list.some((notif) => notif.appName === appName)) {\n                delete root.latestTimeForApp[appName];\n            }\n        });\n    }\n\n    function appNameListForGroups(groups) {\n        return Object.keys(groups).sort((a, b) => {\n            // Sort by time, descending\n            return groups[b].time - groups[a].time;\n        });\n    }\n\n    function groupsForList(list) {\n        const groups = {};\n        list.forEach((notif) => {\n            if (!groups[notif.appName]) {\n                groups[notif.appName] = {\n                    appName: notif.appName,\n                    appIcon: notif.appIcon,\n                    notifications: [],\n                    time: 0\n                };\n            }\n            groups[notif.appName].notifications.push(notif);\n            // Always set to the latest time in the group\n            groups[notif.appName].time = latestTimeForApp[notif.appName] || notif.time;\n        });\n        return groups;\n    }\n\n    property var groupsByAppName: groupsForList(root.list)\n    property var popupGroupsByAppName: groupsForList(root.popupList)\n    property list<string> appNameList: appNameListForGroups(root.groupsByAppName)\n    property list<string> popupAppNameList: appNameListForGroups(root.popupGroupsByAppName)\n\n    // Quickshell's notification IDs starts at 1 on each run, while saved notifications\n    // can already contain higher IDs. This is for avoiding id collisions\n    property int idOffset\n    signal initDone();\n    signal notify(notification: var);\n    signal discard(id: int);\n    signal discardAll();\n    signal timeout(id: var);\n\n\tNotificationServer {\n        id: notifServer\n        // actionIconsSupported: true\n        actionsSupported: true\n        bodyHyperlinksSupported: true\n        bodyImagesSupported: true\n        bodyMarkupSupported: true\n        bodySupported: true\n        imageSupported: true\n        keepOnReload: false\n        persistenceSupported: true\n\n        onNotification: (notification) => {\n            notification.tracked = true\n            const newNotifObject = notifComponent.createObject(root, {\n                \"notificationId\": notification.id + root.idOffset,\n                \"notification\": notification,\n                \"time\": Date.now(),\n            });\n\t\t\troot.list = [...root.list, newNotifObject];\n\n            // Popup\n            if (!root.popupInhibited) {\n                newNotifObject.popup = true;\n                if (notification.expireTimeout != 0) {\n                    newNotifObject.timer = notifTimerComponent.createObject(root, {\n                        \"notificationId\": newNotifObject.notificationId,\n                        \"interval\": notification.expireTimeout < 0 ? (Config?.options.notifications.timeout ?? 7000) : notification.expireTimeout,\n                    });\n                }\n                root.unread++;\n            }\n            root.notify(newNotifObject);\n            // console.log(notifToString(newNotifObject));\n            notifFileView.setText(stringifyList(root.list));\n        }\n    }\n\n    function markAllRead() {\n        root.unread = 0;\n    }\n\n    function discardNotification(id) {\n        console.log(\"[Notifications] Discarding notification with ID: \" + id);\n        const index = root.list.findIndex((notif) => notif.notificationId === id);\n        const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id);\n        if (index !== -1) {\n            root.list.splice(index, 1);\n            notifFileView.setText(stringifyList(root.list));\n            triggerListChange()\n        }\n        if (notifServerIndex !== -1) {\n            notifServer.trackedNotifications.values[notifServerIndex].dismiss()\n        }\n        root.discard(id); // Emit signal\n    }\n\n    function discardAllNotifications() {\n        root.list = []\n        triggerListChange()\n        notifFileView.setText(stringifyList(root.list));\n        notifServer.trackedNotifications.values.forEach((notif) => {\n            notif.dismiss()\n        })\n        root.discardAll();\n    }\n\n    function cancelTimeout(id) {\n        const index = root.list.findIndex((notif) => notif.notificationId === id);\n        if (root.list[index] != null)\n            root.list[index].timer.stop();\n    }\n\n    function timeoutNotification(id) {\n        const index = root.list.findIndex((notif) => notif.notificationId === id);\n        if (root.list[index] != null)\n            root.list[index].popup = false;\n        root.timeout(id);\n    }\n\n    function timeoutAll() {\n        root.popupList.forEach((notif) => {\n            root.timeout(notif.notificationId);\n        })\n        root.popupList.forEach((notif) => {\n            notif.popup = false;\n        });\n    }\n\n    function attemptInvokeAction(id, notifIdentifier) {\n        console.log(\"[Notifications] Attempting to invoke action with identifier: \" + notifIdentifier + \" for notification ID: \" + id);\n        const notifServerIndex = notifServer.trackedNotifications.values.findIndex((notif) => notif.id + root.idOffset === id);\n        console.log(\"Notification server index: \" + notifServerIndex);\n        if (notifServerIndex !== -1) {\n            const notifServerNotif = notifServer.trackedNotifications.values[notifServerIndex];\n            const action = notifServerNotif.actions.find((action) => action.identifier === notifIdentifier);\n            // console.log(\"Action found: \" + JSON.stringify(action));\n            action.invoke()\n        } \n        else {\n            console.log(\"Notification not found in server: \" + id)\n        }\n        root.discardNotification(id);\n    }\n\n    function triggerListChange() {\n        root.list = root.list.slice(0)\n    }\n\n    function refresh() {\n        notifFileView.reload()\n    }\n\n    Component.onCompleted: {\n        refresh()\n    }\n\n    FileView {\n        id: notifFileView\n        path: Qt.resolvedUrl(filePath)\n        onLoaded: {\n            const fileContents = notifFileView.text()\n            root.list = JSON.parse(fileContents).map((notif) => {\n                return notifComponent.createObject(root, {\n                    \"notificationId\": notif.notificationId,\n                    \"actions\": [], // Notification actions are meaningless if they're not tracked by the server or the sender is dead\n                    \"appIcon\": notif.appIcon,\n                    \"appName\": notif.appName,\n                    \"body\": notif.body,\n                    \"image\": notif.image,\n                    \"summary\": notif.summary,\n                    \"time\": notif.time,\n                    \"urgency\": notif.urgency,\n                });\n            });\n            // Find largest notificationId\n            let maxId = 0\n            root.list.forEach((notif) => {\n                maxId = Math.max(maxId, notif.notificationId)\n            })\n\n            console.log(\"[Notifications] File loaded\")\n            root.idOffset = maxId\n            root.initDone()\n        }\n        onLoadFailed: (error) => {\n            if(error == FileViewError.FileNotFound) {\n                console.log(\"[Notifications] File not found, creating new file.\")\n                root.list = []\n                notifFileView.setText(stringifyList(root.list));\n            } else {\n                console.log(\"[Notifications] Error loading file: \" + error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/PolkitService.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.Polkit\n\nSingleton {\n    id: root\n    property alias agent: polkitAgent\n    property alias active: polkitAgent.isActive\n    property alias flow: polkitAgent.flow\n    property bool interactionAvailable: false\n    property string cleanMessage: {\n        if (!root.flow) return \"\";\n        return root.flow.message.endsWith(\".\")\n            ? root.flow.message.slice(0, -1)\n            : root.flow.message\n    }\n    property string cleanPrompt: {\n        const inputPrompt = PolkitService.flow?.inputPrompt.trim() ?? \"\";\n        const cleanedInputPrompt = inputPrompt.endsWith(\":\") ? inputPrompt.slice(0, -1) : inputPrompt;\n        const usePasswordChars = !PolkitService.flow?.responseVisible ?? true\n        return cleanedInputPrompt || (usePasswordChars ? Translation.tr(\"Password\") : Translation.tr(\"Input\"))\n    }\n\n    function cancel() {\n        root.flow.cancelAuthenticationRequest()\n    }\n\n    function submit(string) {\n        root.flow.submit(string)\n        root.interactionAvailable = false\n    }\n\n    Connections {\n        target: root.flow\n        function onAuthenticationFailed() {\n            root.interactionAvailable = true;\n        }\n    }\n\n    PolkitAgent {\n        id: polkitAgent\n        onAuthenticationRequestStarted: {\n            root.interactionAvailable = true;\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Privacy.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.Pipewire\n\n/**\n * Screensharing and mic activity.\n */\nSingleton {\n    id: root\n\n    property bool screenSharing: Pipewire.linkGroups.values.filter(pwlg => pwlg.source.type === PwNodeType.VideoSource).map(pwlg => pwlg.target)\n    property bool micActive: Pipewire.linkGroups.values.filter(pwlg => pwlg.source.type === PwNodeType.AudioSource && pwlg.target.type === PwNodeType.AudioInStream).map(pwlg => pwlg.target)\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ResourceUsage.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\n/**\n * Simple polled resource usage service with RAM, Swap, and CPU usage.\n */\nSingleton {\n    id: root\n\tproperty real memoryTotal: 1\n\tproperty real memoryFree: 0\n\tproperty real memoryUsed: memoryTotal - memoryFree\n    property real memoryUsedPercentage: memoryUsed / memoryTotal\n    property real swapTotal: 1\n\tproperty real swapFree: 0\n\tproperty real swapUsed: swapTotal - swapFree\n    property real swapUsedPercentage: swapTotal > 0 ? (swapUsed / swapTotal) : 0\n    property real cpuUsage: 0\n    property var previousCpuStats\n\n    property string maxAvailableMemoryString: kbToGbString(ResourceUsage.memoryTotal)\n    property string maxAvailableSwapString: kbToGbString(ResourceUsage.swapTotal)\n    property string maxAvailableCpuString: \"--\"\n\n    readonly property int historyLength: Config?.options.resources.historyLength ?? 60\n    property list<real> cpuUsageHistory: []\n    property list<real> memoryUsageHistory: []\n    property list<real> swapUsageHistory: []\n\n    function kbToGbString(kb) {\n        return (kb / (1024 * 1024)).toFixed(1) + \" GB\";\n    }\n\n    function updateMemoryUsageHistory() {\n        memoryUsageHistory = [...memoryUsageHistory, memoryUsedPercentage]\n        if (memoryUsageHistory.length > historyLength) {\n            memoryUsageHistory.shift()\n        }\n    }\n    function updateSwapUsageHistory() {\n        swapUsageHistory = [...swapUsageHistory, swapUsedPercentage]\n        if (swapUsageHistory.length > historyLength) {\n            swapUsageHistory.shift()\n        }\n    }\n    function updateCpuUsageHistory() {\n        cpuUsageHistory = [...cpuUsageHistory, cpuUsage]\n        if (cpuUsageHistory.length > historyLength) {\n            cpuUsageHistory.shift()\n        }\n    }\n    function updateHistories() {\n        updateMemoryUsageHistory()\n        updateSwapUsageHistory()\n        updateCpuUsageHistory()\n    }\n\n\tTimer {\n\t\tinterval: 1\n        running: true \n        repeat: true\n\t\tonTriggered: {\n            // Reload files\n            fileMeminfo.reload()\n            fileStat.reload()\n\n            // Parse memory and swap usage\n            const textMeminfo = fileMeminfo.text()\n            memoryTotal = Number(textMeminfo.match(/MemTotal: *(\\d+)/)?.[1] ?? 1)\n            memoryFree = Number(textMeminfo.match(/MemAvailable: *(\\d+)/)?.[1] ?? 0)\n            swapTotal = Number(textMeminfo.match(/SwapTotal: *(\\d+)/)?.[1] ?? 1)\n            swapFree = Number(textMeminfo.match(/SwapFree: *(\\d+)/)?.[1] ?? 0)\n\n            // Parse CPU usage\n            const textStat = fileStat.text()\n            const cpuLine = textStat.match(/^cpu\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)/)\n            if (cpuLine) {\n                const stats = cpuLine.slice(1).map(Number)\n                const total = stats.reduce((a, b) => a + b, 0)\n                const idle = stats[3]\n\n                if (previousCpuStats) {\n                    const totalDiff = total - previousCpuStats.total\n                    const idleDiff = idle - previousCpuStats.idle\n                    cpuUsage = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0\n                }\n\n                previousCpuStats = { total, idle }\n            }\n\n            root.updateHistories()\n            interval = Config.options?.resources?.updateInterval ?? 3000\n        }\n\t}\n\n\tFileView { id: fileMeminfo; path: \"/proc/meminfo\" }\n    FileView { id: fileStat; path: \"/proc/stat\" }\n\n    Process {\n        id: findCpuMaxFreqProc\n        environment: ({\n            LANG: \"C\",\n            LC_ALL: \"C\"\n        })\n        command: [\"bash\", \"-c\", \"lscpu | grep 'CPU max MHz' | awk '{print $4}'\"]\n        running: true\n        stdout: StdioCollector {\n            id: outputCollector\n            onStreamFinished: {\n                root.maxAvailableCpuString = (parseFloat(outputCollector.text) / 1000).toFixed(0) + \" GHz\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/SessionWarnings.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nSingleton {\n    id: root\n\n    property bool packageManagerRunning: false\n    property bool downloadRunning: false\n\n    function refresh() {\n        packageManagerRunning = false;\n        downloadRunning = false;\n        detectPackageManagerProc.running = false;\n        detectPackageManagerProc.running = true;\n        detectDownloadProc.running = false;\n        detectDownloadProc.running = true;\n    }\n\n    Process {\n        id: detectPackageManagerProc\n        command: [\"bash\", \"-c\", \"pidof yay paru dnf zypper apt apx xbps snap apk yum epsi pikman || ls /var/lib/pacman/db.lck\"]\n        onExited: (exitCode, exitStatus) => {\n            root.packageManagerRunning = (exitCode === 0);\n        }\n    }\n\n    Process {\n        id: detectDownloadProc\n        command: [\"bash\", \"-c\", \"pidof curl wget aria2c yt-dlp || ls ~/Downloads | grep -E '\\.crdownload$|\\.part$'\"]\n        onExited: (exitCode, exitStatus) => {\n            root.downloadRunning = (exitCode === 0);\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/SongRec.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\nSingleton {\n    id: root\n\n    enum MonitorSource { Monitor, Input }\n\n    property var monitorSource: SongRec.MonitorSource.Monitor\n    property int timeoutInterval: Config.options.musicRecognition.interval\n    property int timeoutDuration: Config.options.musicRecognition.timeout\n    readonly property bool running: recognizeMusicProc.running\n\n    function toggleRunning(running) {\n        if (recognizeMusicProc.running && !running === true) root.manuallyStopped = true;\n        if (running != undefined) {\n            recognizeMusicProc.running = running\n        } else {\n            recognizeMusicProc.running = !root.running\n        }\n        musicReconizedProc.running = false\n    }\n\n    function toggleMonitorSource(source) {\n        if (source !== undefined) {\n            root.monitorSource = source\n            return\n        }\n        root.monitorSource = (root.monitorSource === SongRec.MonitorSource.Monitor) ? SongRec.MonitorSource.Input : SongRec.MonitorSource.Monitor\n    }\n    function monitorSourceToString(source) {\n        if (source === SongRec.MonitorSource.Monitor) {\n            return \"monitor\"\n        } else {\n            return \"input\"\n        }\n    }\n    readonly property string monitorSourceString: monitorSourceToString(monitorSource)\n    property var recognizedTrack: ({ title:\"\", subtitle:\"\", url:\"\"})\n    property bool manuallyStopped: false\n\n    function handleRecognition(jsonText) {\n        try {\n            var obj = JSON.parse(jsonText)\n            root.recognizedTrack = {\n                title: obj.track.title,\n                subtitle: obj.track.subtitle,\n                url: obj.track.url\n            }\n            musicReconizedProc.running = true\n        } catch(e) {\n            Quickshell.execDetached([\"notify-send\", Translation.tr(\"Couldn't recognize music\"), Translation.tr(\"Perhaps what you're listening to is too niche\"), \"-a\", \"Shell\"])\n        }\n    }\n\n    Process {\n        id: recognizeMusicProc\n        running: false\n        command: [`${Directories.scriptPath}/musicRecognition/recognize-music.sh`, \"-i\", root.timeoutInterval, \"-t\", root.timeoutDuration, \"-s\", root.monitorSourceString]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (root.manuallyStopped) {\n                    root.manuallyStopped = false\n                    return\n                }\n                handleRecognition(this.text)\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode === 1) {\n                Quickshell.execDetached([\"notify-send\", Translation.tr(\"Couldn't recognize music\"), Translation.tr(\"Make sure you have songrec installed\"), \"-a\", \"Shell\"])\n            }\n        }\n    }\n\n    Process {\n        id: musicReconizedProc\n        running: false\n        command: [\n            \"notify-send\",\n            Translation.tr(\"Music Recognized\"), \n            root.recognizedTrack.title + \" - \" + root.recognizedTrack.subtitle, \n            \"-A\", \"Shazam\",\n            \"-A\", \"YouTube\",\n            \"-a\", \"Shell\"\n        ]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (this.text === \"\") return\n                if (this.text == 0) {\n                    Qt.openUrlExternally(root.recognizedTrack.url);\n                } else {\n                    Qt.openUrlExternally(\"https://www.youtube.com/results?search_query=\" + root.recognizedTrack.title + \" - \" + root.recognizedTrack.subtitle);\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/services/SystemInfo.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\n/**\n * Provides some system info: distro, username.\n */\nSingleton {\n    id: root\n    property string distroName: \"Unknown\"\n    property string distroId: \"unknown\"\n    property string distroIcon: \"linux-symbolic\"\n    property string username: \"user\"\n    property string homeUrl: \"\"\n    property string documentationUrl: \"\"\n    property string supportUrl: \"\"\n    property string bugReportUrl: \"\"\n    property string privacyPolicyUrl: \"\"\n    property string logo: \"\"\n    property string desktopEnvironment: \"\"\n    property string windowingSystem: \"\"\n\n    Timer {\n        triggeredOnStart: true\n        interval: 1\n        running: true\n        repeat: false\n        onTriggered: {\n            getUsername.running = true\n            fileOsRelease.reload()\n            const textOsRelease = fileOsRelease.text()\n\n            // Extract the friendly name (PRETTY_NAME field, fallback to NAME)\n            const prettyNameMatch = textOsRelease.match(/^PRETTY_NAME=\"(.+?)\"/m)\n            const nameMatch = textOsRelease.match(/^NAME=\"(.+?)\"/m)\n            distroName = prettyNameMatch ? prettyNameMatch[1] : (nameMatch ? nameMatch[1].replace(/Linux/i, \"\").trim() : \"Unknown\")\n\n            // Extract the ID\n            const idMatch = textOsRelease.match(/^ID=\"?(.+?)\"?$/m)\n            distroId = idMatch ? idMatch[1] : \"unknown\"\n\n            // Extract additional URLs and logo\n            const homeUrlMatch = textOsRelease.match(/^HOME_URL=\"(.+?)\"/m)\n            homeUrl = homeUrlMatch ? homeUrlMatch[1] : \"\"\n            const documentationUrlMatch = textOsRelease.match(/^DOCUMENTATION_URL=\"(.+?)\"/m)\n            documentationUrl = documentationUrlMatch ? documentationUrlMatch[1] : \"\"\n            const supportUrlMatch = textOsRelease.match(/^SUPPORT_URL=\"(.+?)\"/m)\n            supportUrl = supportUrlMatch ? supportUrlMatch[1] : \"\"\n            const bugReportUrlMatch = textOsRelease.match(/^BUG_REPORT_URL=\"(.+?)\"/m)\n            bugReportUrl = bugReportUrlMatch ? bugReportUrlMatch[1] : \"\"\n            const privacyPolicyUrlMatch = textOsRelease.match(/^PRIVACY_POLICY_URL=\"(.+?)\"/m)\n            privacyPolicyUrl = privacyPolicyUrlMatch ? privacyPolicyUrlMatch[1] : \"\"\n            const logoFieldMatch = textOsRelease.match(/^LOGO=\"?(.+?)\"?$/m)\n            logo = logoFieldMatch ? logoFieldMatch[1] : \"\"\n\n            // Update the distroIcon property based on distroId\n            switch (distroId) {\n                case \"artix\":\n                case \"arch\": distroIcon = \"arch-symbolic\"; break;\n                case \"endeavouros\": distroIcon = \"endeavouros-symbolic\"; break;\n                case \"cachyos\": distroIcon = \"cachyos-symbolic\"; break;\n                case \"nixos\": distroIcon = \"nixos-symbolic\"; break;\n                case \"fedora\": distroIcon = \"fedora-symbolic\"; break;\n                case \"linuxmint\":\n                case \"ubuntu\":\n                case \"zorin\":\n                case \"popos\": distroIcon = \"ubuntu-symbolic\"; break;\n                case \"debian\":\n                case \"raspbian\":\n                case \"kali\": distroIcon = \"debian-symbolic\"; break;\n                case \"funtoo\":\n                case \"gentoo\": distroIcon = \"gentoo-symbolic\"; break;\n                default: distroIcon = \"linux-symbolic\"; break;\n            }\n            if (textOsRelease.toLowerCase().includes(\"nyarch\")) {\n                distroIcon = \"nyarch-symbolic\"\n            }\n\n            if (logo.trim().length === 0) {\n                logo = distroIcon\n            }\n\n        }\n    }\n\n    Process {\n        id: getUsername\n        command: [\"whoami\"]\n        stdout: SplitParser {\n            onRead: data => {\n                root.username = data.trim()\n            }\n        }\n    }\n\n    Process {\n        id: getDesktopEnvironment\n        running: true\n        command: [\"bash\", \"-c\", \"echo $XDG_CURRENT_DESKTOP,$WAYLAND_DISPLAY\"]\n        stdout: StdioCollector {\n            id: deCollector\n            onStreamFinished: {\n                const [desktop, wayland] = deCollector.text.split(\",\")\n                root.desktopEnvironment = desktop.trim()\n                root.windowingSystem = wayland.trim().length > 0 ? \"Wayland\" : \"X11\" // Are there others? 🤔\n            }\n        }\n    }\n\n    FileView {\n        id: fileOsRelease\n        path: \"/etc/os-release\"\n    }\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/services/TaskbarApps.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Wayland\n\nSingleton {\n    id: root\n\n    function isPinned(appId) {\n        return Config.options.dock.pinnedApps.indexOf(appId) !== -1;\n    }\n\n    function togglePin(appId) {\n        if (root.isPinned(appId)) {\n            Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.filter(id => id !== appId)\n        } else {\n            Config.options.dock.pinnedApps = Config.options.dock.pinnedApps.concat([appId])\n        }\n    }\n\n    property list<var> apps: {\n        var map = new Map();\n\n        // Pinned apps\n        const pinnedApps = Config.options?.dock.pinnedApps ?? [];\n        for (const appId of pinnedApps) {\n            if (!map.has(appId.toLowerCase())) map.set(appId.toLowerCase(), ({\n                pinned: true,\n                toplevels: []\n            }));\n        }\n\n        // Separator\n        if (pinnedApps.length > 0) {\n            map.set(\"SEPARATOR\", { pinned: false, toplevels: [] });\n        }\n\n        // Ignored apps\n        const ignoredRegexStrings = Config.options?.dock.ignoredAppRegexes ?? [];\n        const ignoredRegexes = ignoredRegexStrings.map(pattern => new RegExp(pattern, \"i\"));\n        // Open windows\n        for (const toplevel of ToplevelManager.toplevels.values) {\n            if (ignoredRegexes.some(re => re.test(toplevel.appId))) continue;\n            if (!map.has(toplevel.appId.toLowerCase())) map.set(toplevel.appId.toLowerCase(), ({\n                pinned: false,\n                toplevels: []\n            }));\n            map.get(toplevel.appId.toLowerCase()).toplevels.push(toplevel);\n        }\n\n        var values = [];\n\n        for (const [key, value] of map) {\n            values.push(appEntryComp.createObject(null, { appId: key, toplevels: value.toplevels, pinned: value.pinned }));\n        }\n\n        return values;\n    }\n\n    component TaskbarAppEntry: QtObject {\n        id: wrapper\n        required property string appId\n        required property list<var> toplevels\n        required property bool pinned\n    }\n    Component {\n        id: appEntryComp\n        TaskbarAppEntry {}\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/TimerService.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.services\nimport qs.modules.common\n\nimport Quickshell\nimport Quickshell.Io\nimport QtQuick\n\n/**\n * Simple Pomodoro time manager.\n */\nSingleton {\n    id: root\n\n    property int focusTime: Config.options.time.pomodoro.focus\n    property int breakTime: Config.options.time.pomodoro.breakTime\n    property int longBreakTime: Config.options.time.pomodoro.longBreak\n    property int cyclesBeforeLongBreak: Config.options.time.pomodoro.cyclesBeforeLongBreak\n\n    property bool pomodoroRunning: Persistent.states.timer.pomodoro.running\n    property bool pomodoroBreak: Persistent.states.timer.pomodoro.isBreak\n    property bool pomodoroLongBreak: Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak);\n    property int pomodoroLapDuration: pomodoroLongBreak ? longBreakTime : pomodoroBreak ? breakTime : focusTime // This is a binding that's to be kept\n    property int pomodoroSecondsLeft: pomodoroLapDuration // Reasonable init value, to be changed\n    property int pomodoroCycle: Persistent.states.timer.pomodoro.cycle\n\n    property bool stopwatchRunning: Persistent.states.timer.stopwatch.running\n    property int stopwatchTime: 0\n    property int stopwatchStart: Persistent.states.timer.stopwatch.start\n    property var stopwatchLaps: Persistent.states.timer.stopwatch.laps\n\n    // General\n    Component.onCompleted: {\n        if (!stopwatchRunning)\n            stopwatchReset();\n    }\n\n    function getCurrentTimeInSeconds() {  // Pomodoro uses Seconds\n        return Math.floor(Date.now() / 1000);\n    }\n\n    function getCurrentTimeIn10ms() {  // Stopwatch uses 10ms\n        return Math.floor(Date.now() / 10);\n    }\n\n    // Pomodoro\n    function refreshPomodoro() {\n        // Work <-> break ?\n        if (getCurrentTimeInSeconds() >= Persistent.states.timer.pomodoro.start + pomodoroLapDuration) {\n            // Reset counts\n            Persistent.states.timer.pomodoro.isBreak = !Persistent.states.timer.pomodoro.isBreak;\n            Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds();\n\n            // Send notification\n            let notificationMessage;\n            if (Persistent.states.timer.pomodoro.isBreak && (pomodoroCycle + 1 == cyclesBeforeLongBreak)) {\n                notificationMessage = Translation.tr(`🌿 Long break: %1 minutes`).arg(Math.floor(longBreakTime / 60));\n            } else if (Persistent.states.timer.pomodoro.isBreak) {\n                notificationMessage = Translation.tr(`☕ Break: %1 minutes`).arg(Math.floor(breakTime / 60));\n            } else {\n                notificationMessage = Translation.tr(`🔴 Focus: %1 minutes`).arg(Math.floor(focusTime / 60));\n            }\n\n            Quickshell.execDetached([\"notify-send\", \"Pomodoro\", notificationMessage, \"-a\", \"Shell\"]);\n            if (Config.options.sounds.pomodoro) {\n                Audio.playSystemSound(\"alarm-clock-elapsed\")\n            }\n\n            if (!pomodoroBreak) {\n                Persistent.states.timer.pomodoro.cycle = (Persistent.states.timer.pomodoro.cycle + 1) % root.cyclesBeforeLongBreak;\n            }\n        }\n\n        pomodoroSecondsLeft = pomodoroLapDuration - (getCurrentTimeInSeconds() - Persistent.states.timer.pomodoro.start);\n    }\n\n    Timer {\n        id: pomodoroTimer\n        interval: 200\n        running: root.pomodoroRunning\n        repeat: true\n        onTriggered: refreshPomodoro()\n    }\n\n    function togglePomodoro() {\n        Persistent.states.timer.pomodoro.running = !pomodoroRunning;\n        if (Persistent.states.timer.pomodoro.running) {\n            // Start/Resume\n            Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds() + pomodoroSecondsLeft - pomodoroLapDuration;\n        }\n    }\n\n    function resetPomodoro() {\n        Persistent.states.timer.pomodoro.running = false;\n        Persistent.states.timer.pomodoro.isBreak = false;\n        Persistent.states.timer.pomodoro.start = getCurrentTimeInSeconds();\n        Persistent.states.timer.pomodoro.cycle = 0;\n        refreshPomodoro();\n    }\n\n    // Stopwatch\n    function refreshStopwatch() {  // Stopwatch stores time in 10ms\n        stopwatchTime = getCurrentTimeIn10ms() - stopwatchStart;\n    }\n\n    Timer {\n        id: stopwatchTimer\n        interval: 10\n        running: root.stopwatchRunning\n        repeat: true\n        onTriggered: refreshStopwatch()\n    }\n\n    function toggleStopwatch() {\n        if (root.stopwatchRunning)\n            stopwatchPause();\n        else\n            stopwatchResume();\n    }\n\n    function stopwatchPause() {\n        Persistent.states.timer.stopwatch.running = false;\n    }\n\n    function stopwatchResume() {\n        if (stopwatchTime === 0) Persistent.states.timer.stopwatch.laps = [];\n        Persistent.states.timer.stopwatch.running = true;\n        Persistent.states.timer.stopwatch.start = getCurrentTimeIn10ms() - stopwatchTime;\n    }\n\n    function stopwatchReset() {\n        stopwatchTime = 0;\n        Persistent.states.timer.stopwatch.laps = [];\n        Persistent.states.timer.stopwatch.running = false;\n    }\n\n    function stopwatchRecordLap() {\n        Persistent.states.timer.stopwatch.laps.push(stopwatchTime);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Todo.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport qs.modules.common\nimport Quickshell;\nimport Quickshell.Io;\nimport QtQuick;\n\n/**\n * Simple to-do list manager.\n * Each item is an object with \"content\" and \"done\" properties.\n */\nSingleton {\n    id: root\n    property var filePath: Directories.todoPath\n    property var list: []\n    \n    function addItem(item) {\n        list.push(item)\n        // Reassign to trigger onListChanged\n        root.list = list.slice(0)\n        todoFileView.setText(JSON.stringify(root.list))\n    }\n\n    function addTask(desc) {\n        const item = {\n            \"content\": desc,\n            \"done\": false,\n        }\n        addItem(item)\n    }\n\n    function markDone(index) {\n        if (index >= 0 && index < list.length) {\n            list[index].done = true\n            // Reassign to trigger onListChanged\n            root.list = list.slice(0)\n            todoFileView.setText(JSON.stringify(root.list))\n        }\n    }\n\n    function markUnfinished(index) {\n        if (index >= 0 && index < list.length) {\n            list[index].done = false\n            // Reassign to trigger onListChanged\n            root.list = list.slice(0)\n            todoFileView.setText(JSON.stringify(root.list))\n        }\n    }\n\n    function deleteItem(index) {\n        if (index >= 0 && index < list.length) {\n            list.splice(index, 1)\n            // Reassign to trigger onListChanged\n            root.list = list.slice(0)\n            todoFileView.setText(JSON.stringify(root.list))\n        }\n    }\n\n    function refresh() {\n        todoFileView.reload()\n    }\n\n    Component.onCompleted: {\n        refresh()\n    }\n\n    FileView {\n        id: todoFileView\n        path: Qt.resolvedUrl(root.filePath)\n        onLoaded: {\n            const fileContents = todoFileView.text()\n            root.list = JSON.parse(fileContents)\n            console.log(\"[To Do] File loaded\")\n        }\n        onLoadFailed: (error) => {\n            if(error == FileViewError.FileNotFound) {\n                console.log(\"[To Do] File not found, creating new file.\")\n                root.list = []\n                todoFileView.setText(JSON.stringify(root.list))\n            } else {\n                console.log(\"[To Do] Error loading file: \" + error)\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Translation.qml",
    "content": "pragma Singleton\n\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\nimport qs.modules.common\n\nSingleton {\n    id: root\n\n    property var translations: ({})\n    property var generatedTranslations: ({})\n    property var availableLanguages: [\"en_US\"]\n    property var availableGeneratedLanguages: []\n    property var allAvailableLanguages: {\n        const combined = new Set([...root.availableLanguages, ...root.availableGeneratedLanguages]);\n        return Array.from(combined).sort();\n    }\n    property bool isScanning: scanLanguagesProcess.running\n    property bool isLoading: false\n    property string translationKeepSuffix: \"/*keep*/\"\n    property string translationsDir: Quickshell.shellPath(\"translations\")\n    property string generatedTranslationsDir: Directories.shellConfig + \"/translations\"\n\n    property string languageCode: {\n        var configLang = Config?.options.language.ui ?? \"auto\";\n\n        if (configLang !== \"auto\")\n            return configLang;\n\n        return Qt.locale().name;\n    }\n\n    TranslationScanner {\n        id: scanLanguagesProcess\n        translationsDir: root.translationsDir\n        onLanguagesScanned: (languages) => {\n            root.availableLanguages = [...languages];\n        }\n    }\n\n    TranslationScanner {\n        id: scanGeneratedLanguagesProcess\n        translationsDir: root.generatedTranslationsDir\n        onLanguagesScanned: (languages) => {\n            root.availableGeneratedLanguages = [...languages];\n        }\n    }\n\n    onLanguageCodeChanged: {\n        print(\"[Translation] Language changed to\", root.languageCode);\n        translationFileView.languageCode = root.languageCode;\n        generatedTranslationFileView.languageCode = root.languageCode;\n        translationFileView.reread();\n        generatedTranslationFileView.reread();\n    }\n\n    TranslationReader {\n        id: translationFileView\n        translationsDir: root.translationsDir\n        languageCode: root.languageCode\n        onContentLoaded: (data) => {\n            root.translations = data;\n            root.isLoading = false;\n        }\n    }\n\n    TranslationReader {\n        id: generatedTranslationFileView\n        translationsDir: root.generatedTranslationsDir\n        languageCode: root.languageCode\n        onContentLoaded: (data) => {\n            root.generatedTranslations = data;\n            root.isLoading = false;\n        }\n    }\n\n    function tr(text) {\n        // Special cases\n        if (!text) return \"\";\n        var key = text.toString();\n        if (root.isLoading || (!root?.translations?.hasOwnProperty(key) && !root?.generatedTranslations?.hasOwnProperty(key)))\n            return key;\n        \n        // Normal cases\n        var translation = root.translations[key] || root.generatedTranslations[key] || key;\n        // print(key, \"-> [\", root.translations[key], root.generatedTranslations[key], key, \"] ->\", translation);\n        if (translation.endsWith(root.translationKeepSuffix)) {\n            translation = translation.substring(0, translation.length - root.translationKeepSuffix.length).trim();\n        }\n        return translation;\n    }\n\n    component TranslationScanner: Process {\n        id: translationScanner\n        required property string translationsDir\n        signal languagesScanned(var languages)\n\n        command: [\"find\", translationScanner.translationsDir, \"-name\", \"*.json\", \"-exec\", \"basename\", \"{}\", \".json\", \";\"]\n        running: true\n\n        stdout: StdioCollector {\n            id: languagesCollector\n            onStreamFinished: {\n                const output = languagesCollector.text;\n                const files = output.trim().split('\\n').map(f => f.trim());\n                translationScanner.languagesScanned(files);\n            }\n        }\n\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode !== 0) {\n                translationScanner.languagesScanned([\"en_US\"]);\n            }\n        }\n    }\n\n    component TranslationReader: FileView {\n        id: translationReader\n        required property string translationsDir\n        property string languageCode: root.languageCode\n        signal contentLoaded(var data)\n\n        function reread() { // Proper reload in case the file was incorrect before\n            translationReader.path = \"\";\n            translationReader.path = `${translationReader.translationsDir}/${translationReader.languageCode}.json`;\n            translationReader.reload();\n        }\n        path: \"\"\n\n        onLoaded: {\n            var textContent = \"\";\n            try {\n                textContent = text();\n                var jsonData = JSON.parse(textContent);\n                translationReader.contentLoaded(jsonData);\n            } catch (e) {\n                console.log(\"[Translation] Failed to load translations:\", e);\n                translationReader.contentLoaded({});\n            }\n        }\n        onLoadFailed: error => {\n            translationReader.contentLoaded({});\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/TrayService.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport QtQuick\nimport Quickshell\nimport Quickshell.Services.SystemTray\n\nSingleton {\n    id: root\n\n    property bool smartTray: Config.options.tray.filterPassive\n    property list<var> itemsInUserList: SystemTray.items.values.filter(i => (Config.options.tray.pinnedItems.includes(i.id) && (!smartTray || i.status !== Status.Passive)))\n    property list<var> itemsNotInUserList: SystemTray.items.values.filter(i => (!Config.options.tray.pinnedItems.includes(i.id) && (!smartTray || i.status !== Status.Passive)))\n\n    property bool invertPins: Config.options.tray.invertPinnedItems\n    property list<var> pinnedItems: invertPins ? itemsNotInUserList : itemsInUserList\n    property list<var> unpinnedItems: invertPins ? itemsInUserList : itemsNotInUserList\n\n    function getTooltipForItem(item) {\n        var result = item.tooltipTitle.length > 0 ? item.tooltipTitle\n                : (item.title.length > 0 ? item.title : item.id);\n        if (item.tooltipDescription.length > 0) result += \" • \" + item.tooltipDescription;\n        if (Config.options.tray.showItemId) result += \"\\n[\" + item.id + \"]\";\n        return result;\n    }\n\n    // Pinning\n    function pin(itemId) {\n        var pins = Config.options.tray.pinnedItems;\n        if (pins.includes(itemId)) return;\n        Config.options.tray.pinnedItems.push(itemId);\n    }\n    function unpin(itemId) {\n        Config.options.tray.pinnedItems = Config.options.tray.pinnedItems.filter(id => id !== itemId);\n    }\n    function isPinned(itemId) {\n        for (var i = 0; i < root.pinnedItems.length; i++) {\n            if (root.pinnedItems[i].id === itemId)\n                return true;\n        }\n        return false;\n    }\n\n    function togglePin(itemId) {\n        var pins = Config.options.tray.pinnedItems;\n        if (pins.includes(itemId)) {\n            unpin(itemId)\n        } else {\n            pin(itemId)\n        }\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Updates.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport qs.modules.common.functions\nimport QtQuick\nimport Quickshell\nimport Quickshell.Io\n\n/*\n * System updates service. Currently only supports Arch.\n */\nSingleton {\n    id: root\n\n    property bool available: false\n    property alias checking: checkUpdatesProc.running\n    property int count: 0\n    \n    readonly property bool updateAdvised: available && count > Config.options.updates.adviseUpdateThreshold\n    readonly property bool updateStronglyAdvised: available && count > Config.options.updates.stronglyAdviseUpdateThreshold\n\n    function load() {}\n    function refresh() {\n        if (!available) return;\n        print(\"[Updates] Checking for system updates\")\n        checkUpdatesProc.running = true;\n    }\n\n    Timer {\n        interval: Config.options.updates.checkInterval * 60 * 1000\n        repeat: true\n        running: Config.ready && Config.options.updates.enableCheck\n        onTriggered: {\n            print(\"[Updates] Periodic update check due\")\n            root.refresh();\n        }\n    }\n\n    Process {\n        id: checkAvailabilityProc\n        running: Config.ready && Config.options.updates.enableCheck\n        command: [\"which\", \"checkupdates\"]\n        onExited: (exitCode, exitStatus) => {\n            root.available = (exitCode === 0);\n            root.refresh();\n        }\n    }\n\n    Process {\n        id: checkUpdatesProc\n        command: [\"bash\", \"-c\", \"checkupdates | wc -l\"]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                root.count = parseInt(text.trim());\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Wallpapers.qml",
    "content": "import qs.modules.common\nimport qs.modules.common.models\nimport qs.modules.common.functions\nimport QtQuick\nimport Qt.labs.folderlistmodel\nimport Quickshell\nimport Quickshell.Io\npragma Singleton\npragma ComponentBehavior: Bound\n\n/**\n * Provides a list of wallpapers and an \"apply\" action that calls the existing\n * switchwall.sh script. Pretty much a limited file browsing service.\n */\nSingleton {\n    id: root\n\n    property string thumbgenScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/thumbgen-venv.sh`\n    property string generateThumbnailsMagickScriptPath: `${FileUtils.trimFileProtocol(Directories.scriptPath)}/thumbnails/generate-thumbnails-magick.sh`\n    property alias directory: folderModel.folder\n    readonly property string effectiveDirectory: FileUtils.trimFileProtocol(folderModel.folder.toString())\n    property url defaultFolder: Qt.resolvedUrl(`${Directories.pictures}/Wallpapers`)\n    property alias folderModel: folderModel // Expose for direct binding when needed\n    property string searchQuery: \"\"\n    readonly property list<string> extensions: [ // TODO: add videos\n        \"jpg\", \"jpeg\", \"png\", \"webp\", \"avif\", \"bmp\", \"svg\"\n    ]\n    property list<string> wallpapers: [] // List of absolute file paths (without file://)\n    readonly property bool thumbnailGenerationRunning: thumbgenProc.running\n    property real thumbnailGenerationProgress: 0\n\n    signal changed()\n    signal thumbnailGenerated(directory: string)\n    signal thumbnailGeneratedFile(filePath: string)\n\n    function load () {} // For forcing initialization\n    \n    function openFallbackPicker(darkMode = Appearance.m3colors.darkmode) {\n        Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", darkMode ? \"dark\" : \"light\"]);\n    }\n\n    function apply(path, darkMode = Appearance.m3colors.darkmode) {\n        if (!path || path.length === 0) return;\n        Quickshell.execDetached([Directories.wallpaperSwitchScriptPath, \"--mode\", darkMode ? \"dark\" : \"light\", \"--image\", path]);\n        root.changed()\n    }\n\n    Process {\n        id: selectProc\n        property string filePath: \"\"\n        property bool darkMode: Appearance.m3colors.darkmode\n        function select(filePath, darkMode = Appearance.m3colors.darkmode) {\n            selectProc.filePath = filePath\n            selectProc.darkMode = darkMode\n            selectProc.exec([\"test\", \"-d\", FileUtils.trimFileProtocol(filePath)])\n        }\n        onExited: (exitCode, exitStatus) => {\n            if (exitCode === 0) {\n                setDirectory(selectProc.filePath);\n                return;\n            }\n            root.apply(selectProc.filePath, selectProc.darkMode);\n        }\n    }\n\n    function select(filePath, darkMode = Appearance.m3colors.darkmode) {\n        selectProc.select(filePath, darkMode);\n    }\n\n    function randomFromCurrentFolder(darkMode = Appearance.m3colors.darkmode) {\n        if (folderModel.count === 0) return;\n        const randomIndex = Math.floor(Math.random() * folderModel.count);\n        const filePath = folderModel.get(randomIndex, \"filePath\");\n        print(\"Randomly selected wallpaper:\", filePath);\n        root.select(filePath, darkMode);\n    }\n\n    Process {\n        id: validateDirProc\n        property string nicePath: \"\"\n        function setDirectoryIfValid(path) {\n            validateDirProc.nicePath = FileUtils.trimFileProtocol(path).replace(/\\/+$/, \"\")\n            if (/^\\/*$/.test(validateDirProc.nicePath)) validateDirProc.nicePath = \"/\";\n            validateDirProc.exec([\n                \"bash\", \"-c\",\n                `if [ -d \"${validateDirProc.nicePath}\" ]; then echo dir; elif [ -f \"${validateDirProc.nicePath}\" ]; then echo file; else echo invalid; fi`\n            ])\n        }\n        stdout: StdioCollector {\n            onStreamFinished: {\n                    root.directory = Qt.resolvedUrl(validateDirProc.nicePath)\n                const result = text.trim()\n                if (result === \"dir\") {\n                } else if (result === \"file\") {\n                    root.directory = Qt.resolvedUrl(FileUtils.parentDirectory(validateDirProc.nicePath))\n                } else {\n                    // Ignore\n                }\n            }\n        }\n    }\n    function setDirectory(path) {\n        validateDirProc.setDirectoryIfValid(path)\n    }\n    function navigateUp() {\n        folderModel.navigateUp()\n    }\n    function navigateBack() {\n        folderModel.navigateBack()\n    }\n    function navigateForward() {\n        folderModel.navigateForward()\n    }\n\n    // Folder model\n    FolderListModelWithHistory {\n        id: folderModel\n        folder: Qt.resolvedUrl(root.defaultFolder)\n        caseSensitive: false\n        nameFilters: root.extensions.map(ext => `*${searchQuery.split(\" \").filter(s => s.length > 0).map(s => `*${s}*`)}*.${ext}`)\n        showDirs: true\n        showDotAndDotDot: false\n        showOnlyReadable: true\n        sortField: FolderListModel.Time\n        sortReversed: false\n        onCountChanged: {\n            root.wallpapers = []\n            for (let i = 0; i < folderModel.count; i++) {\n                const path = folderModel.get(i, \"filePath\") || FileUtils.trimFileProtocol(folderModel.get(i, \"fileURL\"))\n                if (path && path.length) root.wallpapers.push(path)\n            }\n        }\n    }\n\n    // Thumbnail generation\n    function generateThumbnail(size: string) {\n        if (![\"normal\", \"large\", \"x-large\", \"xx-large\"].includes(size)) throw new Error(\"Invalid thumbnail size\");\n        thumbgenProc.directory = root.directory\n        thumbgenProc.running = false\n        thumbgenProc.command = [\n            \"bash\", \"-c\",\n            `${thumbgenScriptPath} --size ${size} --machine_progress -d ${FileUtils.trimFileProtocol(root.directory)} || ${generateThumbnailsMagickScriptPath} --size ${size} -d ${FileUtils.trimFileProtocol(root.directory)}`,\n        ]\n        // console.log(\"[Wallpapers] Updating thumbnails with command \", thumbgenProc.command.join(\" \"))\n        root.thumbnailGenerationProgress = 0\n        thumbgenProc.running = true\n    }\n    Process {\n        id: thumbgenProc\n        property string directory\n        stdout: SplitParser {\n            onRead: data => {\n                // print(\"thumb gen proc:\", data)\n                let match = data.match(/PROGRESS (\\d+)\\/(\\d+)/)\n                if (match) {\n                    const completed = parseInt(match[1])\n                    const total = parseInt(match[2])\n                    root.thumbnailGenerationProgress = completed / total\n                }\n                match = data.match(/FILE (.+)/)\n                if (match) {\n                    const filePath = match[1]\n                    root.thumbnailGeneratedFile(filePath)\n                }\n            }\n        }\n        onExited: (exitCode, exitStatus) => {\n            // print(\"[Wallpapers] Thumbnail generation completed with exit code\", exitCode)\n            root.thumbnailGenerated(thumbgenProc.directory)\n        }\n    }\n\n    IpcHandler {\n        target: \"wallpapers\"\n\n        function apply(path: string): void {\n            root.apply(path);\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Weather.qml",
    "content": "pragma Singleton\npragma ComponentBehavior: Bound\n\nimport Quickshell\nimport Quickshell.Io\nimport QtQuick\nimport QtPositioning\n\nimport qs.modules.common\n\nSingleton {\n    id: root\n    // 10 minute\n    readonly property int fetchInterval: Config.options.bar.weather.fetchInterval * 60 * 1000\n    readonly property string city: Config.options.bar.weather.city\n    readonly property bool useUSCS: Config.options.bar.weather.useUSCS\n    property bool gpsActive: Config.options.bar.weather.enableGPS\n\n    onUseUSCSChanged: {\n        root.getData();\n    }\n    onCityChanged: {\n        root.getData();\n    }\n\n    property var location: ({\n        valid: false,\n        lat: 0,\n        lon: 0\n    })\n\n    property var data: ({\n        uv: 0,\n        humidity: 0,\n        sunrise: 0,\n        sunset: 0,\n        windDir: 0,\n        wCode: 0,\n        city: 0,\n        wind: 0,\n        precip: 0,\n        visib: 0,\n        press: 0,\n        temp: 0,\n        tempFeelsLike: 0,\n        lastRefresh: 0,\n    })\n\n    function refineData(data) {\n        let temp = {};\n        temp.uv = data?.current?.uvIndex || 0;\n        temp.humidity = (data?.current?.humidity || 0) + \"%\";\n        temp.sunrise = data?.astronomy?.sunrise || \"0.0\";\n        temp.sunset = data?.astronomy?.sunset || \"0.0\";\n        temp.windDir = data?.current?.winddir16Point || \"N\";\n        temp.wCode = data?.current?.weatherCode || \"113\";\n        temp.city = data?.location?.areaName[0]?.value || \"City\";\n        temp.temp = \"\";\n        temp.tempFeelsLike = \"\";\n        if (root.useUSCS) {\n            temp.wind = (data?.current?.windspeedMiles || 0) + \" mph\";\n            temp.precip = (data?.current?.precipInches || 0) + \" in\";\n            temp.visib = (data?.current?.visibilityMiles || 0) + \" m\";\n            temp.press = (data?.current?.pressureInches || 0) + \" psi\";\n            temp.temp += (data?.current?.temp_F || 0);\n            temp.tempFeelsLike += (data?.current?.FeelsLikeF || 0);\n            temp.temp += \"°F\";\n            temp.tempFeelsLike += \"°F\";\n        } else {\n            temp.wind = (data?.current?.windspeedKmph || 0) + \" km/h\";\n            temp.precip = (data?.current?.precipMM || 0) + \" mm\";\n            temp.visib = (data?.current?.visibility || 0) + \" km\";\n            temp.press = (data?.current?.pressure || 0) + \" hPa\";\n            temp.temp += (data?.current?.temp_C || 0);\n            temp.tempFeelsLike += (data?.current?.FeelsLikeC || 0);\n            temp.temp += \"°C\";\n            temp.tempFeelsLike += \"°C\";\n        }\n        temp.lastRefresh = DateTime.time + \" • \" + DateTime.date;\n        root.data = temp;\n    }\n\n    function getData() {\n        let command = \"curl -s wttr.in\";\n\n        if (root.gpsActive && root.location.valid) {\n            command += `/${root.location.lat},${root.location.long}`;\n        } else {\n            command += `/${formatCityName(root.city)}`;\n        }\n\n        // format as json\n        command += \"?format=j1\";\n        command += \" | \";\n        // only take the current weather, location, asytronmy data\n        command += \"jq '{current: .current_condition[0], location: .nearest_area[0], astronomy: .weather[0].astronomy[0]}'\";\n        fetcher.command[2] = command;\n        fetcher.running = true;\n    }\n\n    function formatCityName(cityName) {\n        return cityName.trim().split(/\\s+/).join('+');\n    }\n\n    Component.onCompleted: {\n        if (!root.gpsActive) return;\n        console.info(\"[WeatherService] Starting the GPS service.\");\n        positionSource.start();\n    }\n\n    Process {\n        id: fetcher\n        command: [\"bash\", \"-c\", \"\"]\n        stdout: StdioCollector {\n            onStreamFinished: {\n                if (text.length === 0)\n                    return;\n                try {\n                    const parsedData = JSON.parse(text);\n                    root.refineData(parsedData);\n                    // console.info(`[ data: ${JSON.stringify(parsedData)}`);\n                } catch (e) {\n                    console.error(`[WeatherService] ${e.message}`);\n                }\n            }\n        }\n    }\n\n    PositionSource {\n        id: positionSource\n        updateInterval: root.fetchInterval\n\n        onPositionChanged: {\n            // update the location if the given location is valid\n            // if it fails getting the location, use the last valid location\n            if (position.latitudeValid && position.longitudeValid) {\n                root.location.lat = position.coordinate.latitude;\n                root.location.long = position.coordinate.longitude;\n                root.location.valid = true;\n                // console.info(`📍 Location: ${position.coordinate.latitude}, ${position.coordinate.longitude}`);\n                root.getData();\n                // if can't get initialized with valid location deactivate the GPS\n            } else {\n                root.gpsActive = root.location.valid ? true : false;\n                console.error(\"[WeatherService] Failed to get the GPS location.\");\n            }\n        }\n\n        onValidityChanged: {\n            if (!positionSource.valid) {\n                positionSource.stop();\n                root.location.valid = false;\n                root.gpsActive = false;\n                Quickshell.execDetached([\"notify-send\", Translation.tr(\"Weather Service\"), Translation.tr(\"Cannot find a GPS service. Using the fallback method instead.\"), \"-a\", \"Shell\"]);\n                console.error(\"[WeatherService] Could not aquire a valid backend plugin.\");\n            }\n        }\n    }\n\n    Timer {\n        running: !root.gpsActive\n        repeat: true\n        interval: root.fetchInterval\n        triggeredOnStart: !root.gpsActive\n        onTriggered: root.getData()\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/Ydotool.qml",
    "content": "pragma Singleton\n\nimport qs.modules.common\nimport Quickshell\n\nSingleton {\n    id: root\n    property int shiftMode: 0 // 0: off, 1: on, 2: lock\n    property list<int> shiftKeys: [42, 54] // Keycodes for Shift keys (left and right)\n    property list<int> altKeys: [56, 100] // Keycodes for Alt keys (left and right) \n    property list<int> ctrlKeys: [29, 97] // Keycodes for Ctrl keys (left and right)\n\n    function releaseAllKeys() {\n        const keycodes = Array.from(Array(249).keys());\n        Quickshell.execDetached([\n            \"ydotool\",\n            \"key\", \"--key-delay\", \"0\",\n            ...keycodes.map(keycode => `${keycode}:0`)\n        ])\n        root.shiftMode = 0; // Reset shift mode\n    }\n\n    function releaseShiftKeys() {\n        Quickshell.execDetached([\n            \"ydotool\",\n            \"key\", \"--key-delay\", \"0\",\n            ...root.shiftKeys.map(keycode => `${keycode}:0`)\n        ])\n        root.shiftMode = 0; // Reset shift mode\n    }\n\n    function press(keycode) {\n        Quickshell.execDetached([\n            \"ydotool\",\n            \"key\", \"--key-delay\", \"0\",\n            `${keycode}:1`\n        ]);\n    }\n\n    function release(keycode) {\n        Quickshell.execDetached([\n            \"ydotool\",\n            \"key\", \"--key-delay\", \"0\",\n            `${keycode}:0`\n        ]);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ai/AiMessageData.qml",
    "content": "import QtQuick;\n\n/**\n * Represents a message in an AI conversation. (Kind of) follows the OpenAI API message structure.\n */\nQtObject {\n    property string role\n    property string content\n    property string rawContent\n    property string fileMimeType\n    property string fileUri\n    property string localFilePath\n    property string model\n    property bool thinking: true\n    property bool done: false\n    property var annotations: []\n    property var annotationSources: []\n    property list<string> searchQueries: []\n    property string functionName\n    property var functionCall\n    property string functionResponse\n    property bool functionPending: false\n    property bool visibleToUser: true\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ai/AiModel.qml",
    "content": "import QtQuick;\n\n/**\n * An AI model representation.\n * - name: Friendly name of the model\n * - icon: Icon name of the model\n * - description: Description of the model\n * - endpoint: Endpoint of the model\n * - model: Model code (like gpt-4.1 or gemini-2.5-flash)\n * - requires_key: Whether the model requires an API key\n * - key_id: The identifier of the API key. Use the same identifier for models that can be accessed with the same key.\n * - key_get_link: Link to get an API key\n * - key_get_description: Description of pricing and how to get an API key\n * - api_format: The API format of the model. Can be \"openai\" or \"gemini\". Default is \"openai\".\n * - extraParams: Extra parameters to be passed to the model. This is a JSON object.\n */\n\nQtObject {\n    property string name\n    property string icon\n    property string description\n    property string homepage\n    property string endpoint\n    property string model\n    property bool requires_key: true\n    property string key_id\n    property string key_get_link\n    property string key_get_description\n    property string api_format: \"openai\"\n    property var tools\n    property var extraParams: ({})\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ai/ApiStrategy.qml",
    "content": "import QtQuick\n\nQtObject {\n    function buildEndpoint(model: AiModel): string { throw new Error(\"Not implemented\") }\n    function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>, filePath: string) { throw new Error(\"Not implemented\") }\n    function buildAuthorizationHeader(apiKeyEnvVarName: string): string { throw new Error(\"Not implemented\") }\n    function parseResponseLine(line: string, message: AiMessageData) { throw new Error(\"Not implemented\") }\n    function onRequestFinished(message: AiMessageData): var { return {} } // Default: no special handling\n    function reset() { } // Reset any internal state if needed\n    function buildScriptFileSetup(filePath) { return \"\" } // Default: no setup\n    function finalizeScriptContent(scriptContent: string): string { return scriptContent } // Optionally modify/finalize script\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ai/GeminiApiStrategy.qml",
    "content": "import QtQuick\nimport qs.modules.common.functions as CF\n\nApiStrategy {\n    readonly property string apiKeyEnvVarName: \"API_KEY\"\n    readonly property string fileUriVarName: \"file_uri\"\n    readonly property string fileMimeTypeVarName: \"MIME_TYPE\"\n    readonly property string fileUriSubstitutionString: \"{{ fileUriVarName }}\"\n    readonly property string fileMimeTypeSubstitutionString: \"{{ fileMimeTypeVarName }}\"\n    property string buffer: \"\"\n    \n    function buildEndpoint(model: AiModel): string {\n        const result = model.endpoint + `?key=\\$\\{${root.apiKeyEnvVarName}\\}`\n        // console.log(\"[AI] Endpoint: \" + result);\n        return result;\n    }\n\n    function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>, filePath: string) {\n        let contents = messages.map(message => {\n            // console.log(\"[AI] Building request data for message:\", JSON.stringify(message, null, 2));\n            const geminiApiRoleName = (message.role === \"assistant\") ? \"model\" : message.role;\n            const usingSearch = tools[0]?.google_search !== undefined\n            if (!usingSearch && message.functionCall != undefined && message.functionName.length > 0) {\n                return {\n                    \"role\": geminiApiRoleName,\n                    \"parts\": [{\n                        functionCall: {\n                            \"name\": message.functionName,\n                        }\n                    }]\n                }\n            }\n            if (!usingSearch && message.functionResponse != undefined && message.functionName.length > 0) {\n                return {\n                    \"role\": geminiApiRoleName,\n                    \"parts\": [{ \n                        functionResponse: {\n                            \"name\": message.functionName,\n                            \"response\": { \"content\": message.functionResponse }\n                        }\n                    }]\n                }\n            }\n            return {\n                \"role\": geminiApiRoleName,\n                \"parts\": [\n                    { text: message.rawContent },\n                    ...(message.fileUri && message.fileUri.length > 0 ? [{ \n                        \"file_data\": {\n                            \"mime_type\": message.fileMimeType,\n                            \"file_uri\": message.fileUri\n                        }\n                    }] : [])\n                ]\n            }\n        })\n        if (filePath && filePath.length > 0) {\n            const trimmedFilePath = CF.FileUtils.trimFileProtocol(filePath);\n            // Add file_data part to the last message's parts array\n            contents[contents.length - 1].parts.unshift({\n                file_data: {\n                    mime_type: fileMimeTypeSubstitutionString,\n                    file_uri: fileUriSubstitutionString\n                }\n            });\n        }\n        let baseData = {\n            \"contents\": contents,\n            \"tools\": tools,\n            \"system_instruction\": {\n                \"parts\": [{ text: systemPrompt }]\n            },\n            \"generationConfig\": {\n                \"temperature\": temperature,\n            },\n        };\n        // print(\"Gemini API call payload:\", JSON.stringify(baseData, null, 2));\n        return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;\n    }\n\n    function buildAuthorizationHeader(apiKeyEnvVarName: string): string {\n        // Gemini doesn't use Authorization header, key is in URL\n        return \"\";\n    }\n\n    function parseResponseLine(line, message) {\n        if (line.startsWith(\"[\")) {\n            buffer += line.slice(1).trim();\n        } else if (line === \"]\") {\n            buffer += line.slice(0, -1).trim();\n            return parseBuffer(message);\n        } else if (line.startsWith(\",\")) {\n            return parseBuffer(message);\n        } else {\n            buffer += line.trim();\n        }\n        return {};\n    }\n\n    function parseBuffer(message) {\n        // console.log(\"[Ai] Gemini buffer: \", buffer);\n        let finished = false;\n        try {\n            if (buffer.length === 0) return {};\n            const dataJson = JSON.parse(buffer);\n\n            // Uploaded file\n            if (dataJson.uploadedFile) {\n                message.fileUri = dataJson.uploadedFile.uri;\n                message.fileMimeType = dataJson.uploadedFile.mimeType;\n                return ({})\n            }\n\n            // Error response handling\n            if (dataJson.error) {\n                const errorMsg = `**Error ${dataJson.error.code}**: ${dataJson.error.message}`;\n                message.rawContent += errorMsg;\n                message.content += errorMsg;\n                return { finished: true };\n            }\n\n            // No candidates?\n            if (!dataJson.candidates) return {};\n            \n            // Finished?\n            if (dataJson.candidates[0]?.finishReason) {\n                finished = true;\n            }\n            \n            // Function call handling\n            if (dataJson.candidates[0]?.content?.parts[0]?.functionCall) {\n                const functionCall = dataJson.candidates[0]?.content?.parts[0]?.functionCall;\n                message.functionName = functionCall.name;\n                message.functionCall = functionCall.name;\n                const newContent = `\\n\\n[[ Function: ${functionCall.name}(${JSON.stringify(functionCall.args, null, 2)}) ]]\\n`\n                message.rawContent += newContent;\n                message.content += newContent;\n                return { functionCall: { name: functionCall.name, args: functionCall.args }, finished: finished };\n            }\n\n            // Normal text response\n            const responseContent = dataJson.candidates[0]?.content?.parts[0]?.text\n            message.rawContent += responseContent;\n            message.content += responseContent;\n            \n            // Handle annotations and metadata\n            const annotationSources = dataJson.candidates[0]?.groundingMetadata?.groundingChunks?.map(chunk => {\n                return {\n                    \"type\": \"url_citation\",\n                    \"text\": chunk?.web?.title,\n                    \"url\": chunk?.web?.uri,\n                }\n            }) ?? [];\n\n            const annotations = dataJson.candidates[0]?.groundingMetadata?.groundingSupports?.map(citation => {\n                return {\n                    \"type\": \"url_citation\",\n                    \"start_index\": citation.segment?.startIndex,\n                    \"end_index\": citation.segment?.endIndex,\n                    \"text\": citation?.segment.text,\n                    \"url\": annotationSources[citation.groundingChunkIndices[0]]?.url,\n                    \"sources\": citation.groundingChunkIndices\n                }\n            });\n            message.annotationSources = annotationSources;\n            message.annotations = annotations;\n            message.searchQueries = dataJson.candidates[0]?.groundingMetadata?.webSearchQueries ?? [];\n\n            // Usage metadata\n            if (dataJson.usageMetadata) {\n                return {\n                    tokenUsage: {\n                        input: dataJson.usageMetadata.promptTokenCount ?? -1,\n                        output: dataJson.usageMetadata.candidatesTokenCount ?? -1,\n                        total: dataJson.usageMetadata.totalTokenCount ?? -1\n                    },\n                    finished: finished\n                };\n            }\n            \n        } catch (e) {\n            console.log(\"[AI] Gemini: Could not parse buffer: \", e);\n            message.rawContent += buffer;\n            message.content += buffer;\n        } finally {\n            buffer = \"\";\n        }\n        return { finished: finished };\n    }\n\n    function onRequestFinished(message) {\n        return parseBuffer(message);\n    }\n    \n    function reset() {\n        buffer = \"\";\n    }\n\n    function buildScriptFileSetup(filePath) {\n        const trimmedFilePath = CF.FileUtils.trimFileProtocol(filePath);\n        let content = \"\"\n\n        // print(\"file path:\", filePath)\n        // print(\"trimmed file path:\", trimmedFilePath)\n        // print(\"escaped file path:\", CF.StringUtils.shellSingleQuoteEscape(trimmedFilePath))\n\n        content += `IMAGE_PATH='${CF.StringUtils.shellSingleQuoteEscape(trimmedFilePath)}'\\n`;\n        content += `${fileMimeTypeVarName}=$(file -b --mime-type \"$IMAGE_PATH\")\\n`;\n        content += 'NUM_BYTES=$(wc -c < \"${IMAGE_PATH}\")\\n';\n        content += 'tmp_header_file=\"/tmp/quickshell/ai/upload-header.tmp\"\\n';\n        content += 'tmp_file_info_file=\"/tmp/quickshell/ai/file-info.json.tmp\"\\n';\n\n        // Initial resumable request defining metadata.\n        // The upload url is in the response headers dump them to a file.\n        content += 'curl \"https://generativelanguage.googleapis.com/upload/v1beta/files\"'\n            + ` -H \"x-goog-api-key: \\$${apiKeyEnvVarName}\"`\n            + ' -D $tmp_header_file'\n            + ' -H \"X-Goog-Upload-Protocol: resumable\"'\n            + ' -H \"X-Goog-Upload-Command: start\"'\n            + ' -H \"X-Goog-Upload-Header-Content-Length: ${NUM_BYTES}\"'\n            + ` -H \"X-Goog-Upload-Header-Content-Type: \\${${fileMimeTypeVarName}}\"`\n            + ' -H \"Content-Type: application/json\"'\n            + ` -d \"{'file': {'display_name': 'Image'}}\" 2> /dev/null`\n            + '\\n';\n\n        // Get file upload header\n        content += 'upload_url=$(grep -i \"x-goog-upload-url: \" \"${tmp_header_file}\" | cut -d\" \" -f2 | tr -d \"\\r\")\\n';\n        content += 'rm \"${tmp_header_file}\"\\n';\n\n        // Upload the actual file\n        content += 'curl \"${upload_url}\"'\n            + ` -H \"x-goog-api-key: \\$${apiKeyEnvVarName}\"`\n            + ' -H \"Content-Length: ${NUM_BYTES}\"'\n            + ' -H \"X-Goog-Upload-Offset: 0\"'\n            + ' -H \"X-Goog-Upload-Command: upload, finalize\"'\n            + ' --data-binary \"@${IMAGE_PATH}\" 2> /dev/null > \"${tmp_file_info_file}\"'\n            + '\\n';\n\n        content += `${fileUriVarName}=$(jq -r \".file.uri\" \"$tmp_file_info_file\")\\n`\n        content += `printf \"{\\\\\"uploadedFile\\\\\": {\\\\\"uri\\\\\": \\\\\"$${fileUriVarName}\\\\\", \\\\\"mimeType\\\\\": \\\\\"$${fileMimeTypeVarName}\\\\\"}}\\\\n,\\\\n\"\\n`\n\n        return content\n    }\n\n    function finalizeScriptContent(scriptContent: string): string {\n        return scriptContent.replace(fileMimeTypeSubstitutionString, `'\"\\$${fileMimeTypeVarName}\"'`)\n                            .replace(fileUriSubstitutionString, `'\"\\$${fileUriVarName}\"'`);\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ai/MistralApiStrategy.qml",
    "content": "import QtQuick\n\nApiStrategy {\n    property bool isReasoning: false\n    \n    function buildEndpoint(model: AiModel): string {\n        // console.log(\"[AI] Endpoint: \" + model.endpoint);\n        return model.endpoint;\n    }\n\n    function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>, filePath: string) {\n        let baseData = {\n            \"model\": model.model,\n            \"messages\": [\n                {role: \"system\", content: systemPrompt},\n                ...messages.map(message => {\n                    const hasFunctionCall = message.functionCall != undefined && message.functionName.length > 0\n                    let messageData = {\n                        \"role\": message.role,\n                        \"content\": message.rawContent,\n                    }\n                    if (hasFunctionCall) {\n                        if (message.functionResponse?.length > 0) {\n                            messageData.name = message.functionName; // Does the func call also need this name? or just the func output?\n                            messageData.role = \"tool\";\n                            messageData.content = message.functionResponse;\n                            messageData.tool_call_id = message.functionCall.id\n                        }\n                    }\n                    return messageData\n                }),\n            ],\n            \"stream\": true,\n            \"temperature\": temperature,\n            \"tools\": tools,\n        };\n        // console.log(\"[AI] Request data: \", JSON.stringify(baseData, null, 2));\n        return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;\n    }\n\n    function buildAuthorizationHeader(apiKeyEnvVarName: string): string {\n        return `-H \"Authorization: Bearer \\$\\{${apiKeyEnvVarName}\\}\"`;\n    }\n\n    function parseResponseLine(line, message) {\n        // Remove 'data: ' prefix if present and trim whitespace\n        let cleanData = line.trim();\n        if (cleanData.startsWith(\"data:\")) {\n            cleanData = cleanData.slice(5).trim();\n        }\n        \n        // Handle special cases\n        if (!cleanData || cleanData.startsWith(\":\")) return {};\n        if (cleanData === \"[DONE]\") {\n            return { finished: true };\n        }\n        \n        // Real stuff\n        try {\n            const dataJson = JSON.parse(cleanData);\n\n            // Error response handling\n            if (dataJson.error) {\n                const errorMsg = `**Error**: ${dataJson.error.message || JSON.stringify(dataJson.error)}`;\n                message.rawContent += errorMsg;\n                message.content += errorMsg;\n                return { finished: true };\n            }\n\n            let newContent = \"\";\n\n            const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;\n            const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;\n\n            // Function call\n            if (dataJson.choices[0]?.delta?.tool_calls) {\n                const functionCall = dataJson.choices[0].delta.tool_calls[0];\n                const functionName = functionCall.function.name;\n                const functionArgs = JSON.parse(functionCall.function.arguments) || {}; // Args are given as string???\n                const functionId = functionCall.id;\n                const newContent = `\\n\\n[[ Function: ${functionName}(${JSON.stringify(functionArgs, null, 2)}) ]]\\n`;\n                message.rawContent += newContent;\n                message.content += newContent;\n                message.functionName = functionName;\n                message.functionCall = functionName; \n                return { functionCall: { name: functionName, args: functionArgs, id: functionId } };\n            }\n\n            // Thinking?\n            if (responseContent && responseContent.length > 0) {\n                if (isReasoning) {\n                    isReasoning = false;\n                    const endBlock = \"\\n\\n</think>\\n\\n\";\n                    message.content += endBlock;\n                    message.rawContent += endBlock;\n                }\n                newContent = responseContent;\n            } else if (responseReasoning && responseReasoning.length > 0) {\n                if (!isReasoning) {\n                    isReasoning = true;\n                    const startBlock = \"\\n\\n<think>\\n\\n\";\n                    message.rawContent += startBlock;\n                    message.content += startBlock;\n                }\n                newContent = responseReasoning;\n            }\n\n            // Text\n            message.content += newContent;\n            message.rawContent += newContent;\n\n            // Usage metadata\n            if (dataJson.usage) {\n                return {\n                    tokenUsage: {\n                        input: dataJson.usage.prompt_tokens ?? -1,\n                        output: dataJson.usage.completion_tokens ?? -1,\n                        total: dataJson.usage.total_tokens ?? -1\n                    }\n                };\n            }\n\n            if (`dataJson`.done) {\n                return { finished: true };\n            }\n            \n        } catch (e) {\n            console.log(\"[AI] Mistral: Could not parse line: \", e);\n            message.rawContent += line;\n            message.content += line;\n        }\n        \n        return {};\n    }\n    \n    function onRequestFinished(message) {\n        return {};\n    }\n    \n    function reset() {\n        isReasoning = false;\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/ai/OpenAiApiStrategy.qml",
    "content": "import QtQuick\n\nApiStrategy {\n    property bool isReasoning: false\n    \n    function buildEndpoint(model: AiModel): string {\n        // console.log(\"[AI] Endpoint: \" + model.endpoint);\n        return model.endpoint;\n    }\n\n    function buildRequestData(model: AiModel, messages, systemPrompt: string, temperature: real, tools: list<var>, filePath: string) {\n        let baseData = {\n            \"model\": model.model,\n            \"messages\": [\n                {role: \"system\", content: systemPrompt},\n                ...messages.map(message => {\n                    return {\n                        \"role\": message.role,\n                        \"content\": message.rawContent,\n                    }\n                }),\n            ],\n            \"stream\": true,\n            \"tools\": tools,\n            \"temperature\": temperature,\n        };\n        return model.extraParams ? Object.assign({}, baseData, model.extraParams) : baseData;\n    }\n\n    function buildAuthorizationHeader(apiKeyEnvVarName: string): string {\n        return `-H \"Authorization: Bearer \\$\\{${apiKeyEnvVarName}\\}\"`;\n    }\n\n    function parseResponseLine(line, message) {\n        // Remove 'data: ' prefix if present and trim whitespace\n        let cleanData = line.trim();\n        if (cleanData.startsWith(\"data:\")) {\n            cleanData = cleanData.slice(5).trim();\n        }\n\n        // console.log(\"[AI] OpenAI: Data:\", cleanData);\n        \n        // Handle special cases\n        if (!cleanData || cleanData.startsWith(\":\")) return {};\n        if (cleanData === \"[DONE]\") {\n            return { finished: true };\n        }\n        \n        // Real stuff\n        try {\n            const dataJson = JSON.parse(cleanData);\n\n            // Error response handling\n            if (dataJson.error) {\n                const errorMsg = `**Error**: ${dataJson.error.message || JSON.stringify(dataJson.error)}`;\n                message.rawContent += errorMsg;\n                message.content += errorMsg;\n                return { finished: true };\n            }\n\n            let newContent = \"\";\n\n            const responseContent = dataJson.choices[0]?.delta?.content || dataJson.message?.content;\n            const responseReasoning = dataJson.choices[0]?.delta?.reasoning || dataJson.choices[0]?.delta?.reasoning_content;\n\n            if (responseContent && responseContent.length > 0) {\n                if (isReasoning) {\n                    isReasoning = false;\n                    const endBlock = \"\\n\\n</think>\\n\\n\";\n                    message.content += endBlock;\n                    message.rawContent += endBlock;\n                }\n                newContent = responseContent;\n            } else if (responseReasoning && responseReasoning.length > 0) {\n                if (!isReasoning) {\n                    isReasoning = true;\n                    const startBlock = \"\\n\\n<think>\\n\\n\";\n                    message.rawContent += startBlock;\n                    message.content += startBlock;\n                }\n                newContent = responseReasoning;\n            }\n\n            message.content += newContent;\n            message.rawContent += newContent;\n\n            // Usage metadata\n            if (dataJson.usage) {\n                return {\n                    tokenUsage: {\n                        input: dataJson.usage.prompt_tokens ?? -1,\n                        output: dataJson.usage.completion_tokens ?? -1,\n                        total: dataJson.usage.total_tokens ?? -1\n                    }\n                };\n            }\n\n            if (dataJson.done) {\n                return { finished: true };\n            }\n            \n        } catch (e) {\n            console.log(\"[AI] OpenAI: Could not parse line: \", e);\n            message.rawContent += line;\n            message.content += line;\n        }\n        \n        return {};\n    }\n    \n    function onRequestFinished(message) {\n        // OpenAI format doesn't need special finish handling\n        return {};\n    }\n    \n    function reset() {\n        isReasoning = false;\n    }\n\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/gCloud/token-from-key-venv.sh",
    "content": "#!/usr/bin/env bash\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\n\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n\"$SCRIPT_DIR/token_from_key.py\" \"$@\"\ndeactivate\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/gCloud/token_from_key.py",
    "content": "#!/usr/bin/env python3\nimport calendar\nimport sys\nimport json\nimport google.auth.transport.requests\nimport google.oauth2.service_account\n\ndef get_token(json_str):\n    try:\n        # Load the string into a dictionary\n        info = json.loads(json_str)\n        \n        # Initialize credentials\n        creds = google.oauth2.service_account.Credentials.from_service_account_info(info)\n        scoped_creds = creds.with_scopes(['https://www.googleapis.com/auth/cloud-platform'])\n        \n        # Refresh to get the access token\n        request = google.auth.transport.requests.Request()\n        scoped_creds.refresh(request)\n\n        token = scoped_creds.token\n        expiry = int(calendar.timegm(scoped_creds.expiry.utctimetuple()))\n        \n        print(json.dumps({\n            \"token\": token,\n            \"expiry\": expiry\n        }))\n\n    except Exception as e:\n        sys.stderr.write(f\"Error: {str(e)}\\n\")\n        sys.exit(1)\n\nif __name__ == \"__main__\":\n    if len(sys.argv) < 2:\n        sys.stderr.write(\"Usage: python3 get_token.py '<json_string>'\\n\")\n        sys.exit(1)\n    \n    get_token(sys.argv[1])\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/hyprlandAntiFlashbangShader/anti-flashbang.glsl",
    "content": "#version 300 es\nprecision highp float;\n\nin vec2 v_texcoord;\nuniform sampler2D tex;\nout vec4 fragColor;\n\nfloat overlayOpacityForBrightness(float x) {\n    // Note: range 0 to 1\n    \n    // Will a fancy curve help?... I'll have to experiment more at night\n    // float y = pow(x, 2.0) * 0.75;\n    // float y = (1.0 - exp(-x))*1.15;\n    // float y = (1.0 - exp(-pow((x-0.15), 0.6)))*1.18;\n\n    float y = x*0.75;\n    return min(max(y, 0.001), 1.0);\n}\n\nvoid main() {\n    // 1. Get the current pixel color\n    vec4 pixColor = texture(tex, v_texcoord);\n\n    // 2. Calculate average screen brightness\n    vec3 totalRGB = vec3(0.0);\n    float samples = 0.0;\n    \n    // We use a nested loop to create a 10x10 grid (100 samples)\n    // This is dense enough to catch small icons/text but light enough to run fast.\n    for(float x = 0.05; x < 1.0; x += 0.1) {\n        for(float y = 0.05; y < 1.0; y += 0.1) {\n            totalRGB += texture(tex, vec2(x, y)).rgb;\n            samples++;\n        }\n    }\n    \n    vec3 avgColor = totalRGB / samples;\n    float globalBrightness = dot(avgColor, vec3(0.2126, 0.7152, 0.0722));\n\n    // 3. Get the specific opacity for this brightness level\n    float opacity = overlayOpacityForBrightness(globalBrightness);\n\n    // 4. Apply the \"black overlay\" effect\n    vec3 outColor = mix(pixColor.rgb, vec3(0.0), opacity);\n\n    fragColor = vec4(outColor, pixColor.a);\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/services/network/WifiAccessPoint.qml",
    "content": "import QtQuick\n\nQtObject {\n    required property var lastIpcObject\n    readonly property string ssid: lastIpcObject.ssid\n    readonly property string bssid: lastIpcObject.bssid\n    readonly property int strength: lastIpcObject.strength\n    readonly property int frequency: lastIpcObject.frequency\n    readonly property bool active: lastIpcObject.active\n    readonly property string security: lastIpcObject.security\n    readonly property bool isSecure: security.length > 0\n\n    property bool askingPassword: false\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/settings.qml",
    "content": "//@ pragma UseQApplication\n//@ pragma Env QS_NO_RELOAD_POPUP=1\n//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic\n//@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000\n\n// Adjust this to make the app smaller or larger\n//@ pragma Env QT_SCALE_FACTOR=1\n\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport QtQuick.Window\nimport Quickshell\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions as CF\n\nApplicationWindow {\n    id: root\n    property string firstRunFilePath: CF.FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`)\n    property string firstRunFileContent: \"This file is just here to confirm you've been greeted :>\"\n    property real contentPadding: 8\n    property bool showNextTime: false\n    property var pages: [\n        {\n            name: Translation.tr(\"Quick\"),\n            icon: \"instant_mix\",\n            component: \"modules/settings/QuickConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"General\"),\n            icon: \"browse\",\n            component: \"modules/settings/GeneralConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"Bar\"),\n            icon: \"toast\",\n            iconRotation: 180,\n            component: \"modules/settings/BarConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"Background\"),\n            icon: \"texture\",\n            component: \"modules/settings/BackgroundConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"Interface\"),\n            icon: \"bottom_app_bar\",\n            component: \"modules/settings/InterfaceConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"Services\"),\n            icon: \"settings\",\n            component: \"modules/settings/ServicesConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"Advanced\"),\n            icon: \"construction\",\n            component: \"modules/settings/AdvancedConfig.qml\"\n        },\n        {\n            name: Translation.tr(\"About\"),\n            icon: \"info\",\n            component: \"modules/settings/About.qml\"\n        }\n    ]\n    property int currentPage: 0\n\n    visible: true\n    onClosing: Qt.quit()\n    title: \"illogical-impulse Settings\"\n\n    Component.onCompleted: {\n        MaterialThemeLoader.reapplyTheme()\n        Config.readWriteDelay = 0 // Settings app always only sets one var at a time so delay isn't needed\n    }\n\n    minimumWidth: 750\n    minimumHeight: 500\n    width: 1100\n    height: 750\n    color: Appearance.m3colors.m3background\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            margins: contentPadding\n        }\n\n        Keys.onPressed: (event) => {\n            if (event.modifiers === Qt.ControlModifier) {\n                if (event.key === Qt.Key_PageDown) {\n                    root.currentPage = Math.min(root.currentPage + 1, root.pages.length - 1)\n                    event.accepted = true;\n                } \n                else if (event.key === Qt.Key_PageUp) {\n                    root.currentPage = Math.max(root.currentPage - 1, 0)\n                    event.accepted = true;\n                }\n                else if (event.key === Qt.Key_Tab) {\n                    root.currentPage = (root.currentPage + 1) % root.pages.length;\n                    event.accepted = true;\n                }\n                else if (event.key === Qt.Key_Backtab) {\n                    root.currentPage = (root.currentPage - 1 + root.pages.length) % root.pages.length;\n                    event.accepted = true;\n                }\n            }\n        }\n\n        Item { // Titlebar\n            visible: Config.options?.windows.showTitlebar\n            Layout.fillWidth: true\n            Layout.fillHeight: false\n            implicitHeight: Math.max(titleText.implicitHeight, windowControlsRow.implicitHeight)\n            StyledText {\n                id: titleText\n                anchors {\n                    left: Config.options.windows.centerTitle ? undefined : parent.left\n                    horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined\n                    verticalCenter: parent.verticalCenter\n                    leftMargin: 12\n                }\n                color: Appearance.colors.colOnLayer0\n                text: Translation.tr(\"Settings\")\n                font {\n                    family: Appearance.font.family.title\n                    pixelSize: Appearance.font.pixelSize.title\n                    variableAxes: Appearance.font.variableAxes.title\n                }\n            }\n            RowLayout { // Window controls row\n                id: windowControlsRow\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.right: parent.right\n                RippleButton {\n                    buttonRadius: Appearance.rounding.full\n                    implicitWidth: 35\n                    implicitHeight: 35\n                    onClicked: root.close()\n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        text: \"close\"\n                        iconSize: 20\n                    }\n                }\n            }\n        }\n\n        RowLayout { // Window content with navigation rail and content pane\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n            spacing: contentPadding\n            Item {\n                id: navRailWrapper\n                Layout.fillHeight: true\n                Layout.margins: 5\n                implicitWidth: navRail.expanded ? 150 : fab.baseSize\n                Behavior on implicitWidth {\n                    animation: Appearance.animation.elementMoveFast.numberAnimation.createObject(this)\n                }\n                NavigationRail { // Window content with navigation rail and content pane\n                    id: navRail\n                    anchors {\n                        left: parent.left\n                        top: parent.top\n                        bottom: parent.bottom\n                    }\n                    spacing: 10\n                    expanded: root.width > 900\n                    \n                    NavigationRailExpandButton {\n                        focus: root.visible\n                    }\n\n                    FloatingActionButton {\n                        id: fab\n                        property bool justCopied: false\n                        iconText: justCopied ? \"check\" : \"edit\"\n                        buttonText: justCopied ? Translation.tr(\"Path copied\") : Translation.tr(\"Config file\")\n                        expanded: navRail.expanded\n                        downAction: () => {\n                            Qt.openUrlExternally(`${Directories.config}/illogical-impulse/config.json`);\n                        }\n                        altAction: () => {\n                            Quickshell.clipboardText = CF.FileUtils.trimFileProtocol(`${Directories.config}/illogical-impulse/config.json`);\n                            fab.justCopied = true;\n                            revertTextTimer.restart()\n                        }\n\n                        Timer {\n                            id: revertTextTimer\n                            interval: 1500\n                            onTriggered: {\n                                fab.justCopied = false;\n                            }\n                        }\n\n                        StyledToolTip {\n                            text: Translation.tr(\"Open the shell config file\\nAlternatively right-click to copy path\")\n                        }\n                    }\n\n                    NavigationRailTabArray {\n                        currentIndex: root.currentPage\n                        expanded: navRail.expanded\n                        Repeater {\n                            model: root.pages\n                            NavigationRailButton {\n                                required property var index\n                                required property var modelData\n                                toggled: root.currentPage === index\n                                onPressed: root.currentPage = index;\n                                expanded: navRail.expanded\n                                buttonIcon: modelData.icon\n                                buttonIconRotation: modelData.iconRotation || 0\n                                buttonText: modelData.name\n                                showToggledHighlight: false\n                            }\n                        }\n                    }\n\n                    Item {\n                        Layout.fillHeight: true\n                    }\n                }\n            }\n            Rectangle { // Content container\n                Layout.fillWidth: true\n                Layout.fillHeight: true\n                color: Appearance.m3colors.m3surfaceContainerLow\n                radius: Appearance.rounding.windowRounding - root.contentPadding\n\n                Loader {\n                    id: pageLoader\n                    anchors.fill: parent\n                    opacity: 1.0\n\n                    active: Config.ready\n                    Component.onCompleted: {\n                        source = root.pages[0].component\n                    }\n\n                    Connections {\n                        target: root\n                        function onCurrentPageChanged() {\n                            switchAnim.complete();\n                            switchAnim.start();\n                        }\n                    }\n\n                    SequentialAnimation {\n                        id: switchAnim\n\n                        NumberAnimation {\n                            target: pageLoader\n                            properties: \"opacity\"\n                            from: 1\n                            to: 0\n                            duration: 100\n                            easing.type: Appearance.animation.elementMoveExit.type\n                            easing.bezierCurve: Appearance.animationCurves.emphasizedFirstHalf\n                        }\n                        ParallelAnimation {\n                            PropertyAction {\n                                target: pageLoader\n                                property: \"source\"\n                                value: root.pages[root.currentPage].component\n                            }\n                            PropertyAction {\n                                target: pageLoader\n                                property: \"anchors.topMargin\"\n                                value: 20\n                            }\n                        }\n                        ParallelAnimation {\n                            NumberAnimation {\n                                target: pageLoader\n                                properties: \"opacity\"\n                                from: 0\n                                to: 1\n                                duration: 200\n                                easing.type: Appearance.animation.elementMoveEnter.type\n                                easing.bezierCurve: Appearance.animationCurves.emphasizedLastHalf\n                            }\n                            NumberAnimation {\n                                target: pageLoader\n                                properties: \"anchors.topMargin\"\n                                to: 0\n                                duration: 200\n                                easing.type: Appearance.animation.elementMoveEnter.type\n                                easing.bezierCurve: Appearance.animationCurves.emphasizedLastHalf\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/shell.qml",
    "content": "//@ pragma UseQApplication\n//@ pragma Env QS_NO_RELOAD_POPUP=1\n//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic\n//@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000\n\n// Remove two slashes below and adjust the value to change the UI scale\n////@ pragma Env QT_SCALE_FACTOR=1\n\nimport \"modules/common\"\nimport \"services\"\nimport \"panelFamilies\"\n\nimport QtQuick\nimport QtQuick.Window\nimport Quickshell\nimport Quickshell.Io\nimport Quickshell.Hyprland\n\nShellRoot {\n    id: root\n\n    // Stuff for every panel family\n    ReloadPopup {}\n\n    Component.onCompleted: {\n        MaterialThemeLoader.reapplyTheme()\n        Hyprsunset.load()\n        FirstRunExperience.load()\n        ConflictKiller.load()\n        Cliphist.refresh()\n        Wallpapers.load()\n        Updates.load()\n    }\n\n\n    // Panel families\n    property list<string> families: [\"ii\", \"waffle\"]\n    function cyclePanelFamily() {\n        const currentIndex = families.indexOf(Config.options.panelFamily)\n        const nextIndex = (currentIndex + 1) % families.length\n        Config.options.panelFamily = families[nextIndex]\n    }\n\n    component PanelFamilyLoader: LazyLoader {\n        required property string identifier\n        property bool extraCondition: true\n        active: Config.ready && Config.options.panelFamily === identifier && extraCondition\n    }\n    \n    PanelFamilyLoader {\n        identifier: \"ii\"\n        component: IllogicalImpulseFamily {}\n    }\n\n    PanelFamilyLoader {\n        identifier: \"waffle\"\n        component: WaffleFamily {}\n    }\n\n\n    // Shortcuts\n    IpcHandler {\n        target: \"panelFamily\"\n\n        function cycle(): void {\n            root.cyclePanelFamily()\n        }\n    }\n\n    GlobalShortcut {\n        name: \"panelFamilyCycle\"\n        description: \"Cycles panel family\"\n\n        onPressed: root.cyclePanelFamily()\n    }\n}\n\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/de_DE.json",
    "content": "{\n  \"Material cookie\": \"Material Cookie\",\n  \"Style: Blurred\": \"Stil: Verschwommen\",\n  \"Unknown device\": \"Unbekanntes Gerät\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Jederzeit später ändern mit /dark, /light, /wallpaper im Launcher\\nFalls sich die Shell-Farben nicht ändern:\\n    1. Öffne die rechte Seitenleiste mit Super+N\\n    2. Klicke auf \\\"Hyprland & Quickshell neu laden\\\" in der oberen rechten Ecke\",\n  \"No pending tasks\": \"Keine ausstehenden Aufgaben\",\n  \"Positioning\": \"Positionierung\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Temperatur (Zufälligkeit) des Modells einstellen. Wertebereich zwischen 0 und 2 für Gemini, 0 bis 1 für andere Modelle. Standard ist 0,5.\",\n  \"Critical warning\": \"Kritische Warnung\",\n  \"Unknown Artist\": \"Unbekannter Künstler\",\n  \"Web search\": \"Websuche\",\n  \"Load prompt from %1\": \"Prompt von %1 laden\",\n  \"Attach a file. Only works with Gemini.\": \"Datei anhängen. Funktioniert nur mit Gemini.\",\n  \"Reboot\": \"Neustart\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"API-Schlüssel:\\n\\n```txt\\n%1\\n```\",\n  \"Pinned on startup\": \"Beim Start angeheftet\",\n  \"Right\": \"Rechts\",\n  \"Reboot to firmware settings\": \"Neustart zu Firmware-Einstellungen\",\n  \"Automatically hide\": \"Automatisch ausblenden\",\n  \"Waiting for response...\": \"Warte auf Antwort...\",\n  \"To Do\": \"Aufgaben\",\n  \"Full\": \"Voll\",\n  \"Select Language\": \"Sprache auswählen\",\n  \"Password\": \"Passwort\",\n  \"Bluetooth devices\": \"Bluetooth-Geräte\",\n  \"Enable\": \"Aktivieren\",\n  \"Elements\": \"Elemente\",\n  \"Start\": \"Start\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Zufälliges SFW-Anime-Hintergrundbild von Konachan\\nBild wird in ~/Pictures/Wallpapers gespeichert\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Der beliebte | Beste Menge, aber Qualität kann stark variieren\",\n  \"System uptime:\": \"Systemlaufzeit:\",\n  \"illogical-impulse Welcome\": \"illogical-impulse Willkommen\",\n  \"Code saved to file\": \"Code in Datei gespeichert\",\n  \"Info\": \"Info\",\n  \"Preferred wallpaper zoom (%)\": \"Bevorzugter Hintergrundbild-Zoom (%)\",\n  \"Time\": \"Zeit\",\n  \"Help & Support\": \"Hilfe & Support\",\n  \"Bubble\": \"Blase\",\n  \"Large images | God tier quality, no NSFW.\": \"Große Bilder | Höchste Qualität, kein NSFW.\",\n  \"Dark\": \"Dunkel\",\n  \"Center clock\": \"Zentrale Uhr\",\n  \"Search, calculate or run\": \"Suchen, berechnen oder ausführen\",\n  \"Region height\": \"Regionshöhe\",\n  \"Load chat\": \"Chat laden\",\n  \"Gives the model search capabilities (immediately)\": \"Gibt dem Modell Suchfähigkeiten (sofort)\",\n  \"Depends on workspace\": \"Hängt vom Arbeitsbereich ab\",\n  \"Blurred style\": \"Verschwommener Stil\",\n  \"Screenshot tool\": \"Screenshot-Tool\",\n  \"Enter password\": \"Passwort eingeben\",\n  \"Search the web\": \"Im Web suchen\",\n  \"Local only\": \"Nur lokal\",\n  \"at\": \"um\",\n  \"Math\": \"Mathematik\",\n  \"Consider plugging in your device\": \"Erwäge, dein Gerät anzuschließen\",\n  \"Workspaces shown\": \"Angezeigte Arbeitsbereiche\",\n  \"Place the corners to trigger at the bottom\": \"Platziere die Ecken zum Auslösen am unteren Rand\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Kein API-Schlüssel\\nSetze ihn mit /key DEIN_API_SCHLUESSEL\",\n  \"Auto (System)\": \"Auto (System)\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Pfeiltasten zum Navigieren, Enter zum Auswählen\\nEsc oder irgendwo klicken zum Abbrechen\",\n  \"Critically low battery\": \"Kritisch niedrige Batterie\",\n  \"Open editor\": \"Editor öffnen\",\n  \"%1 notifications\": \"%1 Benachrichtigungen\",\n  \"Region width\": \"Regionsbreite\",\n  \"Max allowed increase\": \"Maximal erlaubter Anstieg\",\n  \"Enable translator\": \"Übersetzer aktivieren\",\n  \"Constantly rotate\": \"Ständig rotieren\",\n  \"Automatically suspends the system when battery is low\": \"Suspendiert das System automatisch bei niedrigem Batteriestand\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"GPS-Dienst nicht gefunden. Verwende stattdessen die Fallback-Methode.\",\n  \"Qt apps\": \"Qt-Apps\",\n  \"Color picker\": \"Farbwähler\",\n  \"Interface\": \"Schnittstelle\",\n  \"Tint app icons\": \"App-Symbole einfärben\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Wähle die Sprache für die Benutzeroberfläche.\\n\\\"Auto\\\" verwendet deine System-Lokalisierung.\",\n  \"Show quote\": \"Zitat anzeigen\",\n  \"Local Ollama model | %1\": \"Lokales Ollama-Modell | %1\",\n  \"Show clock\": \"Uhr anzeigen\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Verwendung: <tt>%1superpaste ANZAHL[i]</tt>\\nGib <tt>i</tt> an, wenn du Bilder möchtest\\nBeispiele:\\n<tt>%1superpaste 4i</tt> für die letzten 4 Bilder\\n<tt>%1superpaste 7</tt> für die letzten 7 Einträge\",\n  \"Audio\": \"Audio\",\n  \"Corner style\": \"Eckenstil\",\n  \"No media\": \"Keine Medien\",\n  \"Unknown function call: %1\": \"Unbekannter Funktionsaufruf: %1\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Online | %1s Modell | Liefert schnelle, reaktionsschnelle und gut formatierte Antworten. Nachteile: nicht sehr eifrig, Dinge zu tun; könnte unbekannte Funktionsaufrufe erfinden\",\n  \"Volume\": \"Lautstärke\",\n  \"Medium\": \"Mittel\",\n  \"Copy code\": \"Code kopieren\",\n  \"Exceeded max allowed\": \"Maximaler Wert überschritten\",\n  \"Keep right sidebar loaded\": \"Rechte Seitenleiste geladen halten\",\n  \"Left\": \"Links\",\n  \"High\": \"Hoch\",\n  \"Rect\": \"Rechteck\",\n  \"Lap\": \"Runde\",\n  \"Clear\": \"Löschen\",\n  \"Screen snip\": \"Bildschirmausschnitt\",\n  \"Reset\": \"Zurücksetzen\",\n  \"Back\": \"Zurück\",\n  \"Dark/Light toggle\": \"Dunkel/Hell umschalten\",\n  \"12h am/pm\": \"12h am/pm\",\n  \"Download complete\": \"Download abgeschlossen\",\n  \"Enable blur\": \"Unschärfe aktivieren\",\n  \"Second hand\": \"Sekundenzeiger\",\n  \"Bar & screen\": \"Leiste & Bildschirm\",\n  \"Discharging:\": \"Entladung:\",\n  \"Up %1\": \"Hoch %1\",\n  \"Low\": \"Niedrig\",\n  \"Hour hand\": \"Stundenzeiger\",\n  \"Clear chat history\": \"Chat-Verlauf löschen\",\n  \"Fruit Salad\": \"Fruchtsalat\",\n  \"%1 Safe Storage\": \"%1 Sicherer Speicher\",\n  \"Hibernate\": \"Ruhezustand\",\n  \"Delete\": \"Löschen\",\n  \"OK\": \"OK\",\n  \"Settings\": \"Einstellungen\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"Dies ist normalerweise sicher und wird ohnehin für deinen Browser und die AI-Seitenleiste benötigt\\nHauptsächlich nützlich für diejenigen, die eine Sperre beim Start verwenden, anstatt eines Display-Managers, der das übernimmt (GDM, SDDM, etc.)\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Hyprlock verwenden (anstatt Quickshell)\",\n  \"Crosshair code (in Valorant's format)\": \"Fadenkreuz-Code (im Valorant-Format)\",\n  \"Silent\": \"Stumm\",\n  \"Useless buttons\": \"Nutzlose Buttons\",\n  \"Hover to reveal\": \"Bewegen zum Anzeigen\",\n  \"Wallpaper & Colors\": \"Hintergrundbild & Farben\",\n  \"Auto\": \"Auto\",\n  \"Visibility\": \"Sichtbarkeit\",\n  \"Shell & utilities\": \"Shell & Utilities\",\n  \"Hollow\": \"Hohl\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"System-Dateiauswahl verwenden\\nRechtsklick, um dies zum Standardverhalten zu machen\",\n  \"On-screen display\": \"Bildschirmanzeige\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Search wallpapers\": \"Hintergrundbilder suchen\",\n  \"Mic toggle\": \"Mikrofon umschalten\",\n  \"Input\": \"Eingabe\",\n  \"Also unlock keyring\": \"Auch Schlüsselbund entsperren\",\n  \"Configuration\": \"Konfiguration\",\n  \"Keep system awake\": \"System wach halten\",\n  \"Unknown command:\": \"Unbekannter Befehl:\",\n  \"Anime boorus\": \"Anime-Boorus\",\n  \"To Do:\": \"Aufgaben:\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Verwendet Gemini, um das Hintergrundbild zu kategorisieren und wählt dann ein Preset basierend darauf aus.\\nDu musst zuerst den Gemini-API-Schlüssel in der linken Seitenleiste festlegen.\\nBilder werden für die Leistung herunterskaliert, aber nur zur Sicherheit,\\nwähle keine Hintergrundbilder mit sensiblen Informationen aus.\",\n  \"Bottom\": \"Unten\",\n  \"Clear the current list of images\": \"Aktuelle Bilderliste löschen\",\n  \"Sunrise\": \"Sonnenaufgang\",\n  \"Show app icons\": \"App-Symbole anzeigen\",\n  \"Format\": \"Format\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Stelle sicher, dass dein Player MPRIS-Unterstützung hat\\noder versuche, die doppelte Player-Filterung zu deaktivieren\",\n  \"Pause\": \"Pause\",\n  \"Desktop\": \"Desktop\",\n  \"Conflicts with the shell's system tray implementation\": \"Kollidiert mit der System-Tray-Implementierung der Shell\",\n  \"Your package manager is running\": \"Dein Paket-Manager läuft\",\n  \"Conflicts with the shell's notification implementation\": \"Kollidiert mit der Benachrichtigungsimplementierung der Shell\",\n  \"Unknown Album\": \"Unbekanntes Album\",\n  \"Pick wallpaper image on your system\": \"Hintergrundbild auf deinem System auswählen\",\n  \"Used:\": \"Verwendet:\",\n  \"Cheat sheet\": \"Kurzreferenz\",\n  \"Clock style\": \"Uhr-Stil\",\n  \"No audio source\": \"Keine Audioquelle\",\n  \"Paired\": \"Gepaart\",\n  \"Documentation\": \"Dokumentation\",\n  \"No\": \"Nein\",\n  \"Pills\": \"Pillen\",\n  \"Thought\": \"Gedanke\",\n  \"When this is off you'll have to click\": \"Wenn dies aus ist, musst du klicken\",\n  \"Select output device\": \"Ausgabegerät auswählen\",\n  \"Logout\": \"Abmelden\",\n  \"Tip: Close a window with Super+Q\": \"Tipp: Fenster mit Super+Q schließen\",\n  \"Finished tasks will go here\": \"Fertige Aufgaben werden hier angezeigt\",\n  \"Terminal: Harmony (%)\": \"Terminal: Harmonie (%)\",\n  \"Corner open\": \"Ecke öffnen\",\n  \"Shell conflicts killer\": \"Shell-Konflikt-Killer\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Saubere Sachen | Ausgezeichnete Qualität, kein NSFW\",\n  \"Scroll to change volume\": \"Scrollen zum Ändern der Lautstärke\",\n  \"Wind\": \"Wind\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API-Schlüssel ist gesetzt\\nÄndern mit /key DEIN_API_SCHLUESSEL\",\n  \"Neutral\": \"Neutral\",\n  \"12h AM/PM\": \"12h AM/PM\",\n  \"Number show delay when pressing Super (ms)\": \"Anzeigeverzögerung für Zahlen beim Drücken von Super (ms)\",\n  \"Fill\": \"Füllen\",\n  \"Always show numbers\": \"Zahlen immer anzeigen\",\n  \"Dot\": \"Punkt\",\n  \"Provider set to\": \"Anbieter gesetzt auf\",\n  \"Unknown Title\": \"Unbekannter Titel\",\n  \"Anime\": \"Anime\",\n  \"Refreshing (manually triggered)\": \"Aktualisiere (manuell ausgelöst)\",\n  \"Dock\": \"Dock\",\n  \"Require password to power off/restart\": \"Passwort für Ausschalten/Neustart erforderlich\",\n  \"Line\": \"Linie\",\n  \"Weather\": \"Wetter\",\n  \"All-rounder | Good quality, decent quantity\": \"Allrounder | Gute Qualität, anständige Menge\",\n  \"Scale (%)\": \"Skalierung (%)\",\n  \"Copy\": \"Kopieren\",\n  \"Usage\": \"Verwendung\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Tippe /key, um mit Online-Modellen zu beginnen\\nStrg+O, um die Seitenleiste zu erweitern\\nStrg+P, um die Seitenleiste in ein Fenster zu lösen\",\n  \"Set the tool to use for the model.\": \"Das zu verwendende Tool für das Modell festlegen.\",\n  \"Disable tools\": \"Tools deaktivieren\",\n  \"Connect\": \"Verbinden\",\n  \"Allow NSFW\": \"NSFW erlauben\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Registrierung fehlgeschlagen. Bitte manuell mit dem Befehl <tt>warp-cli</tt> überprüfen\",\n  \"Time to full:\": \"Zeit bis voll:\",\n  \"Session\": \"Sitzung\",\n  \"Services\": \"Dienste\",\n  \"Nothing here!\": \"Nichts hier!\",\n  \"Overview\": \"Übersicht\",\n  \"Random: osu! seasonal\": \"Zufällig: osu! saisonal\",\n  \"If you want to somehow use fingerprint unlock...\": \"Wenn du irgendwie die Fingerabdruck-Entsperrung verwenden möchtest...\",\n  \"Minute hand\": \"Minutenzeiger\",\n  \"Notifications\": \"Benachrichtigungen\",\n  \"Enable if you want clocks to show seconds accurately\": \"Aktivieren, wenn Uhren Sekunden genau anzeigen sollen\",\n  \"Timer\": \"Timer\",\n  \"Quote settings\": \"Zitat-Einstellungen\",\n  \"System prompt\": \"System-Prompt\",\n  \"Classic\": \"Klassisch\",\n  \"Close\": \"Schließen\",\n  \"Disconnect\": \"Trennen\",\n  \"Go to source (%1)\": \"Zur Quelle gehen (%1)\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Rechtsklick zum Konfigurieren\",\n  \"Forget\": \"Vergessen\",\n  \"Output\": \"Ausgabe\",\n  \"Date style\": \"Datums-Stil\",\n  \"System\": \"System\",\n  \"Usage: %1tool TOOL_NAME\": \"Verwendung: %1tool TOOL_NAME\",\n  \"Workspaces\": \"Arbeitsbereiche\",\n  \"Calendar\": \"Kalender\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Anleitung**: Bei Mistral-Konto anmelden, zu Schlüsseln in der Seitenleiste gehen, auf Neuen Schlüssel erstellen klicken\",\n  \"Volume limit\": \"Lautstärkegrenze\",\n  \"Sunset\": \"Sonnenuntergang\",\n  \"Dial style\": \"Zifferblatt-Stil\",\n  \"Hi there! First things first...\": \"Hallo! Erst mal das Wichtigste...\",\n  \"Save chat to %1\": \"Chat nach %1 speichern\",\n  \"Security\": \"Sicherheit\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Gesamte Token-Anzahl\\nEingabe: %1\\nAusgabe: %2\",\n  \"Cancel wallpaper selection\": \"Hintergrundbild-Auswahl abbrechen\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Bitte aufladen!\\nAutomatische Suspendierung bei %1\",\n  \"Terminal: Harmonize threshold\": \"Terminal: Harmonisiere Schwellenwert\",\n  \"Be patient...\": \"Geduld...\",\n  \"Utility buttons\": \"Utility-Buttons\",\n  \"Tonal Spot\": \"Tonal Spot\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Verhindert abrupte Erhöhungen und begrenzt die Lautstärkegrenze\",\n  \"Set the current API provider\": \"Aktuellen API-Anbieter festlegen\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Verbindung fehlgeschlagen. Bitte manuell mit dem Befehl <tt>warp-cli</tt> überprüfen\",\n  \"Networking\": \"Netzwerk\",\n  \"Tint icons\": \"Symbole einfärben\",\n  \"Low battery\": \"Niedrige Batterie\",\n  \"Make icons pinned by default\": \"Symbole standardmäßig anheften\",\n  \"Get the next page of results\": \"Nächste Seite der Ergebnisse abrufen\",\n  \"Invalid API provider. Supported: \\n-\": \"Ungültiger API-Anbieter. Unterstützt: \\n-\",\n  \"Show \\\"Locked\\\" text\": \"\\\"Gesperrt\\\"-Text anzeigen\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Preis**: kostenlos. Datennutzungsrichtlinie variiert je nach deinen OpenRouter-Kontoeinstellungen.\\n\\n**Anleitung**: Bei OpenRouter-Konto anmelden, zu Schlüsseln im oberen rechten Menü gehen, auf API-Schlüssel erstellen klicken\",\n  \"Not visible to model\": \"Nicht sichtbar für Modell\",\n  \"Lock screen\": \"Bildschirm sperren\",\n  \"Save to Downloads\": \"In Downloads speichern\",\n  \"Expressive\": \"Ausdrucksvoll\",\n  \"Suspend at\": \"Suspendieren bei\",\n  \"Jump to current month\": \"Zum aktuellen Monat springen\",\n  \"Bold\": \"Fett\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Nur Waifus | Ausgezeichnete Qualität, begrenzte Menge\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Klicken zum Umschalten zwischen Hell/Dunkel-Modus\\n(wird angewendet, wenn Hintergrundbild gewählt wird)\",\n  \"Visualize region\": \"Region visualisieren\",\n  \"Quote\": \"Zitat\",\n  \"Sleep\": \"Schlaf\",\n  \"Hit \\\"/\\\" to search\": \"Drücke \\\"/\\\" zum Suchen\",\n  \"Hug\": \"Umarmung\",\n  \"Report a Bug\": \"Fehler melden\",\n  \"Precipitation\": \"Niederschlag\",\n  \"Crosshair\": \"Fadenkreuz\",\n  \"Model set to %1\": \"Modell gesetzt auf %1\",\n  \"Rows\": \"Zeilen\",\n  \"Top\": \"Oben\",\n  \"Long break\": \"Lange Pause\",\n  \"Superpaste\": \"Superpaste\",\n  \"Screen round corner\": \"Bildschirm abgerundete Ecke\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Online | Googles Modell\\nNeueres Modell, das langsamer ist als sein Vorgänger, aber höhere Qualität liefern sollte\",\n  \"Rainbow\": \"Regenbogen\",\n  \"Weeb\": \"Weeb\",\n  \"Large language models\": \"Große Sprachmodelle\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Online-Modelle nicht erlaubt\\n\\nKontrolliert durch `policies.ai` Konfigurationsoption\",\n  \"Policies\": \"Richtlinien\",\n  \"Temperature must be between 0 and 2\": \"Temperatur muss zwischen 0 und 2 liegen\",\n  \"Automatic suspend\": \"Automatische Suspendierung\",\n  \"Extra wallpaper zoom (%)\": \"Zusätzlicher Hintergrundbild-Zoom (%)\",\n  \"GitHub\": \"GitHub\",\n  \"%1 | Right-click to configure\": \"%1 | Rechtsklick zum Konfigurieren\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**Preis**: Kostenloser Tarif mit begrenzten Raten verfügbar. Siehe https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Anleitung**: Erstelle einen GitHub Personal Access Token mit Models-Berechtigung, dann hier als API-Schlüssel festlegen\\n\\n**Hinweis**: Um dies zu verwenden, musst du den Temperatur-Parameter auf 1 setzen\",\n  \"Edit directory\": \"Verzeichnis bearbeiten\",\n  \"Action\": \"Aktion\",\n  \"Search\": \"Suchen\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Tipp: Rechtsklick auf eine Gruppe\\nerweitert sie ebenfalls\",\n  \"Bar\": \"Leiste\",\n  \"Show regions of potential interest\": \"Regionen von potenziellem Interesse anzeigen\",\n  \"Clipboard\": \"Zwischenablage\",\n  \"Stopwatch\": \"Stoppuhr\",\n  \"Enter text to translate...\": \"Text zum Übersetzen eingeben...\",\n  \"App\": \"App\",\n  \"Sides\": \"Seiten\",\n  \"No active player\": \"Kein aktiver Player\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Nicht alle Optionen sind in dieser App verfügbar. Du solltest auch die Konfigurationsdatei überprüfen, indem du auf den \\\"Konfigurationsdatei\\\"-Button in der oberen linken Ecke klickst oder %1 manuell öffnest.\",\n  \"There might be a download in progress\": \"Möglicherweise läuft gerade ein Download\",\n  \"Math result\": \"Mathematik-Ergebnis\",\n  \"Fidelity\": \"Wiedergabetreue\",\n  \"Prefixes\": \"Präfixe\",\n  \"Terminal\": \"Terminal\",\n  \"Incorrect password\": \"Falsches Passwort\",\n  \"Line-separated\": \"Zeilengetrennt\",\n  \"Always\": \"Immer\",\n  \"☕ Break: %1 minutes\": \"☕ Pause: %1 Minuten\",\n  \"Depends on sidebars\": \"Hängt von Seitenleisten ab\",\n  \"Tool set to: %1\": \"Tool gesetzt auf: %1\",\n  \"Save chat\": \"Chat speichern\",\n  \"Crosshair overlay\": \"Fadenkreuz-Overlay\",\n  \"Keybinds\": \"Tastenkürzel\",\n  \"Launch\": \"Starten\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Könnte besser sein, wenn du viele Tippfehler machst,\\naber Ergebnisse können seltsam sein und funktionieren möglicherweise nicht mit Akronymen\\n(z.B. könnte \\\"GIMP\\\" dir nicht das Malprogramm geben)\",\n  \"Choose model\": \"Modell wählen\",\n  \"Base URL\": \"Basis-URL\",\n  \"Float\": \"Schweben\",\n  \"Wallpaper parallax\": \"Hintergrundbild-Parallaxe\",\n  \"Invalid arguments. Must provide `command`.\": \"Ungültige Argumente. Muss `command` bereitstellen.\",\n  \"Fully charged\": \"Vollständig geladen\",\n  \"Earbang protection\": \"Ohrschutz\",\n  \"Low warning\": \"Niedrige Warnung\",\n  \"Advanced\": \"Erweitert\",\n  \"Scroll to change brightness\": \"Scrollen zum Ändern der Helligkeit\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Folgenden System-Prompt geladen\\n\\n---\\n\\n%1\",\n  \"Show next time\": \"Nächstes Mal anzeigen\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Aktuelles Tool: %1\\nSetze es mit %2tool TOOL\",\n  \"Unread indicator: show count\": \"Ungelesen-Indikator: Anzahl anzeigen\",\n  \"Press Super+G to toggle appearance\": \"Drücke Super+G zum Umschalten des Erscheinungsbilds\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Das hat nicht funktioniert. Tipps:\\n- Überprüfe deine Tags und NSFW-Einstellungen\\n- Wenn du keinen Tag im Kopf hast, tippe eine Seitennummer\",\n  \"Dots\": \"Dots\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Volume mixer\": \"Lautstärkemixer\",\n  \"Config file\": \"Konfigurationsdatei\",\n  \"API key set for %1\": \"API-Schlüssel gesetzt für %1\",\n  \"Online via %1 | %2's model\": \"Online über %1 | %2s Modell\",\n  \"Shell command\": \"Shell-Befehl\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Solche Regionen könnten Bilder oder Teile des Bildschirms sein, die eine gewisse Eingrenzung haben.\\nMöglicherweise nicht immer genau.\\nDies wird mit einem lokal ausgeführten Bildverarbeitungsalgorithmus durchgeführt und keine KI wird verwendet.\",\n  \"Reload Hyprland & Quickshell\": \"Hyprland & Quickshell neu laden\",\n  \"Resources\": \"Ressourcen\",\n  \"Brightness\": \"Helligkeit\",\n  \"Unknown\": \"Unbekannt\",\n  \"Polling interval (ms)\": \"Abfrageintervall (ms)\",\n  \"Lock\": \"Sperren\",\n  \"Thinking\": \"Denken\",\n  \"Approve\": \"Genehmigen\",\n  \"Unfinished\": \"Unvollständig\",\n  \"Random: Konachan\": \"Zufällig: Konachan\",\n  \"Connected\": \"Verbunden\",\n  \"Wallpaper safety enforced\": \"Hintergrundbild-Sicherheit erzwungen\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Ungültige Argumente. Muss `key` und `value` bereitstellen.\",\n  \"24h\": \"24h\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Ermöglicht es, Seitenleisten durch Klicken oder Bewegen über Bildschirmecken zu öffnen, unabhängig von der Leistenposition\",\n  \"Bar style\": \"Leisten-Stil\",\n  \"Load:\": \"Last:\",\n  \"Open file link\": \"Dateilink öffnen\",\n  \"Ignored if terminal theming is not enabled\": \"Ignoriert, wenn Terminal-Theming nicht aktiviert ist\",\n  \"Shutdown\": \"Herunterfahren\",\n  \"Hour marks\": \"Stundenmarkierungen\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Zufälliger osu! saisonaler Hintergrund\\nBild wird in ~/Pictures/Wallpapers gespeichert\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Online | Googles Modell\\nSchnell, kann Suchen nach aktuellen Informationen durchführen\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Aktuelles Modell: %1\\nSetze es mit %2model MODELL\",\n  \"Select input device\": \"Eingabegerät auswählen\",\n  \"Connect to Wi-Fi\": \"Mit WLAN verbinden\",\n  \"... and %1 more\": \"... und %1 weitere\",\n  \"Cookie clock settings\": \"Cookie-Uhr-Einstellungen\",\n  \"Brightness and volume\": \"Helligkeit und Lautstärke\",\n  \"Choose file\": \"Datei wählen\",\n  \"Invalid model. Supported: \\n```\": \"Ungültiges Modell. Unterstützt: \\n```\",\n  \"Task Manager\": \"Task-Manager\",\n  \"Charging:\": \"Lädt:\",\n  \"Illegal increment\": \"Ungültige Erhöhung\",\n  \"Total:\": \"Gesamt:\",\n  \"or\": \"oder\",\n  \"Battery\": \"Batterie\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Timeout-Dauer (wenn nicht durch Benachrichtigung definiert) (ms)\",\n  \"Cancel\": \"Abbrechen\",\n  \"Locked\": \"Gesperrt\",\n  \"Temperature: %1\": \"Temperatur: %1\",\n  \"Hover to trigger\": \"Bewegen zum Auslösen\",\n  \"Command rejected by user\": \"Befehl vom Benutzer abgelehnt\",\n  \"User agent (for services that require it)\": \"User-Agent (für Dienste, die es benötigen)\",\n  \"Saved to %1\": \"Gespeichert nach %1\",\n  \"Emojis\": \"Emojis\",\n  \"Color generation\": \"Farbgenerierung\",\n  \"Welcome app\": \"Willkommens-App\",\n  \"Humidity\": \"Luftfeuchtigkeit\",\n  \"Page %1\": \"Seite %1\",\n  \"Feels like %1\": \"Fühlt sich an wie %1\",\n  \"Distro\": \"Distro\",\n  \"Transparency\": \"Transparenz\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 Aufgaben\",\n  \"Markdown test\": \"Markdown-Test\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Ungültiges Tool. Unterstützte Tools:\\n- %1\",\n  \"No notifications\": \"Keine Benachrichtigungen\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Der Hentai eine | Große Menge, viel NSFW, Qualität variiert stark\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Resume\": \"Fortsetzen\",\n  \"Work safety\": \"Arbeitssicherheit\",\n  \"Temperature\\nChange with /temp VALUE\": \"Temperatur\\nÄndern mit /temp WERT\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Vordergrund-Verstärkung (%)\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Nachtlicht | Rechtsklick zum Umschalten des Auto-Modus\",\n  \"Closet\": \"Schrank\",\n  \"Yes\": \"Ja\",\n  \"Columns\": \"Spalten\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Um einen API-Schlüssel zu setzen, übergebe ihn mit dem Befehl %4\\n\\nUm den Schlüssel anzuzeigen, übergebe \\\"get\\\" mit dem Befehl<br/>\\n\\n### Für %1:\\n\\n**Link**: %2\\n\\n%3\",\n  \"Kill conflicting programs?\": \"Konfliktierende Programme beenden?\",\n  \"For storing API keys and other sensitive information\": \"Zum Speichern von API-Schlüsseln und anderen sensiblen Informationen\",\n  \"Reject\": \"Ablehnen\",\n  \"Set API key\": \"API-Schlüssel setzen\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Hinweise für Zerochan:\\n- Du musst eine Farbe eingeben\\n- Setze deinen Zerochan-Benutzernamen in der Konfigurationsoption `sidebar.booru.zerochan.username`. Du [könntest gesperrt werden, wenn du das nicht tust](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Content\": \"Inhalt\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"Vertical\": \"Vertikal\",\n  \"Pick a wallpaper\": \"Hintergrundbild wählen\",\n  \"Load chat from %1\": \"Chat von %1 laden\",\n  \"Launch on startup\": \"Beim Start starten\",\n  \"Add\": \"Hinzufügen\",\n  \"Style: general\": \"Stil: allgemein\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Levenshtein-Distanz-basierten Algorithmus anstatt Fuzzy verwenden\",\n  \"Shell & utilities theming must also be enabled\": \"Shell & Utilities-Theming muss ebenfalls aktiviert sein\",\n  \"Workspace\": \"Arbeitsbereich\",\n  \"Translator\": \"Übersetzer\",\n  \"Free:\": \"Frei:\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Lange Pause: %1 Minuten\",\n  \"Value scroll\": \"Wert-Scrollen\",\n  \"Bar position\": \"Leisten-Position\",\n  \"Language\": \"Sprache\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Aktueller API-Endpunkt: %1\\nSetze ihn mit %2mode ANBIETER\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Denke daran, dass man auf den meisten Geräten immer die Ein-/Aus-Taste gedrückt halten kann, um einen erzwungenen Ausschaltvorgang durchzuführen\\nDies macht es nur ein kleines bisschen schwieriger, dass Unfälle passieren\",\n  \"AI\": \"KI\",\n  \"Task description\": \"Aufgabenbeschreibung\",\n  \"Add task\": \"Aufgabe hinzufügen\",\n  \"Donate\": \"Spenden\",\n  \"Disable NSFW content\": \"NSFW-Inhalt deaktivieren\",\n  \"Set the system prompt for the model.\": \"Den System-Prompt für das Modell festlegen.\",\n  \"Done\": \"Fertig\",\n  \"Focus\": \"Fokus\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"Shell-Konfigurationsdatei öffnen.\\nFalls der Button nicht funktioniert oder nicht in deinem bevorzugten Editor öffnet,\\nkannst du ~/.config/illogical-impulse/config.json manuell öffnen\",\n  \"View Markdown source\": \"Markdown-Quelle anzeigen\",\n  \"Border\": \"Rahmen\",\n  \"Temperature set to %1\": \"Temperatur gesetzt auf %1\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"Online | Googles Modell\\nGoogles modernstes Mehrzweckmodell, das sich bei Programmierung und komplexen Denkaufgaben auszeichnet.\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Nachricht an das Modell... \\\"%1\\\" für Befehle\",\n  \"Translation goes here...\": \"Übersetzung kommt hier hin...\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Wenn aktiviert, hält den Inhalt der rechten Seitenleiste geladen, um die Verzögerung beim Öffnen zu reduzieren,\\nzum Preis von etwa 15 MB konstantem RAM-Verbrauch. Die Bedeutung der Verzögerung hängt von der Leistung deines Systems ab.\\nEin benutzerdefiniertes Kernel wie linux-cachyos könnte helfen\",\n  \"For desktop wallpapers | Good quality\": \"Für Desktop-Hintergrundbilder | Gute Qualität\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Fokus: %1 Minuten\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Der aktuelle System-Prompt ist\\n\\n---\\n\\n%1\",\n  \"About\": \"Über\",\n  \"Quick\": \"Schnell\",\n  \"General\": \"Allgemein\",\n  \"UV Index\": \"UV-Index\",\n  \"Force dark mode in terminal\": \"Dunklen Modus im Terminal erzwingen\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Region ziehen oder klicken • LMB: Kopieren • RMB: Bearbeiten\",\n  \"%1 characters\": \"%1 Zeichen\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Preis**: kostenlos. Daten werden für Training verwendet.\\n\\n**Anleitung**: Bei Google-Konto anmelden, AI Studio erlauben, Google Cloud-Projekt zu erstellen oder was auch immer es fragt, zurückgehen und auf API-Schlüssel abrufen klicken\",\n  \"Monochrome\": \"Monochrom\",\n  \"Details\": \"Details\",\n  \"Issues\": \"Probleme\",\n  \"Keyboard toggle\": \"Tastatur umschalten\",\n  \"Might look ass. Unsupported.\": \"Könnte schlecht aussehen. Nicht unterstützt.\",\n  \"Download\": \"Herunterladen\",\n  \"%1 does not require an API key\": \"%1 erfordert keinen API-Schlüssel\",\n  \"Style & wallpaper\": \"Stil & Hintergrundbild\",\n  \"Second precision\": \"Sekunden-Präzision\",\n  \"Group style\": \"Gruppen-Stil\",\n  \"Break\": \"Pause\",\n  \"Run\": \"Ausführen\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Viel Spaß! Du kannst die Willkommens-App jederzeit mit <tt>Super+Shift+Alt+/</tt> wieder öffnen. Um die Einstellungs-App zu öffnen, drücke <tt>Super+I</tt>\",\n  \"Interface Language\": \"Schnittstellen-Sprache\",\n  \"Game mode\": \"Spielmodus\",\n  \"Usage: %1save CHAT_NAME\": \"Verwendung: %1save CHAT_NAME\",\n  \"Thin\": \"Dünn\",\n  \"Light\": \"Hell\",\n  \"When not fullscreen\": \"Wenn nicht Vollbild\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Befehle, Konfigurationen bearbeiten, suchen.\\nBenötigt einen zusätzlichen Zug, um in den Suchmodus zu wechseln, falls nötig\",\n  \"Privacy Policy\": \"Datenschutzerklärung\",\n  \"Timeout (ms)\": \"Timeout (ms)\",\n  \"Allow NSFW content\": \"NSFW-Inhalt erlauben\",\n  \"Edit\": \"Bearbeiten\",\n  \"Digits in the middle\": \"Ziffern in der Mitte\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Online | Googles Modell\\nEin Gemini 2.5 Flash-Modell, optimiert für Kosteneffizienz und hohen Durchsatz.\",\n  \"Weather Service\": \"Wetterdienst\",\n  \"Background\": \"Hintergrund\",\n  \"Pick random from this folder\": \"Zufällig aus diesem Ordner wählen\",\n  \"Pressure\": \"Druck\",\n  \"Save\": \"Speichern\",\n  \"Run command\": \"Befehl ausführen\",\n  \"Time to empty:\": \"Zeit bis leer:\",\n  \"Place at bottom\": \"Unten platzieren\",\n  \"Switched to search mode. Continue with the user's request.\": \"Zum Suchmodus gewechselt. Mit der Anfrage des Benutzers fortfahren.\",\n  \"Performance Profile toggle\": \"Leistungsprofil umschalten\",\n  \"Sidebars\": \"Seitenleisten\",\n  \"Usage: %1load CHAT_NAME\": \"Verwendung: %1load CHAT_NAME\",\n  \"Auto styling with Gemini\": \"Automatisches Styling mit Gemini\",\n  \"Simple digital\": \"Einfach digital\",\n  \"No API key set for %1\": \"Kein API-Schlüssel gesetzt für %1\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Tags eingeben oder \\\"%1\\\" für Befehle\",\n  \"%1 queries pending\": \"%1 Abfragen ausstehend\",\n  \"Discussions\": \"Diskussionen\",\n  \"Tray\": \"Tray\",\n  \"Numbers\": \"Zahlen\",\n  \"Intelligence\": \"Intelligenz\",\n  \"Open network portal\": \"Netzwerkportal öffnen\",\n  \"<i>No further instruction provided</i>\": \"<i>Keine weiteren Anweisungen bereitgestellt</i>\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"Sprache nicht aufgeführt oder unvollständige Übersetzungen?\\nDu kannst wählen, Übersetzungen dafür mit Gemini zu generieren.\\n1. Öffne die linke Seitenleiste mit Super+A, setze Modell auf Gemini (falls noch nicht)\\n2. Tippe /key, drücke Enter und folge den Anweisungen\\n3. Tippe /key DEIN_API_SCHLUESSEL\\n4. Tippe den Locale-Code deiner Sprache unten ein und drücke Generieren\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Locale-Code, z.B. fr_FR, de_DE, zh_CN...\",\n  \"Select language\": \"Sprache auswählen\",\n  \"Generate translation with Gemini\": \"Übersetzung mit Gemini generieren\",\n  \"Generating...\\nDon't close this window!\": \"Generiere...\\nSchließe dieses Fenster nicht!\",\n  \"Generate\\nTypically takes 2 minutes\": \"Generieren\\nDauert typischerweise 2 Minuten\",\n  \"Use system file picker\": \"System-Dateiauswahl verwenden\",\n  \"Wallpaper selector\": \"Hintergrundbild-Auswahl\",\n  \"but force at absolute corner\": \"aber erzwinge an absoluter Ecke\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"Wenn die vorherige Option aus und diese an ist,\\nkannst du immer noch über das Ende der Ecke bewegen, um die Seitenleiste zu öffnen,\\nund der verbleibende Bereich kann für Lautstärke/Helligkeit-Scrollen verwendet werden\",\n  \"Copy path\": \"Pfad kopieren\",\n  \"Windows\": \"Fenster\",\n  \"Regenerate\": \"Neu generieren\",\n  \"Microphone\": \"Mikrofon\",\n  \"Unmuted\": \"Nicht stumm\",\n  \"System sound\": \"System-Sound\",\n  \"Enable now\": \"Jetzt aktivieren\",\n  \"Night Light\": \"Nachtlicht\",\n  \"Show aim lines\": \"Ziellinien anzeigen\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Warum das cool ist:\\nFür Werte ungleich 0 wird es nicht ausgelöst, wenn du die\\nBildschirmecke entlang der horizontalen Kante erreichst, aber es wird ausgelöst, wenn\\ndu es entlang der vertikalen Kante tust\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"Bitte aufladen!\\nAutomatische Suspendierung bei %1%\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"Beispiel-Anwendungsfall: Eroge auf einem Arbeitsbereich, dunkles Discord-Fenster auf einem anderen\",\n  \"Couldn't recognize music\": \"Musik konnte nicht erkannt werden\",\n  \"Automatic\": \"Automatisch\",\n  \"Hint target regions\": \"Zielregionen-Hinweise\",\n  \"Devices\": \"Geräte\",\n  \"Eye protection\": \"Augenschutz\",\n  \"Japanese\": \"Japanisch\",\n  \"Layers\": \"Ebenen\",\n  \"Listening...\": \"Hören...\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"LMB zum Aktivieren/Deaktivieren\\nRMB zum Umschalten der Größe\\nScrollen zum Tauschen der Position\",\n  \"Identify Music\": \"Musik erkennen\",\n  \"Quick toggles\": \"Schnellumschalter\",\n  \"Hide sussy/anime wallpapers\": \"Verdächtige/Anime-Hintergrundbilder ausblenden\",\n  \"Android\": \"Android\",\n  \"Show\": \"Anzeigen\",\n  \"Muted\": \"Stumm\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Audio-Eingabe | Rechtsklick für Lautstärkemixer & Geräteauswahl\",\n  \"Region selector (screen snipping/Google Lens)\": \"Regionsauswahl (Bildschirmausschnitt/Google Lens)\",\n  \"Total duration timeout (s)\": \"Gesamtdauer-Timeout (s)\",\n  \"Music Recognition\": \"Musikerkennung\",\n  \"Night Light | Right-click to configure\": \"Nachtlicht | Rechtsklick zum Konfigurieren\",\n  \"Anti-flashbang (experimental)\": \"Anti-Flashbang (experimentell)\",\n  \"Digital clock settings\": \"Digitale Uhr-Einstellungen\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Könnten Bilder oder Teile des Bildschirms sein, die eine gewisse Eingrenzung haben.\\nMöglicherweise nicht immer genau.\\nDies wird mit einem lokal ausgeführten Bildverarbeitungsalgorithmus durchgeführt und keine KI wird verwendet.\",\n  \"Polling interval (m)\": \"Abfrageintervall (m)\",\n  \"Inactive\": \"Inaktiv\",\n  \"Authentication\": \"Authentifizierung\",\n  \"Full warning\": \"Volle Warnung\",\n  \"Power Profile\": \"Energieprofil\",\n  \"Content region\": \"Inhaltsregion\",\n  \"Internet\": \"Internet\",\n  \"Record\": \"Aufnehmen\",\n  \"Circle selection\": \"Kreisauswahl\",\n  \"Edit quick toggles\": \"Schnellumschalter bearbeiten\",\n  \"Virtual Keyboard\": \"Virtuelle Tastatur\",\n  \"Music Recognized\": \"Musik erkannt\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Make sure you have songrec installed\": \"Stelle sicher, dass songrec installiert ist\",\n  \"Dark Mode\": \"Dunkler Modus\",\n  \"No device\": \"Kein Gerät\",\n  \"Animate time change\": \"Zeitänderung animieren\",\n  \"It may take a few seconds to update\": \"Es kann einige Sekunden dauern, bis es aktualisiert wird\",\n  \"Polling interval (s)\": \"Abfrageintervall (s)\",\n  \"Perhaps what you're listening to is too niche\": \"Vielleicht ist das, was du hörst, zu speziell\",\n  \"Fahrenheit unit\": \"Fahrenheit-Einheit\",\n  \"Sliders\": \"Schieberegler\",\n  \"Roman\": \"Römisch\",\n  \"Number style\": \"Zahlen-Stil\",\n  \"Intensity\": \"Intensität\",\n  \"Google Lens\": \"Google Lens\",\n  \"Circle\": \"Kreis\",\n  \"Hide clipboard images copied from sussy sources\": \"Zwischenablage-Bilder von verdächtigen Quellen ausblenden\",\n  \"Scroll to Bottom\": \"Nach unten scrollen\",\n  \"Enabled\": \"Aktiviert\",\n  \"Nothing\": \"Nichts\",\n  \"Audio input\": \"Audio-Eingabe\",\n  \"with vertical offset\": \"mit vertikalem Versatz\",\n  \"Padding\": \"Abstand\",\n  \"Please unplug the charger\": \"Bitte Ladegerät abziehen\",\n  \"Show notifications\": \"Benachrichtigungen anzeigen\",\n  \"Path copied\": \"Pfad kopiert\",\n  \"On-screen keyboard\": \"Bildschirmtastatur\",\n  \"City name\": \"Stadtname\",\n  \"Click to cycle through power profiles\": \"Klicken zum Durchwechseln der Energieprofile\",\n  \"Recognize music | Right-click to toggle source\": \"Musik erkennen | Rechtsklick zum Umschalten der Quelle\",\n  \"Use old sine wave cookie implementation\": \"Alte Sinuswellen-Cookie-Implementierung verwenden\",\n  \"Rectangular selection\": \"Rechteckauswahl\",\n  \"Audio output\": \"Audio-Ausgabe\",\n  \"Applications\": \"Anwendungen\",\n  \"Circle to Search\": \"Kreis zum Suchen\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Audio-Ausgabe | Rechtsklick für Lautstärkemixer & Geräteauswahl\",\n  \"Enable GPS based location\": \"GPS-basierte Standortbestimmung aktivieren\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"Du musst zuerst deinen Gemini-API-Schlüssel eingeben.\\nTippe /key in der Seitenleiste für Anweisungen.\",\n  \"Sounds\": \"Sounds\",\n  \"Active\": \"Aktiv\",\n  \"Keep awake\": \"Wach halten\",\n  \"Auto,\": \"Auto,\",\n  \"Normal\": \"Normal\",\n  \"Force hover open at absolute corner\": \"Hover-Öffnen an absoluter Ecke erzwingen\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Shell-Konfigurationsdatei öffnen\\nAlternativ Rechtsklick zum Kopieren des Pfads\",\n  \"Recognize music\": \"Musik erkennen\",\n  \"Stroke width\": \"Strichstärke\",\n  \"Use varying shapes for password characters\": \"Verschiedene Formen für Passwort-Zeichen verwenden\",\n  \"Battery full\": \"Batterie voll\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/en_US.json",
    "content": "{\n  \"Material cookie\": \"Material cookie\",\n  \"Style: Blurred\": \"Style: Blurred\",\n  \"Unknown device\": \"Unknown device\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\",\n  \"No pending tasks\": \"No pending tasks\",\n  \"Positioning\": \"Positioning\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\",\n  \"Critical warning\": \"Critical warning\",\n  \"Unknown Artist\": \"Unknown Artist\",\n  \"Web search\": \"Web search\",\n  \"Load prompt from %1\": \"Load prompt from %1\",\n  \"Attach a file. Only works with Gemini.\": \"Attach a file. Only works with Gemini.\",\n  \"Reboot\": \"Reboot\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"API key:\\n\\n```txt\\n%1\\n```\",\n  \"Pinned on startup\": \"Pinned on startup\",\n  \"Right\": \"Right\",\n  \"Reboot to firmware settings\": \"Reboot to firmware settings\",\n  \"Automatically hide\": \"Automatically hide\",\n  \"Waiting for response...\": \"Waiting for response...\",\n  \"To Do\": \"To Do\",\n  \"Full\": \"Full\",\n  \"Select Language\": \"Select Language\",\n  \"Password\": \"Password\",\n  \"Bluetooth devices\": \"Bluetooth devices\",\n  \"Enable\": \"Enable\",\n  \"Elements\": \"Elements\",\n  \"Start\": \"Start\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"The popular one | Best quantity, but quality can vary wildly\",\n  \"System uptime:\": \"System uptime:\",\n  \"illogical-impulse Welcome\": \"illogical-impulse Welcome\",\n  \"Code saved to file\": \"Code saved to file\",\n  \"Info\": \"Info\",\n  \"Preferred wallpaper zoom (%)\": \"Preferred wallpaper zoom (%)\",\n  \"Time\": \"Time\",\n  \"Help & Support\": \"Help & Support\",\n  \"Bubble\": \"Bubble\",\n  \"Large images | God tier quality, no NSFW.\": \"Large images | God tier quality, no NSFW.\",\n  \"Dark\": \"Dark\",\n  \"Center clock\": \"Center clock\",\n  \"Search, calculate or run\": \"Search, calculate or run\",\n  \"Region height\": \"Region height\",\n  \"Load chat\": \"Load chat\",\n  \"Gives the model search capabilities (immediately)\": \"Gives the model search capabilities (immediately)\",\n  \"Depends on workspace\": \"Depends on workspace\",\n  \"Blurred style\": \"Blurred style\",\n  \"Screenshot tool\": \"Screenshot tool\",\n  \"Enter password\": \"Enter password\",\n  \"Search the web\": \"Search the web\",\n  \"Local only\": \"Local only\",\n  \"at\": \"at\",\n  \"Math\": \"Math\",\n  \"Consider plugging in your device\": \"Consider plugging in your device\",\n  \"Workspaces shown\": \"Workspaces shown\",\n  \"Place the corners to trigger at the bottom\": \"Place the corners to trigger at the bottom\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"No API key\\nSet it with /key YOUR_API_KEY\",\n  \"Auto (System)\": \"Auto (System)\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\",\n  \"Critically low battery\": \"Critically low battery\",\n  \"Open editor\": \"Open editor\",\n  \"%1 notifications\": \"%1 notifications\",\n  \"Region width\": \"Region width\",\n  \"Max allowed increase\": \"Max allowed increase\",\n  \"Enable translator\": \"Enable translator\",\n  \"Constantly rotate\": \"Constantly rotate\",\n  \"Automatically suspends the system when battery is low\": \"Automatically suspends the system when battery is low\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Cannot find a GPS service. Using the fallback method instead.\",\n  \"Qt apps\": \"Qt apps\",\n  \"Color picker\": \"Color picker\",\n  \"Interface\": \"Interface\",\n  \"Tint app icons\": \"Tint app icons\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\",\n  \"Show quote\": \"Show quote\",\n  \"Local Ollama model | %1\": \"Local Ollama model | %1\",\n  \"Show clock\": \"Show clock\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\",\n  \"Audio\": \"Audio\",\n  \"Corner style\": \"Corner style\",\n  \"No media\": \"No media\",\n  \"Unknown function call: %1\": \"Unknown function call: %1\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\",\n  \"Volume\": \"Volume\",\n  \"Gamma\": \"Gamma\",\n  \"Medium\": \"Medium\",\n  \"Copy code\": \"Copy code\",\n  \"Exceeded max allowed\": \"Exceeded max allowed\",\n  \"Keep right sidebar loaded\": \"Keep right sidebar loaded\",\n  \"Left\": \"Left\",\n  \"High\": \"High\",\n  \"Rect\": \"Rect\",\n  \"Lap\": \"Lap\",\n  \"Clear\": \"Clear\",\n  \"Screen snip\": \"Screen snip\",\n  \"Reset\": \"Reset\",\n  \"Back\": \"Back\",\n  \"Dark/Light toggle\": \"Dark/Light toggle\",\n  \"12h am/pm\": \"12h am/pm\",\n  \"Download complete\": \"Download complete\",\n  \"Enable blur\": \"Enable blur\",\n  \"Second hand\": \"Second hand\",\n  \"Bar & screen\": \"Bar & screen\",\n  \"Discharging:\": \"Discharging:\",\n  \"Up %1\": \"Up %1\",\n  \"Low\": \"Low\",\n  \"Hour hand\": \"Hour hand\",\n  \"Clear chat history\": \"Clear chat history\",\n  \"Fruit Salad\": \"Fruit Salad\",\n  \"%1 Safe Storage\": \"%1 Safe Storage\",\n  \"Hibernate\": \"Hibernate\",\n  \"Delete\": \"Delete\",\n  \"OK\": \"OK\",\n  \"Settings\": \"Settings\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Use Hyprlock (instead of Quickshell)\",\n  \"Crosshair code (in Valorant's format)\": \"Crosshair code (in Valorant's format)\",\n  \"Silent\": \"Silent\",\n  \"Useless buttons\": \"Useless buttons\",\n  \"Hover to reveal\": \"Hover to reveal\",\n  \"Wallpaper & Colors\": \"Wallpaper & Colors\",\n  \"Auto\": \"Auto\",\n  \"Visibility\": \"Visibility\",\n  \"Shell & utilities\": \"Shell & utilities\",\n  \"Hollow\": \"Hollow\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Use the system file picker instead\\nRight-click to make this the default behavior\",\n  \"On-screen display\": \"On-screen display\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Search wallpapers\": \"Search wallpapers\",\n  \"Mic toggle\": \"Mic toggle\",\n  \"Input\": \"Input\",\n  \"Also unlock keyring\": \"Also unlock keyring\",\n  \"Configuration\": \"Configuration\",\n  \"Keep system awake\": \"Keep system awake\",\n  \"Unknown command:\": \"Unknown command:\",\n  \"Anime boorus\": \"Anime boorus\",\n  \"To Do:\": \"To Do:\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\",\n  \"Bottom\": \"Bottom\",\n  \"Clear the current list of images\": \"Clear the current list of images\",\n  \"Sunrise\": \"Sunrise\",\n  \"Show app icons\": \"Show app icons\",\n  \"Format\": \"Format\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\",\n  \"Pause\": \"Pause\",\n  \"Desktop\": \"Desktop\",\n  \"Conflicts with the shell's system tray implementation\": \"Conflicts with the shell's system tray implementation\",\n  \"Your package manager is running\": \"Your package manager is running\",\n  \"Conflicts with the shell's notification implementation\": \"Conflicts with the shell's notification implementation\",\n  \"Unknown Album\": \"Unknown Album\",\n  \"Pick wallpaper image on your system\": \"Pick wallpaper image on your system\",\n  \"Used:\": \"Used:\",\n  \"Cheat sheet\": \"Cheat sheet\",\n  \"Clock style\": \"Clock style\",\n  \"No audio source\": \"No audio source\",\n  \"Paired\": \"Paired\",\n  \"Documentation\": \"Documentation\",\n  \"No\": \"No\",\n  \"Pills\": \"Pills\",\n  \"Thought\": \"Thought\",\n  \"When this is off you'll have to click\": \"When this is off you'll have to click\",\n  \"Select output device\": \"Select output device\",\n  \"Logout\": \"Logout\",\n  \"Tip: Close a window with Super+Q\": \"Tip: Close a window with Super+Q\",\n  \"Finished tasks will go here\": \"Finished tasks will go here\",\n  \"Terminal: Harmony (%)\": \"Terminal: Harmony (%)\",\n  \"Corner open\": \"Corner open\",\n  \"Shell conflicts killer\": \"Shell conflicts killer\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Clean stuff | Excellent quality, no NSFW\",\n  \"Scroll to change volume\": \"Scroll to change volume\",\n  \"Wind\": \"Wind\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API key is set\\nChange with /key YOUR_API_KEY\",\n  \"Neutral\": \"Neutral\",\n  \"12h AM/PM\": \"12h AM/PM\",\n  \"Number show delay when pressing Super (ms)\": \"Number show delay when pressing Super (ms)\",\n  \"Fill\": \"Fill\",\n  \"Always show numbers\": \"Always show numbers\",\n  \"Dot\": \"Dot\",\n  \"Provider set to\": \"Provider set to\",\n  \"Unknown Title\": \"Unknown Title\",\n  \"Anime\": \"Anime\",\n  \"Refreshing (manually triggered)\": \"Refreshing (manually triggered)\",\n  \"Dock\": \"Dock\",\n  \"Require password to power off/restart\": \"Require password to power off/restart\",\n  \"Line\": \"Line\",\n  \"Weather\": \"Weather\",\n  \"All-rounder | Good quality, decent quantity\": \"All-rounder | Good quality, decent quantity\",\n  \"Scale (%)\": \"Scale (%)\",\n  \"Copy\": \"Copy\",\n  \"Usage\": \"Usage\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\",\n  \"Set the tool to use for the model.\": \"Set the tool to use for the model.\",\n  \"Disable tools\": \"Disable tools\",\n  \"Connect\": \"Connect\",\n  \"Allow NSFW\": \"Allow NSFW\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\",\n  \"Time to full:\": \"Time to full:\",\n  \"Session\": \"Session\",\n  \"Services\": \"Services\",\n  \"Nothing here!\": \"Nothing here!\",\n  \"Overview\": \"Overview\",\n  \"Random: osu! seasonal\": \"Random: osu! seasonal\",\n  \"If you want to somehow use fingerprint unlock...\": \"If you want to somehow use fingerprint unlock...\",\n  \"Minute hand\": \"Minute hand\",\n  \"Notifications\": \"Notifications\",\n  \"Enable if you want clocks to show seconds accurately\": \"Enable if you want clocks to show seconds accurately\",\n  \"Timer\": \"Timer\",\n  \"Quote settings\": \"Quote settings\",\n  \"System prompt\": \"System prompt\",\n  \"Classic\": \"Classic\",\n  \"Close\": \"Close\",\n  \"Disconnect\": \"Disconnect\",\n  \"Go to source (%1)\": \"Go to source (%1)\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Right-click to configure\",\n  \"Forget\": \"Forget\",\n  \"Output\": \"Output\",\n  \"Date style\": \"Date style\",\n  \"System\": \"System\",\n  \"Usage: %1tool TOOL_NAME\": \"Usage: %1tool TOOL_NAME\",\n  \"Workspaces\": \"Workspaces\",\n  \"Calendar\": \"Calendar\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\",\n  \"Volume limit\": \"Volume limit\",\n  \"Sunset\": \"Sunset\",\n  \"Dial style\": \"Dial style\",\n  \"Hi there! First things first...\": \"Hi there! First things first...\",\n  \"Save chat to %1\": \"Save chat to %1\",\n  \"Security\": \"Security\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Total token count\\nInput: %1\\nOutput: %2\",\n  \"Cancel wallpaper selection\": \"Cancel wallpaper selection\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Please charge!\\nAutomatic suspend triggers at %1\",\n  \"Terminal: Harmonize threshold\": \"Terminal: Harmonize threshold\",\n  \"Be patient...\": \"Be patient...\",\n  \"Utility buttons\": \"Utility buttons\",\n  \"Tonal Spot\": \"Tonal Spot\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Prevents abrupt increments and restricts volume limit\",\n  \"Set the current API provider\": \"Set the current API provider\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\",\n  \"Networking\": \"Networking\",\n  \"Tint icons\": \"Tint icons\",\n  \"Low battery\": \"Low battery\",\n  \"Make icons pinned by default\": \"Make icons pinned by default\",\n  \"Get the next page of results\": \"Get the next page of results\",\n  \"Invalid API provider. Supported: \\n-\": \"Invalid API provider. Supported: \\n-\",\n  \"Show \\\"Locked\\\" text\": \"Show \\\"Locked\\\" text\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\",\n  \"Not visible to model\": \"Not visible to model\",\n  \"Lock screen\": \"Lock screen\",\n  \"Save to Downloads\": \"Save to Downloads\",\n  \"Expressive\": \"Expressive\",\n  \"Suspend at\": \"Suspend at\",\n  \"Jump to current month\": \"Jump to current month\",\n  \"Bold\": \"Bold\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Waifus only | Excellent quality, limited quantity\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\",\n  \"Visualize region\": \"Visualize region\",\n  \"Quote\": \"Quote\",\n  \"Sleep\": \"Sleep\",\n  \"Hit \\\"/\\\" to search\": \"Hit \\\"/\\\" to search\",\n  \"Hug\": \"Hug\",\n  \"Report a Bug\": \"Report a Bug\",\n  \"Precipitation\": \"Precipitation\",\n  \"Crosshair\": \"Crosshair\",\n  \"Model set to %1\": \"Model set to %1\",\n  \"Rows\": \"Rows\",\n  \"Top\": \"Top\",\n  \"Long break\": \"Long break\",\n  \"Superpaste\": \"Superpaste\",\n  \"Screen round corner\": \"Screen round corner\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\",\n  \"Rainbow\": \"Rainbow\",\n  \"Weeb\": \"Weeb\",\n  \"Large language models\": \"Large language models\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Online models disallowed\\n\\nControlled by `policies.ai` config option\",\n  \"Policies\": \"Policies\",\n  \"Temperature must be between 0 and 2\": \"Temperature must be between 0 and 2\",\n  \"Automatic suspend\": \"Automatic suspend\",\n  \"Extra wallpaper zoom (%)\": \"Extra wallpaper zoom (%)\",\n  \"GitHub\": \"GitHub\",\n  \"%1 | Right-click to configure\": \"%1 | Right-click to configure\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\",\n  \"Edit directory\": \"Edit directory\",\n  \"Action\": \"Action\",\n  \"Search\": \"Search\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Tip: right-clicking a group\\nalso expands it\",\n  \"Bar\": \"Bar\",\n  \"Show regions of potential interest\": \"Show regions of potential interest\",\n  \"Clipboard\": \"Clipboard\",\n  \"Stopwatch\": \"Stopwatch\",\n  \"Enter text to translate...\": \"Enter text to translate...\",\n  \"App\": \"App\",\n  \"Sides\": \"Sides\",\n  \"No active player\": \"No active player\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\",\n  \"There might be a download in progress\": \"There might be a download in progress\",\n  \"Math result\": \"Math result\",\n  \"Fidelity\": \"Fidelity\",\n  \"Prefixes\": \"Prefixes\",\n  \"Terminal\": \"Terminal\",\n  \"Incorrect password\": \"Incorrect password\",\n  \"Line-separated\": \"Line-separated\",\n  \"Always\": \"Always\",\n  \"☕ Break: %1 minutes\": \"☕ Break: %1 minutes\",\n  \"Depends on sidebars\": \"Depends on sidebars\",\n  \"Tool set to: %1\": \"Tool set to: %1\",\n  \"Save chat\": \"Save chat\",\n  \"Crosshair overlay\": \"Crosshair overlay\",\n  \"Keybinds\": \"Keybinds\",\n  \"Launch\": \"Launch\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\",\n  \"Choose model\": \"Choose model\",\n  \"Base URL\": \"Base URL\",\n  \"Float\": \"Float\",\n  \"Wallpaper parallax\": \"Wallpaper parallax\",\n  \"Invalid arguments. Must provide `command`.\": \"Invalid arguments. Must provide `command`.\",\n  \"Fully charged\": \"Fully charged\",\n  \"Earbang protection\": \"Earbang protection\",\n  \"Low warning\": \"Low warning\",\n  \"Advanced\": \"Advanced\",\n  \"Scroll to change brightness\": \"Scroll to change brightness\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Loaded the following system prompt\\n\\n---\\n\\n%1\",\n  \"Show next time\": \"Show next time\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Current tool: %1\\nSet it with %2tool TOOL\",\n  \"Unread indicator: show count\": \"Unread indicator: show count\",\n  \"Press Super+G to toggle appearance\": \"Press Super+G to toggle appearance\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\",\n  \"Dots\": \"Dots\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Volume mixer\": \"Volume mixer\",\n  \"Config file\": \"Config file\",\n  \"API key set for %1\": \"API key set for %1\",\n  \"Online via %1 | %2's model\": \"Online via %1 | %2's model\",\n  \"Shell command\": \"Shell command\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\",\n  \"Reload Hyprland & Quickshell\": \"Reload Hyprland & Quickshell\",\n  \"Resources\": \"Resources\",\n  \"Brightness\": \"Brightness\",\n  \"Unknown\": \"Unknown\",\n  \"Polling interval (ms)\": \"Polling interval (ms)\",\n  \"Lock\": \"Lock\",\n  \"Thinking\": \"Thinking\",\n  \"Approve\": \"Approve\",\n  \"Unfinished\": \"Unfinished\",\n  \"Random: Konachan\": \"Random: Konachan\",\n  \"Connected\": \"Connected\",\n  \"Wallpaper safety enforced\": \"Wallpaper safety enforced\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Invalid arguments. Must provide `key` and `value`.\",\n  \"24h\": \"24h\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\",\n  \"Bar style\": \"Bar style\",\n  \"Load:\": \"Load:\",\n  \"Open file link\": \"Open file link\",\n  \"Ignored if terminal theming is not enabled\": \"Ignored if terminal theming is not enabled\",\n  \"Shutdown\": \"Shutdown\",\n  \"Hour marks\": \"Hour marks\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Online | Google's model\\nFast, can perform searches for up-to-date information\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Current model: %1\\nSet it with %2model MODEL\",\n  \"Select input device\": \"Select input device\",\n  \"Connect to Wi-Fi\": \"Connect to Wi-Fi\",\n  \"... and %1 more\": \"... and %1 more\",\n  \"Cookie clock settings\": \"Cookie clock settings\",\n  \"Looks a bit softer and more consistent with different number of sides,\\nbut has less impressive morphing\": \"Looks a bit softer and more consistent with different number of sides,\\nbut has less impressive morphing\",\n  \"Makes the clock always rotate. This is extremely expensive\\n(expect 50% usage on Intel UHD Graphics) and thus impractical.\": \"Makes the clock always rotate. This is extremely expensive\\n(expect 50% usage on Intel UHD Graphics) and thus impractical.\",\n  \"Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons\": \"Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons\",\n  \"Can't be turned on when using 'Numbers' dial style for aesthetic reasons\": \"Can't be turned on when using 'Numbers' dial style for aesthetic reasons\",\n  \"Brightness and volume\": \"Brightness and volume\",\n  \"Choose file\": \"Choose file\",\n  \"Invalid model. Supported: \\n```\": \"Invalid model. Supported: \\n```\",\n  \"Task Manager\": \"Task Manager\",\n  \"Charging:\": \"Charging:\",\n  \"Illegal increment\": \"Illegal increment\",\n  \"Total:\": \"Total:\",\n  \"or\": \"or\",\n  \"Battery\": \"Battery\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Timeout duration (if not defined by notification) (ms)\",\n  \"Cancel\": \"Cancel\",\n  \"Locked\": \"Locked\",\n  \"Temperature: %1\": \"Temperature: %1\",\n  \"Hover to trigger\": \"Hover to trigger\",\n  \"Command rejected by user\": \"Command rejected by user\",\n  \"User agent (for services that require it)\": \"User agent (for services that require it)\",\n  \"Saved to %1\": \"Saved to %1\",\n  \"Emojis\": \"Emojis\",\n  \"Color generation\": \"Color generation\",\n  \"Welcome app\": \"Welcome app\",\n  \"Humidity\": \"Humidity\",\n  \"Page %1\": \"Page %1\",\n  \"Feels like %1\": \"Feels like %1\",\n  \"Distro\": \"Distro\",\n  \"Transparency\": \"Transparency\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 tasks\",\n  \"Markdown test\": \"Markdown test\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Invalid tool. Supported tools:\\n- %1\",\n  \"No notifications\": \"No notifications\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Resume\": \"Resume\",\n  \"Work safety\": \"Work safety\",\n  \"Temperature\\nChange with /temp VALUE\": \"Temperature\\nChange with /temp VALUE\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Foreground boost (%)\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Night Light | Right-click to toggle Auto mode\",\n  \"Closet\": \"Closet\",\n  \"Yes\": \"Yes\",\n  \"Columns\": \"Columns\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\",\n  \"Kill conflicting programs?\": \"Kill conflicting programs?\",\n  \"For storing API keys and other sensitive information\": \"For storing API keys and other sensitive information\",\n  \"Reject\": \"Reject\",\n  \"Set API key\": \"Set API key\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Content\": \"Content\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"Vertical\": \"Vertical\",\n  \"Pick a wallpaper\": \"Pick a wallpaper\",\n  \"Load chat from %1\": \"Load chat from %1\",\n  \"Launch on startup\": \"Launch on startup\",\n  \"Add\": \"Add\",\n  \"Style: general\": \"Style: general\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Use Levenshtein distance-based algorithm instead of fuzzy\",\n  \"Shell & utilities theming must also be enabled\": \"Shell & utilities theming must also be enabled\",\n  \"Workspace\": \"Workspace\",\n  \"Translator\": \"Translator\",\n  \"Free:\": \"Free:\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Long break: %1 minutes\",\n  \"Value scroll\": \"Value scroll\",\n  \"Bar position\": \"Bar position\",\n  \"Language\": \"Language\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\",\n  \"AI\": \"AI\",\n  \"Task description\": \"Task description\",\n  \"Add task\": \"Add task\",\n  \"Donate\": \"Donate\",\n  \"Disable NSFW content\": \"Disable NSFW content\",\n  \"Set the system prompt for the model.\": \"Set the system prompt for the model.\",\n  \"Done\": \"Done\",\n  \"Focus\": \"Focus\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\",\n  \"View Markdown source\": \"View Markdown source\",\n  \"Border\": \"Border\",\n  \"Temperature set to %1\": \"Temperature set to %1\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Message the model... \\\"%1\\\" for commands\",\n  \"Translation goes here...\": \"Translation goes here...\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\",\n  \"For desktop wallpapers | Good quality\": \"For desktop wallpapers | Good quality\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Focus: %1 minutes\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"The current system prompt is\\n\\n---\\n\\n%1\",\n  \"About\": \"About\",\n  \"Quick\": \"Quick\",\n  \"General\": \"General\",\n  \"UV Index\": \"UV Index\",\n  \"Force dark mode in terminal\": \"Force dark mode in terminal\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Drag or click a region • LMB: Copy • RMB: Edit\",\n  \"%1 characters\": \"%1 characters\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\",\n  \"Monochrome\": \"Monochrome\",\n  \"Details\": \"Details\",\n  \"Issues\": \"Issues\",\n  \"Keyboard toggle\": \"Keyboard toggle\",\n  \"Might look ass. Unsupported.\": \"Might look ass. Unsupported.\",\n  \"Download\": \"Download\",\n  \"%1 does not require an API key\": \"%1 does not require an API key\",\n  \"Style & wallpaper\": \"Style & wallpaper\",\n  \"Second precision\": \"Second precision\",\n  \"Group style\": \"Group style\",\n  \"Break\": \"Break\",\n  \"Run\": \"Run\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\",\n  \"Interface Language\": \"Interface Language\",\n  \"Game mode\": \"Game mode\",\n  \"Usage: %1save CHAT_NAME\": \"Usage: %1save CHAT_NAME\",\n  \"Thin\": \"Thin\",\n  \"Light\": \"Light\",\n  \"When not fullscreen\": \"When not fullscreen\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\",\n  \"Privacy Policy\": \"Privacy Policy\",\n  \"Timeout (ms)\": \"Timeout (ms)\",\n  \"Allow NSFW content\": \"Allow NSFW content\",\n  \"Edit\": \"Edit\",\n  \"Digits in the middle\": \"Digits in the middle\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\",\n  \"Weather Service\": \"Weather Service\",\n  \"Background\": \"Background\",\n  \"Pick random from this folder\": \"Pick random from this folder\",\n  \"Pressure\": \"Pressure\",\n  \"Save\": \"Save\",\n  \"Run command\": \"Run command\",\n  \"Time to empty:\": \"Time to empty:\",\n  \"Place at bottom\": \"Place at bottom\",\n  \"Switched to search mode. Continue with the user's request.\": \"Switched to search mode. Continue with the user's request.\",\n  \"Performance Profile toggle\": \"Performance Profile toggle\",\n  \"Sidebars\": \"Sidebars\",\n  \"Usage: %1load CHAT_NAME\": \"Usage: %1load CHAT_NAME\",\n  \"Auto styling with Gemini\": \"Auto styling with Gemini\",\n  \"Simple digital\": \"Simple digital\",\n  \"No API key set for %1\": \"No API key set for %1\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Enter tags, or \\\"%1\\\" for commands\",\n  \"%1 queries pending\": \"%1 queries pending\",\n  \"Discussions\": \"Discussions\",\n  \"Tray\": \"Tray\",\n  \"Numbers\": \"Numbers\",\n  \"Intelligence\": \"Intelligence\",\n  \"Open network portal\": \"Open network portal\",\n  \"<i>No further instruction provided</i>\": \"<i>No further instruction provided</i>\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Locale code, e.g. fr_FR, de_DE, zh_CN...\",\n  \"Select language\": \"Select language\",\n  \"Generate translation with Gemini\": \"Generate translation with Gemini\",\n  \"Generating...\\nDon't close this window!\": \"Generating...\\nDon't close this window!\",\n  \"Generate\\nTypically takes 2 minutes\": \"Generate\\nTypically takes 2 minutes\",\n  \"Use system file picker\": \"Use system file picker\",\n  \"Wallpaper selector\": \"Wallpaper selector\",\n  \"but force at absolute corner\": \"but force at absolute corner\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\",\n  \"Copy path\": \"Copy path\",\n  \"Windows\": \"Windows\",\n  \"Regenerate\": \"Regenerate\",\n  \"Microphone\": \"Microphone\",\n  \"Unmuted\": \"Unmuted\",\n  \"System sound\": \"System sound\",\n  \"Enable now\": \"Enable now\",\n  \"Night Light\": \"Night Light\",\n  \"Show aim lines\": \"Show aim lines\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"Please charge!\\nAutomatic suspend triggers at %1%\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"Example use case: eroge on one workspace, dark Discord window on another\",\n  \"Couldn't recognize music\": \"Couldn't recognize music\",\n  \"Automatic\": \"Automatic\",\n  \"Hint target regions\": \"Hint target regions\",\n  \"Devices\": \"Devices\",\n  \"Eye protection\": \"Eye protection\",\n  \"Japanese\": \"Japanese\",\n  \"Layers\": \"Layers\",\n  \"Listening...\": \"Listening...\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\",\n  \"Identify Music\": \"Identify Music\",\n  \"Quick toggles\": \"Quick toggles\",\n  \"Hide sussy/anime wallpapers\": \"Hide sussy/anime wallpapers\",\n  \"Android\": \"Android\",\n  \"Show\": \"Show\",\n  \"Muted\": \"Muted\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Audio input | Right-click for volume mixer & device selector\",\n  \"Region selector (screen snipping/Google Lens)\": \"Region selector (screen snipping/Google Lens)\",\n  \"Total duration timeout (s)\": \"Total duration timeout (s)\",\n  \"Music Recognition\": \"Music Recognition\",\n  \"Night Light | Right-click to configure\": \"Night Light | Right-click to configure\",\n  \"Anti-flashbang (experimental)\": \"Anti-flashbang (experimental)\",\n  \"Digital clock settings\": \"Digital clock settings\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\",\n  \"Polling interval (m)\": \"Polling interval (m)\",\n  \"Inactive\": \"Inactive\",\n  \"Authentication\": \"Authentication\",\n  \"Full warning\": \"Full warning\",\n  \"Power Profile\": \"Power Profile\",\n  \"Content region\": \"Content region\",\n  \"Internet\": \"Internet\",\n  \"Record\": \"Record\",\n  \"Circle selection\": \"Circle selection\",\n  \"Edit quick toggles\": \"Edit quick toggles\",\n  \"Virtual Keyboard\": \"Virtual Keyboard\",\n  \"Music Recognized\": \"Music Recognized\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Make sure you have songrec installed\": \"Make sure you have songrec installed\",\n  \"Dark Mode\": \"Dark Mode\",\n  \"No device\": \"No device\",\n  \"Animate time change\": \"Animate time change\",\n  \"It may take a few seconds to update\": \"It may take a few seconds to update\",\n  \"Polling interval (s)\": \"Polling interval (s)\",\n  \"Perhaps what you're listening to is too niche\": \"Perhaps what you're listening to is too niche\",\n  \"Fahrenheit unit\": \"Fahrenheit unit\",\n  \"Sliders\": \"Sliders\",\n  \"Roman\": \"Roman\",\n  \"Number style\": \"Number style\",\n  \"Intensity\": \"Intensity\",\n  \"Google Lens\": \"Google Lens\",\n  \"Circle\": \"Circle\",\n  \"Hide clipboard images copied from sussy sources\": \"Hide clipboard images copied from sussy sources\",\n  \"Scroll to Bottom\": \"Scroll to Bottom\",\n  \"Enabled\": \"Enabled\",\n  \"Nothing\": \"Nothing\",\n  \"Audio input\": \"Audio input\",\n  \"with vertical offset\": \"with vertical offset\",\n  \"Padding\": \"Padding\",\n  \"Please unplug the charger\": \"Please unplug the charger\",\n  \"Show notifications\": \"Show notifications\",\n  \"Path copied\": \"Path copied\",\n  \"On-screen keyboard\": \"On-screen keyboard\",\n  \"City name\": \"City name\",\n  \"Click to cycle through power profiles\": \"Click to cycle through power profiles\",\n  \"Recognize music | Right-click to toggle source\": \"Recognize music | Right-click to toggle source\",\n  \"Use old sine wave cookie implementation\": \"Use old sine wave cookie implementation\",\n  \"Rectangular selection\": \"Rectangular selection\",\n  \"Audio output\": \"Audio output\",\n  \"Applications\": \"Applications\",\n  \"Circle to Search\": \"Circle to Search\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Audio output | Right-click for volume mixer & device selector\",\n  \"Enable GPS based location\": \"Enable GPS based location\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\",\n  \"Sounds\": \"Sounds\",\n  \"Active\": \"Active\",\n  \"Keep awake\": \"Keep awake\",\n  \"Auto,\": \"Auto,\",\n  \"Normal\": \"Normal\",\n  \"Force hover open at absolute corner\": \"Force hover open at absolute corner\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Open the shell config file\\nAlternatively right-click to copy path\",\n  \"Recognize music\": \"Recognize music\",\n  \"Stroke width\": \"Stroke width\",\n  \"Use varying shapes for password characters\": \"Use varying shapes for password characters\",\n  \"Battery full\": \"Battery full\",\n  \"Pin\": \"Pin\",\n  \"Unpin\": \"Unpin\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/es_MX.json",
    "content": "{\n  \"Dark/Light toggle\": \"Alternar oscuro/claro\",\n  \"There might be a download in progress. Check your Downloads folder.\": \"Puede haber una descarga en curso. Revisa tu carpeta de Descargas.\",\n  \"Authentication\": \"Autenticación\",\n  \"More comfortable viewing at night\": \"Visualización más cómoda de noche\",\n  \"Disconnect\": \"Desconectar\",\n  \"Sunrise\": \"Amanecer\",\n  \"Show notifications\": \"Mostrar notificaciones\",\n  \"Video Recording Path\": \"Ruta de grabación de video\",\n  \"Input device\": \"Dispositivo de entrada\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"Cuando la opción anterior está desactivada y esta está activada,\\naún puedes pasar el cursor por el extremo de la esquina para abrir la barra lateral,\\ny el área restante puede usarse para desplazar el volumen/brillo\",\n  \"Save chat\": \"Guardar chat\",\n  \"Bar style\": \"Estilo de barra\",\n  \"Monospace font\": \"Fuente monoespaciada\",\n  \"Show hidden icons\": \"Mostrar iconos ocultos\",\n  \"Work safety\": \"Seguridad laboral\",\n  \"Page %1\": \"Página %1\",\n  \"Illegal increment\": \"Incremento no permitido\",\n  \"New desktop\": \"Nuevo escritorio\",\n  \"Adapts the <b>display (physical screen) brightness</b><br><br>Pros: Less expensive, retains colors<br>Cons: Not immediately responsive<br><br><i>Adjusts display brightness after each Hyprland IPC event</i>\": \"Adapta el <b>brillo del monitor (pantalla física)</b><br><br>Ventajas: Menos costoso, conserva los colores<br>Desventajas: No responde de inmediato<br><br><i>Ajusta el brillo del monitor tras cada evento IPC de Hyprland</i>\",\n  \"Font family name (e.g., JetBrains Mono NF)\": \"Nombre de familia de fuente (p. ej., JetBrains Mono NF)\",\n  \"Open\": \"Abrir\",\n  \"Jump to current month\": \"Ir al mes actual\",\n  \"Shut down\": \"Apagar\",\n  \"Normal\": \"Normal\",\n  \"Fonts\": \"Fuentes\",\n  \"Widgets\": \"Widgets\",\n  \"Set the tool to use for the model.\": \"Establece la herramienta a usar con el modelo.\",\n  \"Bar\": \"Barra\",\n  \"Font width and roundness settings are only available for some fonts like Google Sans Flex\": \"Los ajustes de ancho y redondez de fuente solo están disponibles para algunas fuentes como Google Sans Flex\",\n  \"Hour hand\": \"Manecilla de hora\",\n  \"Use macOS-like symbols for mods keys\": \"Usar símbolos estilo macOS para teclas modificadoras\",\n  \"Unpin from Start\": \"Desanclar del inicio\",\n  \"Connected\": \"Conectado\",\n  \"All\": \"Todo\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Herramienta no válida. Herramientas compatibles:\\n- %1\",\n  \"Timer\": \"Temporizador\",\n  \"Bottom\": \"Abajo\",\n  \"Connect\": \"Conectar\",\n  \"Sunset\": \"Atardecer\",\n  \"View Markdown source\": \"Ver fuente Markdown\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"No se encontró un servicio GPS. Usando el método alternativo.\",\n  \"Minute hand\": \"Manecilla de minutos\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"No todas las opciones están disponibles en esta aplicación. También deberías revisar el archivo de configuración haciendo clic en el botón \\\"Archivo de configuración\\\" en la esquina superior izquierda o abriendo %1 manualmente.\",\n  \"Click to unmute\": \"Clic para activar el sonido\",\n  \"Useless buttons\": \"Botones inútiles\",\n  \"Command rejected by user\": \"Comando rechazado por el usuario\",\n  \"Lock\": \"Bloquear\",\n  \"Expressive font\": \"Fuente expresiva\",\n  \"Security\": \"Seguridad\",\n  \"Sound input\": \"Entrada de sonido\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"En línea | Modelo de Google\\nModelo más reciente, más lento que su predecesor pero con respuestas de mayor calidad\",\n  \"Roman\": \"Romano\",\n  \"Style: general\": \"Estilo: general\",\n  \"Numbers font\": \"Fuente de números\",\n  \"Internet\": \"Internet\",\n  \"Date style\": \"Estilo de fecha\",\n  \"When this is off you'll have to click\": \"Cuando está desactivado tendrás que hacer clic\",\n  \"Corner style\": \"Estilo de esquina\",\n  \"Rows\": \"Filas\",\n  \"Tool set to: %1\": \"Herramienta establecida en: %1\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Modelos en línea no permitidos\\n\\nControlado por la opción de configuración `policies.ai`\",\n  \"Number show delay when pressing Super (ms)\": \"Retraso para mostrar número al presionar Super (ms)\",\n  \"Keep right sidebar loaded\": \"Mantener la barra lateral derecha cargada\",\n  \"Base URL\": \"URL base\",\n  \"Cheat sheet\": \"Hoja de trucos\",\n  \"Recognize music\": \"Reconocer música\",\n  \"Region width\": \"Ancho de región\",\n  \"Wallpaper & Colors\": \"Fondo de pantalla y colores\",\n  \"Sounds\": \"Sonidos\",\n  \"About\": \"Acerca de\",\n  \"Dial style\": \"Estilo de esfera\",\n  \"Classic\": \"Clásico\",\n  \"Translation goes here...\": \"La traducción va aquí...\",\n  \"Widget: Weather\": \"Widget: Clima\",\n  \"Copy\": \"Copiar\",\n  \"Services\": \"Servicios\",\n  \"Dark\": \"Oscuro\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"El prompt de sistema actual es\\n\\n---\\n\\n%1\",\n  \"Refreshing (manually triggered)\": \"Actualizando (activado manualmente)\",\n  \"Qt apps\": \"Apps Qt\",\n  \"Corner open\": \"Apertura por esquina\",\n  \"Reload Hyprland & Quickshell\": \"Recargar Hyprland y Quickshell\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Notas para Zerochan:\\n- Debes ingresar un color\\n- Configura tu nombre de usuario de Zerochan en la opción `sidebar.booru.zerochan.username`. ¡[Podrías ser baneado si no lo haces](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"No\": \"No\",\n  \"Mic toggle\": \"Alternar micrófono\",\n  \"Snipping area\": \"Área de recorte\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Solo waifus | Excelente calidad, cantidad limitada\",\n  \"Yes\": \"Sí\",\n  \"Restart\": \"Reiniciar\",\n  \"Thin\": \"Delgado\",\n  \"File Explorer\": \"Explorador de archivos\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Uso: <tt>%1superpaste NUM_DE_ENTRADAS[i]</tt>\\nAgrega <tt>i</tt> cuando quieras imágenes\\nEjemplos:\\n<tt>%1superpaste 4i</tt> para las últimas 4 imágenes\\n<tt>%1superpaste 7</tt> para las últimas 7 entradas\",\n  \"Font roundness\": \"Redondez de fuente\",\n  \"Click to cycle through power profiles\": \"Clic para cambiar entre perfiles de energía\",\n  \"Split buttons\": \"Botones divididos\",\n  \"Bar & screen\": \"Barra y pantalla\",\n  \"Pin to Start\": \"Anclar al inicio\",\n  \"Used for displaying numbers\": \"Usada para mostrar números\",\n  \"Center clock\": \"Reloj centrado\",\n  \"Audio\": \"Audio\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Selecciona el idioma de la interfaz.\\n\\\"Auto\\\" usará la configuración regional de tu sistema.\",\n  \"Unknown Album\": \"Álbum desconocido\",\n  \"Your package manager is running\": \"Tu gestor de paquetes está en ejecución\",\n  \"Swap\": \"Intercambio\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Total de tokens\\nEntrada: %1\\nSalida: %2\",\n  \"Volume limit\": \"Límite de volumen\",\n  \"Sign out\": \"Cerrar sesión\",\n  \"Tonal Spot\": \"Tono puntual\",\n  \"Apps\": \"Aplicaciones\",\n  \"Privacy Policy\": \"Política de privacidad\",\n  \"Media\": \"Multimedia\",\n  \"%1 Safe Storage\": \"%1 Almacenamiento seguro\",\n  \"Network\": \"Red\",\n  \"Inactive\": \"Inactivo\",\n  \"Save chat to %1\": \"Guardar chat en %1\",\n  \"RAM\": \"RAM\",\n  \"No media\": \"Sin multimedia\",\n  \"Invalid arguments. Must provide `command`.\": \"Argumentos no válidos. Se debe proporcionar `command`.\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Least busy\": \"Menos ocupado\",\n  \"When not fullscreen\": \"Cuando no está en pantalla completa\",\n  \"Used for general UI text\": \"Usada para texto general de la interfaz\",\n  \"Prefixes\": \"Prefijos\",\n  \"Weeb\": \"Weeb\",\n  \"Bluetooth devices\": \"Dispositivos Bluetooth\",\n  \"Anime\": \"Anime\",\n  \"Tray\": \"Bandeja\",\n  \"Top\": \"Arriba\",\n  \"Policies\": \"Políticas\",\n  \"Get the latest features and security improvements with\\nthe newest feature update.\\n\\n%1 packages\": \"Obtén las últimas funciones y mejoras de seguridad con\\nla actualización más reciente.\\n\\n%1 paquetes\",\n  \"Pressure\": \"Presión\",\n  \"Math\": \"Matemáticas\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Ingresa etiquetas, o \\\"%1\\\" para comandos\",\n  \"Enable GPS based location\": \"Activar ubicación por GPS\",\n  \"Super key symbol\": \"Símbolo de la tecla Super\",\n  \"Load chat\": \"Cargar chat\",\n  \"Hibernate\": \"Hibernar\",\n  \"Config file\": \"Archivo de\\nconfiguración\",\n  \"Stroke width\": \"Grosor de trazo\",\n  \"Reading font\": \"Fuente de lectura\",\n  \"Close all windows\": \"Cerrar todas las ventanas\",\n  \"Workspace\": \"Espacio de trabajo\",\n  \"Clock style (locked)\": \"Estilo de reloj (bloqueado)\",\n  \"Right\": \"Derecha\",\n  \"Night Light\": \"Luz nocturna\",\n  \"12h am/pm\": \"12h am/pm\",\n  \"Couldn't recognize music\": \"No se pudo reconocer la música\",\n  \"Fidelity\": \"Fidelidad\",\n  \"Settings\": \"Configuración\",\n  \"Hover to reveal\": \"Pasar el cursor para revelar\",\n  \"Polling interval (s)\": \"Intervalo de consulta (s)\",\n  \"Load prompt from %1\": \"Cargar prompt desde %1\",\n  \"Web\": \"Web\",\n  \"Dots\": \"Puntos\",\n  \"Rect\": \"Rectángulo\",\n  \"Be patient...\": \"Ten paciencia...\",\n  \"Free:\": \"Libre:\",\n  \"Allow NSFW content\": \"Permitir contenido NSFW\",\n  \"Sound effects\": \"Efectos de sonido\",\n  \"Language\": \"Idioma\",\n  \"Circle to Search\": \"Círculo para buscar\",\n  \"Transparency\": \"Transparencia\",\n  \"Networking\": \"Redes\",\n  \"Large language models\": \"Modelos de lenguaje\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Para establecer una clave API, pásala con el comando %4\\n\\nPara ver la clave, pasa \\\"get\\\" con el comando<br/>\\n\\n### Para %1:\\n\\n**Enlace**: %2\\n\\n%3\",\n  \"%1\\nInternet access\": \"%1\\nAcceso a Internet\",\n  \"Ignored if terminal theming is not enabled\": \"Se ignora si el tema de terminal no está activado\",\n  \"e.g. 󰘴  for Ctrl, 󰘵  for Alt, 󰘶  for Shift, etc\": \"p. ej. 󰘴  para Ctrl, 󰘵  para Alt, 󰘶  para Shift, etc.\",\n  \"Close (Esc)\": \"Cerrar (Esc)\",\n  \"Break\": \"Descanso\",\n  \"Require password to power off/restart\": \"Requerir contraseña para apagar/reiniciar\",\n  \"Generate translation with Gemini\": \"Generar traducción con Gemini\",\n  \"Battery\": \"Batería\",\n  \"at\": \"a las\",\n  \"Use symbols for mouse\": \"Usar símbolos para el ratón\",\n  \"Display modifiers and keys in multiple keycap (e.g., \\\"Ctrl + A\\\" instead of \\\"Ctrl A\\\" or \\\"󰘴 + A\\\" instead of \\\"󰘴 A\\\")\": \"Mostrar modificadores y teclas con varias teclas (p. ej., \\\"Ctrl + A\\\" en lugar de \\\"Ctrl A\\\" o \\\"󰘴 + A\\\" en lugar de \\\"󰘴 A\\\")\",\n  \"Long break\": \"Descanso largo\",\n  \"No active player\": \"Sin reproductor activo\",\n  \"Overlay: Crosshair\": \"Superposición: Mira\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Por qué es útil:\\nPara valores distintos de 0, no se activará al llegar a la\\nesquina de la pantalla por el borde horizontal, pero sí\\nal hacerlo por el borde vertical\",\n  \"Brightness and volume\": \"Brillo y volumen\",\n  \"Scroll to Bottom\": \"Desplazarse al final\",\n  \"Always show numbers\": \"Mostrar siempre los números\",\n  \"Brightness\": \"Brillo\",\n  \"Choose file\": \"Elegir archivo\",\n  \"Local only\": \"Solo local\",\n  \"Keybinds\": \"Atajos de teclado\",\n  \"Go to source (%1)\": \"Ir a la fuente (%1)\",\n  \"Do you want to allow this app to make changes to your device?\": \"¿Quieres permitir que esta aplicación realice cambios en tu dispositivo?\",\n  \"Disable tools\": \"Desactivar herramientas\",\n  \"Reboot to firmware settings\": \"Reiniciar a configuración de firmware\",\n  \"Draggable\": \"Arrastrable\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Asegúrate de que tu reproductor tenga soporte MPRIS\\no intenta desactivar el filtrado de reproductores duplicados\",\n  \"Balance brightness based on content\": \"Equilibrar el brillo según el contenido\",\n  \"GitHub\": \"GitHub\",\n  \"Info\": \"Información\",\n  \"Clear the current list of images\": \"Limpiar la lista actual de imágenes\",\n  \"Pinned on startup\": \"Anclado al inicio\",\n  \"Color picker\": \"Selector de color\",\n  \"Automatic\": \"Automático\",\n  \"City name\": \"Nombre de ciudad\",\n  \"e.g. 󱊫 for F1, 󱊶  for F12\": \"p. ej. 󱊫 para F1, 󱊶  para F12\",\n  \"Top-down\": \"De arriba hacia abajo\",\n  \"Font family name (e.g., Space Grotesk)\": \"Nombre de familia de fuente (p. ej., Space Grotesk)\",\n  \"Output\": \"Salida\",\n  \"Advanced\": \"Avanzado\",\n  \"Second precision\": \"Precisión de segundos\",\n  \"To Do\": \"Por hacer\",\n  \"Automatically hide\": \"Ocultar automáticamente\",\n  \"Quick\": \"Rápido\",\n  \"Keybind font size\": \"Tamaño de fuente de atajos\",\n  \"Hi there! First things first...\": \"¡Hola! Primero lo primero...\",\n  \"Music Recognition\": \"Reconocimiento de música\",\n  \"Listening...\": \"Escuchando...\",\n  \"Show\": \"Mostrar\",\n  \"Center icons\": \"Centrar iconos\",\n  \"Dot\": \"Punto\",\n  \"Incorrect password\": \"Contraseña incorrecta\",\n  \"Unmuted\": \"Sin silencio\",\n  \"On-screen keyboard\": \"Teclado en pantalla\",\n  \"Brightness adjustment\": \"Ajuste de brillo\",\n  \"Workspaces shown\": \"Espacios de trabajo mostrados\",\n  \"Donate\": \"Donar\",\n  \"Recognize text\": \"Reconocer texto\",\n  \"Content\": \"Contenido\",\n  \"Enter password\": \"Ingresar contraseña\",\n  \"Han chars\": \"Caracteres Han\",\n  \"Please unplug the charger\": \"Por favor desconecta el cargador\",\n  \"☕ Break: %1 minutes\": \"☕ Descanso: %1 minutos\",\n  \"Delete\": \"Eliminar\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"Clave API:\\n\\n```txt\\n%1\\n```\",\n  \"Android\": \"Android\",\n  \"Done\": \"Listo\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"El registro falló. Por favor inspecciona manualmente con el comando <tt>warp-cli</tt>\",\n  \"Enable blur\": \"Activar desenfoque\",\n  \"Wind\": \"Viento\",\n  \"Value scroll\": \"Desplazamiento de valor\",\n  \"Not visible to model\": \"No visible para el modelo\",\n  \"Exceeded max allowed\": \"Se superó el máximo permitido\",\n  \"Report a Bug\": \"Reportar un error\",\n  \"Speakers (%1): %2\": \"Altavoces (%1): %2\",\n  \"Enable if you want clocks to show seconds accurately\": \"Activa esto si quieres que los relojes muestren los segundos con precisión\",\n  \"Not secured\": \"No asegurado\",\n  \"Used:\": \"Usado:\",\n  \"Depends on sidebars\": \"Depende de las barras\\nlaterales\",\n  \"Second hand\": \"Segundero\",\n  \"Reboot\": \"Reiniciar\",\n  \"Reject\": \"Rechazar\",\n  \"For storing API keys and other sensitive information\": \"Para almacenar claves API y otra información sensible\",\n  \"Not connected\": \"No conectado\",\n  \"Critically low battery\": \"Batería críticamente baja\",\n  \"Show this window on all desktops\": \"Mostrar esta ventana en todos los escritorios\",\n  \"Time\": \"Hora\",\n  \"Sliders\": \"Controles deslizantes\",\n  \"Enabled\": \"Activado\",\n  \"For desktop wallpapers | Good quality\": \"Para fondos de escritorio | Buena calidad\",\n  \"Left to right\": \"De izquierda a derecha\",\n  \"Quick toggles\": \"Interruptores rápidos\",\n  \"Details\": \"Detalles\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Salida de audio | Clic derecho para mezclador de volumen y selector de dispositivo\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Usar el selector de archivos del sistema en su lugar\\nClic derecho para establecer esto como comportamiento predeterminado\",\n  \"Thought\": \"Pensamiento\",\n  \"Volume\": \"Volumen\",\n  \"Emojis\": \"Emojis\",\n  \"Time to full:\": \"Tiempo para carga completa:\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Pueden ser imágenes o partes de la pantalla con algún contenido delimitado.\\nPuede no ser siempre preciso.\\nEsto se realiza con un algoritmo de procesamiento de imágenes local, sin IA.\",\n  \"Perhaps what you're listening to is too niche\": \"Quizás lo que estás escuchando es demasiado de nicho\",\n  \"Command\": \"Comando\",\n  \"Wallpaper selector\": \"Selector de fondo de pantalla\",\n  \"Muted\": \"Silenciado\",\n  \"Sleep\": \"Suspender\",\n  \"Gives the model search capabilities (immediately)\": \"Proporciona al modelo capacidades de búsqueda (de inmediato)\",\n  \"Crosshair code (in Valorant's format)\": \"Código de mira (en formato de Valorant)\",\n  \"Used for headings and titles\": \"Usada para encabezados y títulos\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Usa Gemini para categorizar el fondo de pantalla y luego selecciona un preset basado en eso.\\nNecesitarás establecer la clave API de Gemini en la barra lateral izquierda primero.\\nLas imágenes se reducen para mejorar el rendimiento, pero por seguridad,\\nno selecciones fondos con información sensible.\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Luz nocturna | Clic derecho para alternar modo automático\",\n  \"Change password\": \"Cambiar contraseña\",\n  \"Unknown Title\": \"Título desconocido\",\n  \"Numbers\": \"Números\",\n  \"24h\": \"24h\",\n  \"No applications\": \"Sin aplicaciones\",\n  \"Force hover open at absolute corner\": \"Forzar apertura al pasar por la esquina exacta\",\n  \"of %1\": \"de %1\",\n  \"Code saved to file\": \"Código guardado en archivo\",\n  \"Content region\": \"Región de contenido\",\n  \"Invalid model. Supported: \\n```\": \"Modelo no válido. Compatibles: \\n```\",\n  \"Font family name\": \"Nombre de familia de fuente\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"¡Por favor carga el dispositivo!\\nLa suspensión automática se activa al %1%\",\n  \"Manage accounts\": \"Administrar cuentas\",\n  \"Open editor\": \"Abrir editor\",\n  \"Font used for Nerd Font icons\": \"Fuente usada para iconos de Nerd Font\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Comandos, editar configuraciones, buscar.\\nRequiere un turno extra para cambiar a modo búsqueda si es necesario\",\n  \"Record\": \"Grabar\",\n  \"Used for decorative/expressive text\": \"Usada para texto decorativo/expresivo\",\n  \"Visualize region\": \"Visualizar región\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"¿Idioma no listado o traducciones incompletas?\\nPuedes elegir generar traducciones con Gemini.\\n1. Abre la barra lateral izquierda con Super+A, establece el modelo en Gemini (si no lo está ya)\\n2. Escribe /key, presiona Enter y sigue las instrucciones\\n3. Escribe /key TU_CLAVE_API\\n4. Escribe el código de idioma abajo y presiona Generar\",\n  \"Saving...\": \"Guardando...\",\n  \"Monochrome\": \"Monocromático\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Fondo estacional aleatorio de osu!\\nLa imagen se guarda en ~/Pictures/Wallpapers\",\n  \"Polkit\": \"Polkit\",\n  \"Manage my account\": \"Administrar mi cuenta\",\n  \"Edit quick toggles\": \"Editar interruptores rápidos\",\n  \"Unknown function call: %1\": \"Llamada de función desconocida: %1\",\n  \"Clipboard\": \"Portapapeles\",\n  \"<b>Dims screen content</b> as needed.<br><br>Pros: Immediately responsive<br>Cons: Expensive and can hurt color accuracy<br><br><i>Uses a Hyprland screen shader</i>\": \"<b>Atenúa el contenido de la pantalla</b> según sea necesario.<br><br>Ventajas: Responde de inmediato<br>Desventajas: Costoso y puede afectar la precisión del color<br><br><i>Usa un shader de pantalla de Hyprland</i>\",\n  \"No new notifications\": \"Sin nuevas notificaciones\",\n  \"Volume mixer\": \"Mezclador de volumen\",\n  \"Force dark mode in terminal\": \"Forzar modo oscuro en la terminal\",\n  \"Save paths\": \"Guardar rutas\",\n  \"Copy path\": \"Copiar ruta\",\n  \"Anime boorus\": \"Boorus de anime\",\n  \"Open network portal\": \"Abrir portal de red\",\n  \"Quick markup (Ctrl+E)\": \"Marcado rápido (Ctrl+E)\",\n  \"Large images | God tier quality, no NSFW.\": \"Imágenes grandes | Calidad excepcional, sin NSFW.\",\n  \"Polling interval (ms)\": \"Intervalo de consulta (ms)\",\n  \"Model set to %1\": \"Modelo establecido en %1\",\n  \"Identify Music\": \"Identificar música\",\n  \"Circle selection\": \"Selección circular\",\n  \"Dark Mode\": \"Modo oscuro\",\n  \"Make icons pinned by default\": \"Anclar iconos de forma predeterminada\",\n  \"User agent (for services that require it)\": \"Agente de usuario (para servicios que lo requieren)\",\n  \"Use varying shapes for password characters\": \"Usar formas variadas para los caracteres de contraseña\",\n  \"Search, calculate or run\": \"Buscar, calcular o ejecutar\",\n  \"More Bluetooth settings\": \"Más ajustes de Bluetooth\",\n  \"Output device\": \"Dispositivo de salida\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Fondo de anime SFW aleatorio de Konachan\\nLa imagen se guarda en ~/Pictures/Wallpapers\",\n  \"Rectangle\": \"Rectángulo\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Consejo: hacer clic derecho en un grupo\\ntambién lo expande\",\n  \"Pin to taskbar\": \"Anclar a la barra de tareas\",\n  \"Show date\": \"Mostrar fecha\",\n  \"Battery: %1%2\": \"Batería: %1%2\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Endpoint API actual: %1\\nEstablécelo con %2mode PROVEEDOR\",\n  \"Columns\": \"Columnas\",\n  \"Recognize music | Right-click to toggle source\": \"Reconocer música | Clic derecho para cambiar fuente\",\n  \"Replace 󱕐   for \\\"Scroll ↓\\\", 󱕑   \\\"Scroll ↑\\\", L󰍽   \\\"LMB\\\", R󰍽   \\\"RMB\\\", 󱕒   \\\"Scroll ↑/↓\\\" and ⇞/⇟ for \\\"Page_↑/↓\\\"\": \"Reemplaza 󱕐   por \\\"Desplazar ↓\\\", 󱕑   \\\"Desplazar ↑\\\", L󰍽   \\\"Clic izq.\\\", R󰍽   \\\"Clic der.\\\", 󱕒   \\\"Desplazar ↑/↓\\\" y ⇞/⇟ por \\\"Pág. ↑/↓\\\"\",\n  \"Choose model\": \"Elegir modelo\",\n  \"Approve\": \"Aprobar\",\n  \"Configuration\": \"Configuración\",\n  \"Hit \\\"/\\\" to search\": \"Presiona \\\"/\\\" para buscar\",\n  \"Regenerate\": \"Regenerar\",\n  \"Move right\": \"Mover a la derecha\",\n  \"Image source\": \"Fuente de imagen\",\n  \"Dock\": \"Dock\",\n  \"Open recordings folder\": \"Abrir carpeta de grabaciones\",\n  \"Pin\": \"Anclar\",\n  \"Click to mute\": \"Clic para silenciar\",\n  \"Search for apps\": \"Buscar aplicaciones\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"La conexión falló. Por favor inspecciona manualmente con el comando <tt>warp-cli</tt>\",\n  \"Show only when locked\": \"Mostrar solo cuando está bloqueado\",\n  \"Sides\": \"Lados\",\n  \"Edit directory\": \"Editar directorio\",\n  \"You can also manually edit cheatsheet.superKey\": \"También puedes editar manualmente cheatsheet.superKey\",\n  \"Color generation\": \"Generación de color\",\n  \"Unpin\": \"Desanclar\",\n  \"Font weight\": \"Peso de fuente\",\n  \"Reset\": \"Restablecer\",\n  \"Math result\": \"Resultado matemático\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"¡Disfruta! Puedes volver a abrir la aplicación de bienvenida en cualquier momento con <tt>Super+Shift+Alt+/</tt>. Para abrir la configuración, presiona <tt>Super+I</tt>\",\n  \"Notifications\": \"Notificaciones\",\n  \"Save to Downloads\": \"Guardar en Descargas\",\n  \"Command-line-invoked Action\": \"Acción invocada por línea de comandos\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Teclas de flecha para navegar, Enter para seleccionar\\nEsc o clic en cualquier lugar para cancelar\",\n  \"Paired\": \"Vinculado\",\n  \"Game mode\": \"Modo juego\",\n  \"Emoji\": \"Emoji\",\n  \"Health:\": \"Salud:\",\n  \"Anti-flashbang (experimental)\": \"Anti-destello (experimental)\",\n  \"Turn on from sunset to sunrise\": \"Activar del atardecer al amanecer\",\n  \"Close\": \"Cerrar\",\n  \"Workspaces\": \"Espacios de trabajo\",\n  \"Record region\": \"Grabar región\",\n  \"Shell & utilities theming must also be enabled\": \"El tema de shell y utilidades también debe estar activado\",\n  \"Superpaste\": \"Superpegar\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"Primero necesitas ingresar tu clave API de Gemini.\\nEscribe /key en la barra lateral para ver las instrucciones.\",\n  \"Enable update checks\": \"Activar verificación de actualizaciones\",\n  \"Select Language\": \"Seleccionar idioma\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Código de idioma, p. ej. fr_FR, de_DE, zh_CN...\",\n  \"Desktop %1\": \"Escritorio %1\",\n  \"Keep awake\": \"Mantener activo\",\n  \"Hollow\": \"Hueco\",\n  \"Group style\": \"Estilo de grupo\",\n  \"It may take a few seconds to update\": \"Puede tardar unos segundos en actualizarse\",\n  \"Unknown command:\": \"Comando desconocido:\",\n  \"Action\": \"Acción\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Establece la temperatura (aleatoriedad) del modelo. Los valores van de 0 a 2 para Gemini, de 0 a 1 para otros modelos. El valor predeterminado es 0.5.\",\n  \"Style: Blurred\": \"Estilo: Difuminado\",\n  \"Parallax\": \"Desplazamiento\",\n  \"Bubble\": \"Burbuja\",\n  \"Saved to %1\": \"Guardado en %1\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Herramienta actual: %1\\nEstablécela con %2tool HERRAMIENTA\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"El popular | Mayor cantidad, pero la calidad puede variar mucho\",\n  \"Sidebars\": \"Barras laterales\",\n  \"Description font size\": \"Tamaño de fuente de descripción\",\n  \"Close window\": \"Cerrar ventana\",\n  \"Calendar\": \"Calendario\",\n  \"%1 mins\": \"%1 min\",\n  \"Automatically suspends the system when battery is low\": \"Suspende automáticamente el sistema cuando la batería está baja\",\n  \"Enable\": \"Activar\",\n  \"Show aim lines\": \"Mostrar líneas de mira\",\n  \"Overview\": \"Vista general\",\n  \"Enable opening zoom animation\": \"Activar animación de zoom al abrir\",\n  \"Copy code\": \"Copiar código\",\n  \"Path copied\": \"Ruta copiada\",\n  \"Audio output\": \"Salida de audio\",\n  \"Open file link\": \"Abrir enlace de archivo\",\n  \"Tooltips\": \"Información emergente\",\n  \"Move left\": \"Mover a la izquierda\",\n  \"Sound output\": \"Salida de sonido\",\n  \"Windows\": \"Ventanas\",\n  \"Fahrenheit unit\": \"Unidad Fahrenheit\",\n  \"Night Light | Right-click to configure\": \"Luz nocturna | Clic derecho para configurar\",\n  \"Set API key\": \"Establecer clave API\",\n  \"Markdown test\": \"Prueba de Markdown\",\n  \"Pills\": \"Píldoras\",\n  \"Font family name (e.g., Google Sans Flex)\": \"Nombre de familia de fuente (p. ej., Google Sans Flex)\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"... and %1 more\": \"... y %1 más\",\n  \"Shell & utilities\": \"Shell y utilidades\",\n  \"Provider set to\": \"Proveedor establecido en\",\n  \"Main font\": \"Fuente principal\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Modelo actual: %1\\nEstablécelo con %2model MODELO\",\n  \"Nerd font icons\": \"Iconos de Nerd Font\",\n  \"Style & wallpaper\": \"Estilo y fondo de pantalla\",\n  \"Scroll to change brightness\": \"Desplazar para cambiar el brillo\",\n  \"Input\": \"Entrada\",\n  \"Intensity\": \"Intensidad\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Enfoque: %1 minutos\",\n  \"Lock screen\": \"Pantalla de bloqueo\",\n  \"Password\": \"Contraseña\",\n  \"with vertical offset\": \"con desplazamiento vertical\",\n  \"Fill\": \"Relleno\",\n  \"Generating...\\nDon't close this window!\": \"Generando...\\n¡No cierres esta ventana!\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"En línea | Modelo de %1 | Proporciona respuestas rápidas, ágiles y bien formateadas. Desventajas: no muy dispuesto a hacer cosas; puede inventar llamadas de función desconocidas\",\n  \"Feels like %1\": \"Sensación térmica %1\",\n  \"Welcome app\": \"App de bienvenida\",\n  \"Start\": \"Iniciar\",\n  \"Digital clock settings\": \"Configuración del reloj digital\",\n  \"Expressive\": \"Expresivo\",\n  \"Auto,\": \"Auto,\",\n  \"Task View\": \"Vista de tareas\",\n  \"Bottom-up\": \"De abajo hacia arriba\",\n  \"Type /key to get started with online models\\nCtrl+O to expand sidebar\\nCtrl+P to pin sidebar\\nCtrl+D to detach sidebar\": \"Escribe /key para comenzar con modelos en línea\\nCtrl+O para expandir la barra lateral\\nCtrl+P para anclar la barra lateral\\nCtrl+D para desanclar la barra lateral\",\n  \"Wallpaper safety enforced\": \"Seguridad de fondo de pantalla aplicada\",\n  \"Automatic suspend\": \"Suspensión automática\",\n  \"Music Recognized\": \"Música reconocida\",\n  \"Cookie\": \"Cookie\",\n  \"UV Index\": \"Índice UV\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"Clave API establecida\\nCámbiala con /key TU_CLAVE_API\",\n  \"Show next time\": \"Mostrar la próxima vez\",\n  \"Most busy\": \"Más ocupado\",\n  \"Finished tasks will go here\": \"Las tareas completadas aparecerán aquí\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Issues\": \"Problemas\",\n  \"Load:\": \"Carga:\",\n  \"Hug\": \"Abrazo\",\n  \"(Plugged in)\": \"(Conectado)\",\n  \"Discussions\": \"Discusiones\",\n  \"Enter text to translate...\": \"Ingresa texto para traducir...\",\n  \"Low battery\": \"Batería baja\",\n  \"Positioning\": \"Posicionamiento\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Te permite abrir las barras laterales haciendo clic o pasando el cursor por las esquinas de la pantalla independientemente de la posición de la barra\",\n  \"Resources\": \"Recursos\",\n  \"Load chat from %1\": \"Cargar chat desde %1\",\n  \"Clear all\": \"Limpiar todo\",\n  \"OK\": \"Aceptar\",\n  \"Tip: Close a window with Super+Q\": \"Consejo: Cierra una ventana con Super+Q\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Contenido limpio | Excelente calidad, sin NSFW\",\n  \"Save\": \"Guardar\",\n  \"Productivity\": \"Productividad\",\n  \"Disable NSFW content\": \"Desactivar contenido NSFW\",\n  \"Locked\": \"Bloqueado\",\n  \"Nothing here!\": \"¡Nada aquí!\",\n  \"Temperature: %1\": \"Temperatura: %1\",\n  \"Hour marks\": \"Marcas de hora\",\n  \"Web search\": \"Búsqueda web\",\n  \"No pending tasks\": \"Sin tareas pendientes\",\n  \"Circle\": \"Círculo\",\n  \"API key set for %1\": \"Clave API establecida para %1\",\n  \"Unknown device\": \"Dispositivo desconocido\",\n  \"Scroll to change volume\": \"Desplazar para cambiar el volumen\",\n  \"Generate\\nTypically takes 2 minutes\": \"Generar\\nGeneralmente tarda 2 minutos\",\n  \"Allow NSFW\": \"Permitir NSFW\",\n  \"Stopwatch\": \"Cronómetro\",\n  \"Hint target regions\": \"Resaltar regiones objetivo\",\n  \"Text extractor\": \"Extractor de texto\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Abrir el archivo de configuración del shell\\nAlternativamente, clic derecho para copiar la ruta\",\n  \"No API key set for %1\": \"No hay clave API establecida para %1\",\n  \"Critical warning\": \"Advertencia crítica\",\n  \"Animate time change\": \"Animar cambio de hora\",\n  \"Screen snip\": \"Captura de pantalla\",\n  \"CPU\": \"CPU\",\n  \"Nothing\": \"Nada\",\n  \"Focus\": \"Enfoque\",\n  \"Run\": \"Ejecutar\",\n  \"Temperature must be between 0 and 2\": \"La temperatura debe estar entre 0 y 2\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Puede funcionar mejor si cometes muchos errores tipográficos,\\npero los resultados pueden ser extraños y puede que no funcione con acrónimos\\n(p. ej., \\\"GIMP\\\" puede que no te dé el programa de edición)\",\n  \"Documentation\": \"Documentación\",\n  \"Screenshot Path (leave empty to just copy)\": \"Ruta de captura de pantalla (deja vacío para solo copiar)\",\n  \"Help & Support\": \"Ayuda y soporte\",\n  \"All-rounder | Good quality, decent quantity\": \"Versátil | Buena calidad, cantidad decente\",\n  \"On\": \"Activado\",\n  \"Earbang protection\": \"Protección contra destellos de audio\",\n  \"Rainbow\": \"Arcoíris\",\n  \"Download\": \"Descargar\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Entrada de audio | Clic derecho para mezclador de volumen y selector de dispositivo\",\n  \"Random: Konachan\": \"Aleatorio: Konachan\",\n  \"Usage: %1tool TOOL_NAME\": \"Uso: %1tool NOMBRE_HERRAMIENTA\",\n  \"Use symbols for function keys\": \"Usar símbolos para teclas de función\",\n  \"Lap\": \"Vuelta\",\n  \"Active\": \"Activo\",\n  \"Local Ollama model | %1\": \"Modelo local de Ollama | %1\",\n  \"Shell command\": \"Comando de shell\",\n  \"Hide clipboard images copied from sussy sources\": \"Ocultar imágenes del portapapeles copiadas de fuentes sospechosas\",\n  \"Creativity\": \"Creatividad\",\n  \"Select language\": \"Seleccionar idioma\",\n  \"Usage\": \"Uso\",\n  \"Show \\\"Locked\\\" text\": \"Mostrar texto \\\"Bloqueado\\\"\",\n  \"Adjust the color temperature\": \"Ajustar la temperatura de color\",\n  \"Move to front\": \"Mover al frente\",\n  \"Pick a wallpaper\": \"Elegir un fondo de pantalla\",\n  \"Logout\": \"Cerrar sesión\",\n  \"Unknown\": \"Desconocido\",\n  \"If you want to somehow use fingerprint unlock...\": \"Si quieres usar desbloqueo por huella digital de alguna manera...\",\n  \"Precipitation\": \"Precipitación\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Cámbialo cuando quieras con /dark, /light, /wallpaper en el lanzador\\nSi los colores del shell no cambian:\\n    1. Abre la barra lateral derecha con Super+N\\n    2. Haz clic en \\\"Recargar Hyprland y Quickshell\\\" en la esquina superior derecha\",\n  \"Digital\": \"Digital\",\n  \"Full\": \"Completo\",\n  \"Microphone\": \"Micrófono\",\n  \"Clock style\": \"Estilo de reloj\",\n  \"Used for code and terminal\": \"Usada para código y terminal\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Previene incrementos abruptos y restringe el límite de volumen\",\n  \"Download complete\": \"Descarga completa\",\n  \"Connect to Wi-Fi\": \"Conectar a Wi-Fi\",\n  \"Use adaptive alignment\": \"Usar alineación adaptativa\",\n  \"System prompt\": \"Prompt de sistema\",\n  \"Enjoy your empty sidebar...\": \"Disfruta tu barra lateral vacía...\",\n  \"Fruit Salad\": \"Ensalada de frutas\",\n  \"Overlay: Floating Image\": \"Superposición: Imagen flotante\",\n  \"Auto styling with Gemini\": \"Estilo automático con Gemini\",\n  \"Anti-flashbang\": \"Anti-destello\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Font family name (e.g., Readex Pro)\": \"Nombre de familia de fuente (p. ej., Readex Pro)\",\n  \"Pause\": \"Pausar\",\n  \"Interface\": \"Interfaz\",\n  \"Number style\": \"Estilo de número\",\n  \"Set the current API provider\": \"Establecer el proveedor de API actual\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Recuerda que en la mayoría de dispositivos siempre puedes mantener presionado el botón de encendido para forzar el apagado\\nEsto solo hace que sea un poco más difícil que ocurran accidentes\",\n  \"Weather Service\": \"Servicio meteorológico\",\n  \"Vertical\": \"Vertical\",\n  \"Write something here...\\nUse '-' to create copyable bullet points, like this:\\n\\nSheep fricker\\n- 4x Slab\\n- 1x Boat\\n- 4x Redstone Dust\\n- 1x Sticky Piston\\n- 1x End Rod\\n- 4x Redstone Repeater\\n- 1x Redstone Torch\\n- 1x Sheep\": \"Escribe algo aquí...\\nUsa '-' para crear viñetas copiables, así:\\n\\nOveja traviesa\\n- 4x Losa\\n- 1x Bote\\n- 4x Polvo de redstone\\n- 1x Pistón pegajoso\\n- 1x Varilla del End\\n- 4x Repetidor de redstone\\n- 1x Antorcha de redstone\\n- 1x Oveja\",\n  \"Edit\": \"Editar\",\n  \"Add task\": \"Agregar tarea\",\n  \"Depends on workspace\": \"Depende del espacio\\nde trabajo\",\n  \"Use old sine wave cookie implementation\": \"Usar implementación antigua de cookie en onda sinusoidal\",\n  \"Darken screen\": \"Oscurecer pantalla\",\n  \"Border\": \"Borde\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"Esto generalmente es seguro y necesario para tu navegador y la barra lateral de IA de todas formas\\nPrincipalmente útil para quienes usan bloqueo al inicio en lugar de un gestor de pantalla que lo hace (GDM, SDDM, etc.)\",\n  \"Shell conflicts killer\": \"Eliminador de conflictos del shell\",\n  \"End session\": \"Terminar sesión\",\n  \"Best match\": \"Mejor coincidencia\",\n  \"Online | Google's model\\nPro-level intelligence at the speed and pricing of Flash.\": \"En línea | Modelo de Google\\nInteligencia de nivel Pro a la velocidad y precio de Flash.\",\n  \"Font family\": \"Familia de fuente\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"El de hentai | Gran cantidad, mucho NSFW, la calidad varía mucho\",\n  \"Notes\": \"Notas\",\n  \"Actions\": \"Acciones\",\n  \"Task description\": \"Descripción de tarea\",\n  \"Eye protection\": \"Protección ocular\",\n  \"Image search\": \"Búsqueda de imágenes\",\n  \"Saved\": \"Guardado\",\n  \"Neutral\": \"Neutro\",\n  \"Conflicts with the shell's notification implementation\": \"Conflicto con la implementación de notificaciones del shell\",\n  \"Resume\": \"Reanudar\",\n  \"Low warning\": \"Advertencia baja\",\n  \"Task Manager\": \"Administrador de tareas\",\n  \"Aligns the date and quote to left, center or right depending on its position on the screen.\": \"Alinea la fecha y la cita a la izquierda, al centro o a la derecha según su posición en la pantalla.\",\n  \"Utilities & Tools\": \"Utilidades y herramientas\",\n  \"Snip\": \"Recortar\",\n  \"Time to empty:\": \"Tiempo para agotar:\",\n  \"%1 | Right-click to configure\": \"%1 | Clic derecho para configurar\",\n  \"Consider plugging in your device\": \"Considera conectar tu dispositivo\",\n  \"%1 does not require an API key\": \"%1 no requiere una clave API\",\n  \"Cancel wallpaper selection\": \"Cancelar selección de fondo de pantalla\",\n  \"See fewer\": \"Ver menos\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Clic para alternar modo claro/oscuro\\n(se aplica al elegir el fondo de pantalla)\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"Clic izq. para activar/desactivar\\nClic der. para alternar tamaño\\nDesplazar para cambiar posición\",\n  \"Window\": \"Ventana\",\n  \"Cancel\": \"Cancelar\",\n  \"Unknown Artist\": \"Artista desconocido\",\n  \"Press Super+G to open the overlay and pin the crosshair\": \"Presiona Super+G para abrir la superposición y anclar la mira\",\n  \"Set the system prompt for the model.\": \"Establece el prompt de sistema para el modelo.\",\n  \"Format\": \"Formato\",\n  \"Unpin from taskbar\": \"Desanclar de la barra de tareas\",\n  \"Charging:\": \"Cargando:\",\n  \"System updates (Arch only)\": \"Actualizaciones del sistema (solo Arch)\",\n  \"Line\": \"Línea\",\n  \"Quote\": \"Cita\",\n  \"Interface Language\": \"Idioma de la interfaz\",\n  \"Terminal: Harmonize threshold\": \"Terminal: Umbral de armonización\",\n  \"System uptime:\": \"Tiempo de actividad del sistema:\",\n  \"Enable now\": \"Activar ahora\",\n  \"Virtual Keyboard\": \"Teclado virtual\",\n  \"Click to show\": \"Clic para mostrar\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Cuando está activado, mantiene el contenido de la barra lateral derecha cargado para reducir el retraso al abrir,\\na costa de un uso constante de RAM de aproximadamente 15 MB. La importancia del retraso depende del rendimiento de tu sistema.\\nUsar un kernel personalizado como linux-cachyos podría ayudar\",\n  \"Secured\": \"Asegurado\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Precio**: gratis. Los datos se usan para entrenamiento.\\n\\n**Instrucciones**: Inicia sesión en tu cuenta de Google, permite que AI Studio cree un proyecto de Google Cloud o lo que solicite, regresa y haz clic en Obtener clave API\",\n  \"Forget\": \"Olvidar\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Se cargó el siguiente prompt de sistema\\n\\n---\\n\\n%1\",\n  \"Attach a file. Only works with Gemini.\": \"Adjuntar un archivo. Solo funciona con Gemini.\",\n  \"Temperature set to %1\": \"Temperatura establecida en %1\",\n  \"Desktop\": \"Escritorio\",\n  \"Preferred wallpaper zoom (%)\": \"Zoom preferido del fondo de pantalla (%)\",\n  \"Set FPS limit\": \"Establecer límite de FPS\",\n  \"Line-separated\": \"Separado por líneas\",\n  \"Also unlock keyring\": \"También desbloquear el llavero\",\n  \"Pick random from this folder\": \"Elegir aleatoriamente de esta carpeta\",\n  \"Font width\": \"Ancho de fuente\",\n  \"Enter a valid number\": \"Ingresa un número válido\",\n  \"Medium\": \"Mediano\",\n  \"Search with Google Lens\": \"Buscar con Google Lens\",\n  \"Wi-Fi\": \"Wi-Fi\",\n  \"Padding\": \"Relleno interior\",\n  \"Layers\": \"Capas\",\n  \"Float\": \"Flotante\",\n  \"Hover to trigger\": \"Pasar el cursor para activar\",\n  \"Other\": \"Otro\",\n  \"Focusing\": \"Enfocando\",\n  \"Session\": \"Sesión\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Duración del tiempo de espera (si no está definido por la notificación) (ms)\",\n  \"Local account\": \"Cuenta local\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Argumentos no válidos. Se deben proporcionar `key` y `value`.\",\n  \"Closet\": \"De clóset\",\n  \"Used for reading large blocks of text\": \"Usada para leer bloques grandes de texto\",\n  \"Polling interval (m)\": \"Intervalo de consulta (m)\",\n  \"Kill conflicting programs?\": \"¿Terminar programas en conflicto?\",\n  \"Light\": \"Claro\",\n  \"Translator\": \"Traductor\",\n  \"Visibility\": \"Visibilidad\",\n  \"Copy region (LMB) or annotate (RMB)\": \"Copiar región (clic izq.) o anotar (clic der.)\",\n  \"Performance Profile toggle\": \"Alternar perfil de rendimiento\",\n  \"Distro\": \"Distro\",\n  \"Add\": \"Agregar\",\n  \"Timeout (ms)\": \"Tiempo de espera (ms)\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Eso no funcionó. Consejos:\\n- Revisa tus etiquetas y configuración NSFW\\n- Si no tienes una etiqueta en mente, escribe un número de página\",\n  \"Temperature\\nChange with /temp VALUE\": \"Temperatura\\nCámbiala con /temp VALOR\",\n  \"Digits in the middle\": \"Dígitos en el centro\",\n  \"Bold\": \"Negrita\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"AI\": \"IA\",\n  \"Content adjustment\": \"Ajuste de contenido\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Sin clave API\\nEstablécela con /key TU_CLAVE_API\",\n  \"Hide sussy/anime wallpapers\": \"Ocultar fondos de pantalla sospechosos/anime\",\n  \"Always\": \"Siempre\",\n  \"Overlay: General\": \"Superposición: General\",\n  \"Background\": \"Fondo\",\n  \"Usage: %1save CHAT_NAME\": \"Uso: %1save NOMBRE_CHAT\",\n  \"Terminal\": \"Terminal\",\n  \"Left\": \"Izquierda\",\n  \"Shutdown\": \"Apagado\",\n  \"or\": \"o\",\n  \"Discharging:\": \"Descargando:\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Descanso largo: %1 minutos\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Usar algoritmo basado en distancia de Levenshtein en lugar de búsqueda difusa\",\n  \"Auto (System)\": \"Auto (Sistema)\",\n  \"Use system file picker\": \"Usar selector de archivos del sistema\",\n  \"Up %1\": \"Encendido %1\",\n  \"%1 characters\": \"%1 caracteres\",\n  \"Invalid API provider. Supported: \\n-\": \"Proveedor de API no válido. Compatibles: \\n-\",\n  \"Humidity\": \"Humedad\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Usar Hyprlock (en lugar de Quickshell)\",\n  \"Search wallpapers\": \"Buscar fondos de pantalla\",\n  \"Total duration timeout (s)\": \"Tiempo de espera total (s)\",\n  \"Check interval (mins)\": \"Intervalo de verificación (min)\",\n  \"Rectangular selection\": \"Selección rectangular\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 tareas\",\n  \"Terminal: Harmony (%)\": \"Terminal: Armonía (%)\",\n  \"Title font\": \"Fuente de título\",\n  \"Fully charged\": \"Completamente cargado\",\n  \"+%1 notifications\": \"+%1 notificaciones\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Instrucciones**: Inicia sesión en tu cuenta de Mistral, ve a Claves en la barra lateral, haz clic en Crear nueva clave\",\n  \"Unknown Application\": \"Aplicación desconocida\",\n  \"Power Profile\": \"Perfil de energía\",\n  \"Place at bottom\": \"Colocar en la parte inferior\",\n  \"Extra wallpaper zoom (%)\": \"Zoom adicional del fondo de pantalla (%)\",\n  \"Unfinished\": \"Sin terminar\",\n  \"Silent\": \"Silencioso\",\n  \"On-screen display\": \"Visualización en pantalla\",\n  \"Clear chat history\": \"Limpiar historial de chat\",\n  \"%1 notifications\": \"%1 notificaciones\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Clic derecho para configurar\",\n  \"Cookie clock settings\": \"Configuración del reloj Cookie\",\n  \"Widget: Clock\": \"Widget: Reloj\",\n  \"Keep system awake\": \"Mantener el sistema activo\",\n  \"Right to left\": \"De derecha a izquierda\",\n  \"Elements\": \"Elementos\",\n  \"Off\": \"Desactivado\",\n  \"Region selector (screen snipping/Google Lens)\": \"Selector de región (recorte de pantalla/Google Lens)\",\n  \"Search\": \"Buscar\",\n  \"Max allowed increase\": \"Incremento máximo\\npermitido\",\n  \"Total:\": \"Total:\",\n  \"12h AM/PM\": \"12h AM/PM\",\n  \"Thinking\": \"Pensando\",\n  \"Random: osu! seasonal\": \"Aleatorio: estacional de osu!\",\n  \"Constantly rotate\": \"Rotar constantemente\",\n  \"Scale (%)\": \"Escala (%)\",\n  \"Audio input\": \"Entrada de audio\",\n  \"Conflicts with the shell's system tray implementation\": \"Conflicto con la implementación de la bandeja del sistema del shell\",\n  \"General\": \"General\",\n  \"Place the corners to trigger at the bottom\": \"Colocar las esquinas de activación en la parte inferior\",\n  \"Google Lens\": \"Google Lens\",\n  \"App\": \"Aplicación\",\n  \"System sound\": \"Sonido del sistema\",\n  \"<i>No further instruction provided</i>\": \"<i>No se proporcionaron instrucciones adicionales</i>\",\n  \"Enable translator\": \"Activar traductor\",\n  \"Auto\": \"Auto\",\n  \"Switched to search mode. Continue with the user's request.\": \"Cambiado a modo búsqueda. Continúa con la solicitud del usuario.\",\n  \"Screen round corner\": \"Esquina redondeada de pantalla\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Impulso de primer plano (%)\",\n  \"Show app icons\": \"Mostrar iconos de aplicaciones\",\n  \"Full warning\": \"Advertencia de carga completa\",\n  \"More volume settings\": \"Más ajustes de volumen\",\n  \"Region height\": \"Altura de región\",\n  \"Intelligence\": \"Inteligencia\",\n  \"Unread indicator: show count\": \"Indicador de no leídos: mostrar conteo\",\n  \"Last refresh: %1\": \"Última actualización: %1\",\n  \"Launch on startup\": \"Iniciar al arranque\",\n  \"Keyboard toggle\": \"Alternar teclado\",\n  \"Bar position\": \"Posición de la barra\",\n  \"Back\": \"Atrás\",\n  \"Pick wallpaper image on your system\": \"Elegir imagen de fondo de pantalla de tu sistema\",\n  \"Get the next page of results\": \"Obtener la siguiente página de resultados\",\n  \"illogical-impulse Welcome\": \"Bienvenida de illogical-impulse\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Envía un mensaje al modelo... \\\"%1\\\" para comandos\",\n  \"Make sure you have songrec installed\": \"Asegúrate de tener songrec instalado\",\n  \"Commands\": \"Comandos\",\n  \"Pinned\": \"Anclado\",\n  \"Tint icons\": \"Colorear iconos\",\n  \"Battery full\": \"Batería llena\",\n  \"To Do:\": \"Por hacer:\",\n  \"Tint app icons\": \"Colorear iconos de aplicaciones\",\n  \"Weather\": \"Clima\",\n  \"Usage: %1load CHAT_NAME\": \"Uso: %1load NOMBRE_CHAT\",\n  \"Font size\": \"Tamaño de fuente\",\n  \"System\": \"Sistema\",\n  \"More Internet settings\": \"Más ajustes de Internet\",\n  \"Utility buttons\": \"Botones de utilidad\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/fr_FR.json",
    "content": "{\n  \"Material cookie\": \"Material cookie\",\n  \"Style: Blurred\": \"Style : Flouté\",\n  \"Unknown device\": \"Appareil inconnu\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Modifiez à tout moment avec /dark, /light, /wallpaper dans le lanceur\\nSi les couleurs du shell ne changent pas :\\n    1. Ouvrez le panneau latéral droit avec Super+N\\n    2. Cliquez sur \\\"Recharger Hyprland & Quickshell\\\" dans le coin supérieur droit\",\n  \"No pending tasks\": \"Aucune tâche en attente\",\n  \"Positioning\": \"Positionnement\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Définir la température (aléatoire) du modèle. Les valeurs vont de 0 à 2 pour Gemini, de 0 à 1 pour les autres modèles. La valeur par défaut est 0,5.\",\n  \"Critical warning\": \"Avertissement critique\",\n  \"Unknown Artist\": \"Artiste inconnu\",\n  \"Web search\": \"Recherche web\",\n  \"Load prompt from %1\": \"Charger l'invite depuis %1\",\n  \"Attach a file. Only works with Gemini.\": \"Joindre un fichier. Fonctionne uniquement avec Gemini.\",\n  \"Reboot\": \"Redémarrer\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"Clé API :\\n\\n```txt\\n%1\\n```\",\n  \"Pinned on startup\": \"Épinglé au démarrage\",\n  \"Right\": \"Droite\",\n  \"Reboot to firmware settings\": \"Redémarrer dans les paramètres du firmware\",\n  \"Automatically hide\": \"Masquer automatiquement\",\n  \"Waiting for response...\": \"En attente d'une réponse...\",\n  \"To Do\": \"À faire\",\n  \"Full\": \"Plein\",\n  \"Select Language\": \"Sélectionner la langue\",\n  \"Password\": \"Mot de passe\",\n  \"Bluetooth devices\": \"Appareils Bluetooth\",\n  \"Enable\": \"Activer\",\n  \"Elements\": \"Éléments\",\n  \"Start\": \"Démarrer\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Fond d'écran anime aléatoire (SFW) de Konachan\\nL'image est sauvegardée dans ~/Pictures/Wallpapers\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Le plus populaire | Meilleure quantité, mais la qualité peut varier énormément\",\n  \"System uptime:\": \"Temps de fonctionnement :\",\n  \"illogical-impulse Welcome\": \"Bienvenue illogical-impulse\",\n  \"Code saved to file\": \"Code sauvegardé dans le fichier\",\n  \"Info\": \"Info\",\n  \"Preferred wallpaper zoom (%)\": \"Zoom préféré du fond d'écran (%)\",\n  \"Time\": \"Heure\",\n  \"Help & Support\": \"Aide et support\",\n  \"Bubble\": \"Bulle\",\n  \"Large images | God tier quality, no NSFW.\": \"Grandes images | Qualité excellente, sans contenu NSFW.\",\n  \"Dark\": \"Sombre\",\n  \"Center clock\": \"Horloge centrée\",\n  \"Search, calculate or run\": \"Rechercher, calculer ou exécuter\",\n  \"Region height\": \"Hauteur de la région\",\n  \"Load chat\": \"Charger la conversation\",\n  \"Gives the model search capabilities (immediately)\": \"Donne au modèle des capacités de recherche (immédiatement)\",\n  \"Depends on workspace\": \"Dépend de l'espace de travail\",\n  \"Blurred style\": \"Style flouté\",\n  \"Screenshot tool\": \"Outil de capture d'écran\",\n  \"Enter password\": \"Entrer le mot de passe\",\n  \"Search the web\": \"Rechercher sur le web\",\n  \"Local only\": \"Local uniquement\",\n  \"at\": \"à\",\n  \"Math\": \"Calcul\",\n  \"Consider plugging in your device\": \"Pensez à brancher votre appareil\",\n  \"Workspaces shown\": \"Espaces de travail affichés\",\n  \"Place the corners to trigger at the bottom\": \"Placer les coins de déclenchement en bas\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Aucune clé API\\nDéfinissez-la avec /key VOTRE_CLÉ_API\",\n  \"Auto (System)\": \"Auto (Système)\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Touches fléchées pour naviguer, Entrée pour sélectionner\\nÉchap ou clic n'importe où pour annuler\",\n  \"Critically low battery\": \"Batterie critique\",\n  \"Open editor\": \"Ouvrir l'éditeur\",\n  \"%1 notifications\": \"%1 notifications\",\n  \"Region width\": \"Largeur de la région\",\n  \"Max allowed increase\": \"Augmentation maximale autorisée\",\n  \"Enable translator\": \"Activer le traducteur\",\n  \"Constantly rotate\": \"Rotation constante\",\n  \"Automatically suspends the system when battery is low\": \"Suspend automatiquement le système lorsque la batterie est faible\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Impossible de trouver un service GPS. Utilisation de la méthode de secours.\",\n  \"Qt apps\": \"Applications Qt\",\n  \"Color picker\": \"Sélecteur de couleur\",\n  \"Interface\": \"Interface\",\n  \"Tint app icons\": \"Teinter les icônes d'application\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Sélectionner la langue de l'interface utilisateur.\\n\\\"Auto\\\" utilisera les paramètres régionaux de votre système.\",\n  \"Show quote\": \"Afficher la citation\",\n  \"Local Ollama model | %1\": \"Modèle Ollama local | %1\",\n  \"Show clock\": \"Afficher l'horloge\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Utilisation : <tt>%1superpaste NB_ENTRÉES[i]</tt>\\nAjoutez <tt>i</tt> pour les images\\nExemples :\\n<tt>%1superpaste 4i</tt> pour les 4 dernières images\\n<tt>%1superpaste 7</tt> pour les 7 dernières entrées\",\n  \"Audio\": \"Audio\",\n  \"Corner style\": \"Style des coins\",\n  \"No media\": \"Aucun média\",\n  \"Unknown function call: %1\": \"Appel de fonction inconnu : %1\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"En ligne | Modèle de %1 | Fournit des réponses rapides, réactives et bien formatées. Inconvénients : pas très enclin à agir ; peut inventer des appels de fonctions inconnus\",\n  \"Volume\": \"Volume\",\n  \"Gamma\": \"Gamma\",\n  \"Medium\": \"Moyen\",\n  \"Copy code\": \"Copier le code\",\n  \"Exceeded max allowed\": \"Dépassement du maximum autorisé\",\n  \"Keep right sidebar loaded\": \"Garder le panneau latéral droit chargé\",\n  \"Left\": \"Gauche\",\n  \"High\": \"Élevé\",\n  \"Rect\": \"Rect\",\n  \"Lap\": \"Tour\",\n  \"Clear\": \"Effacer\",\n  \"Screen snip\": \"Capture d'écran\",\n  \"Reset\": \"Réinitialiser\",\n  \"Back\": \"Retour\",\n  \"Dark/Light toggle\": \"Basculer sombre/clair\",\n  \"12h am/pm\": \"12h am/pm\",\n  \"Download complete\": \"Téléchargement terminé\",\n  \"Enable blur\": \"Activer le flou\",\n  \"Second hand\": \"Trotteuse\",\n  \"Bar & screen\": \"Barre et écran\",\n  \"Discharging:\": \"Décharge :\",\n  \"Up %1\": \"Actif depuis %1\",\n  \"Low\": \"Faible\",\n  \"Hour hand\": \"Aiguille des heures\",\n  \"Clear chat history\": \"Effacer l'historique de conversation\",\n  \"Fruit Salad\": \"Salade de fruits\",\n  \"%1 Safe Storage\": \"%1 Stockage sécurisé\",\n  \"Hibernate\": \"Hibernation\",\n  \"Delete\": \"Supprimer\",\n  \"OK\": \"OK\",\n  \"Settings\": \"Paramètres\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"C'est généralement sûr et nécessaire pour votre navigateur et la barre latérale IA\\nSurtout utile pour ceux qui utilisent le verrouillage au démarrage au lieu d'un gestionnaire d'affichage (GDM, SDDM, etc.)\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Utiliser Hyprlock (au lieu de Quickshell)\",\n  \"Crosshair code (in Valorant's format)\": \"Code du réticule (au format Valorant)\",\n  \"Silent\": \"Silencieux\",\n  \"Useless buttons\": \"Boutons inutiles\",\n  \"Hover to reveal\": \"Survoler pour révéler\",\n  \"Wallpaper & Colors\": \"Fond d'écran et couleurs\",\n  \"Auto\": \"Auto\",\n  \"Visibility\": \"Visibilité\",\n  \"Shell & utilities\": \"Shell et utilitaires\",\n  \"Hollow\": \"Creux\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Utiliser le sélecteur de fichiers système\\nClic droit pour en faire le comportement par défaut\",\n  \"On-screen display\": \"Affichage à l'écran\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Search wallpapers\": \"Rechercher des fonds d'écran\",\n  \"Mic toggle\": \"Basculer le micro\",\n  \"Input\": \"Entrée\",\n  \"Also unlock keyring\": \"Déverrouiller également le trousseau\",\n  \"Configuration\": \"Configuration\",\n  \"Keep system awake\": \"Empêcher la mise en veille\",\n  \"Unknown command:\": \"Commande inconnue :\",\n  \"Anime boorus\": \"Boorus anime\",\n  \"To Do:\": \"À faire :\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Utilise Gemini pour catégoriser le fond d'écran puis choisit un préréglage basé sur celui-ci.\\nVous devrez d'abord définir la clé API Gemini dans le panneau latéral gauche.\\nLes images sont réduites pour les performances, mais par précaution,\\nne sélectionnez pas de fonds d'écran contenant des informations sensibles.\",\n  \"Bottom\": \"Bas\",\n  \"Clear the current list of images\": \"Effacer la liste d'images actuelle\",\n  \"Sunrise\": \"Lever du soleil\",\n  \"Show app icons\": \"Afficher les icônes d'application\",\n  \"Format\": \"Format\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Assurez-vous que votre lecteur prend en charge MPRIS\\nou essayez de désactiver le filtrage des lecteurs dupliqués\",\n  \"Pause\": \"Pause\",\n  \"Desktop\": \"Bureau\",\n  \"Conflicts with the shell's system tray implementation\": \"Conflit avec l'implémentation de la barre système du shell\",\n  \"Your package manager is running\": \"Votre gestionnaire de paquets est en cours d'exécution\",\n  \"Conflicts with the shell's notification implementation\": \"Conflit avec l'implémentation des notifications du shell\",\n  \"Unknown Album\": \"Album inconnu\",\n  \"Pick wallpaper image on your system\": \"Choisir une image de fond d'écran sur votre système\",\n  \"Used:\": \"Utilisé :\",\n  \"Cheat sheet\": \"Aide-mémoire\",\n  \"Clock style\": \"Style d'horloge\",\n  \"No audio source\": \"Aucune source audio\",\n  \"Paired\": \"Jumelé\",\n  \"Documentation\": \"Documentation\",\n  \"No\": \"Non\",\n  \"Pills\": \"Pilules\",\n  \"Thought\": \"Pensée\",\n  \"When this is off you'll have to click\": \"Lorsque cette option est désactivée, vous devrez cliquer\",\n  \"Select output device\": \"Sélectionner le périphérique de sortie\",\n  \"Logout\": \"Déconnexion\",\n  \"Tip: Close a window with Super+Q\": \"Astuce : Fermer une fenêtre avec Super+Q\",\n  \"Finished tasks will go here\": \"Les tâches terminées apparaîtront ici\",\n  \"Terminal: Harmony (%)\": \"Terminal : Harmonie (%)\",\n  \"Corner open\": \"Ouverture par coin\",\n  \"Shell conflicts killer\": \"Suppresseur de conflits du shell\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Contenu propre | Excellente qualité, sans NSFW\",\n  \"Scroll to change volume\": \"Défiler pour changer le volume\",\n  \"Wind\": \"Vent\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"Clé API définie\\nModifiez avec /key VOTRE_CLÉ_API\",\n  \"Neutral\": \"Neutre\",\n  \"12h AM/PM\": \"12h AM/PM\",\n  \"Number show delay when pressing Super (ms)\": \"Délai d'affichage des numéros à l'appui de Super (ms)\",\n  \"Fill\": \"Remplissage\",\n  \"Always show numbers\": \"Toujours afficher les numéros\",\n  \"Dot\": \"Point\",\n  \"Provider set to\": \"Fournisseur défini sur\",\n  \"Unknown Title\": \"Titre inconnu\",\n  \"Anime\": \"Anime\",\n  \"Refreshing (manually triggered)\": \"Actualisation (déclenchée manuellement)\",\n  \"Dock\": \"Dock\",\n  \"Require password to power off/restart\": \"Exiger un mot de passe pour éteindre/redémarrer\",\n  \"Line\": \"Ligne\",\n  \"Weather\": \"Météo\",\n  \"All-rounder | Good quality, decent quantity\": \"Polyvalent | Bonne qualité, quantité correcte\",\n  \"Scale (%)\": \"Échelle (%)\",\n  \"Copy\": \"Copier\",\n  \"Usage\": \"Utilisation\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Tapez /key pour commencer avec les modèles en ligne\\nCtrl+O pour agrandir le panneau latéral\\nCtrl+P pour détacher le panneau latéral dans une fenêtre\",\n  \"Set the tool to use for the model.\": \"Définir l'outil à utiliser pour le modèle.\",\n  \"Disable tools\": \"Désactiver les outils\",\n  \"Connect\": \"Connecter\",\n  \"Allow NSFW\": \"Autoriser le NSFW\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Inscription échouée. Veuillez inspecter manuellement avec la commande <tt>warp-cli</tt>\",\n  \"Time to full:\": \"Temps pour charger complètement :\",\n  \"Session\": \"Session\",\n  \"Services\": \"Services\",\n  \"Nothing here!\": \"Rien ici !\",\n  \"Overview\": \"Aperçu\",\n  \"Random: osu! seasonal\": \"Aléatoire : osu! saisonnier\",\n  \"If you want to somehow use fingerprint unlock...\": \"Si vous souhaitez utiliser le déverrouillage par empreinte digitale...\",\n  \"Minute hand\": \"Aiguille des minutes\",\n  \"Notifications\": \"Notifications\",\n  \"Enable if you want clocks to show seconds accurately\": \"Activer pour que les horloges affichent les secondes avec précision\",\n  \"Timer\": \"Minuteur\",\n  \"Quote settings\": \"Paramètres de citation\",\n  \"System prompt\": \"Invite système\",\n  \"Classic\": \"Classique\",\n  \"Close\": \"Fermer\",\n  \"Disconnect\": \"Déconnecter\",\n  \"Go to source (%1)\": \"Aller à la source (%1)\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Clic droit pour configurer\",\n  \"Forget\": \"Oublier\",\n  \"Output\": \"Sortie\",\n  \"Date style\": \"Style de date\",\n  \"System\": \"Système\",\n  \"Usage: %1tool TOOL_NAME\": \"Utilisation : %1tool NOM_OUTIL\",\n  \"Workspaces\": \"Espaces de travail\",\n  \"Calendar\": \"Calendrier\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Instructions** : Connectez-vous à votre compte Mistral, allez dans Clés dans la barre latérale, cliquez sur Créer une nouvelle clé\",\n  \"Volume limit\": \"Limite de volume\",\n  \"Sunset\": \"Coucher du soleil\",\n  \"Dial style\": \"Style du cadran\",\n  \"Hi there! First things first...\": \"Bonjour ! Commençons par l'essentiel...\",\n  \"Save chat to %1\": \"Sauvegarder la conversation dans %1\",\n  \"Security\": \"Sécurité\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Nombre total de tokens\\nEntrée : %1\\nSortie : %2\",\n  \"Cancel wallpaper selection\": \"Annuler la sélection du fond d'écran\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Veuillez brancher !\\nLa suspension automatique se déclenche à %1\",\n  \"Terminal: Harmonize threshold\": \"Terminal : Seuil d'harmonisation\",\n  \"Be patient...\": \"Soyez patient...\",\n  \"Utility buttons\": \"Boutons utilitaires\",\n  \"Tonal Spot\": \"Tonal Spot\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Empêche les augmentations brusques et limite le volume\",\n  \"Set the current API provider\": \"Définir le fournisseur API actuel\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Connexion échouée. Veuillez inspecter manuellement avec la commande <tt>warp-cli</tt>\",\n  \"Networking\": \"Réseau\",\n  \"Tint icons\": \"Teinter les icônes\",\n  \"Low battery\": \"Batterie faible\",\n  \"Make icons pinned by default\": \"Épingler les icônes par défaut\",\n  \"Get the next page of results\": \"Obtenir la page suivante de résultats\",\n  \"Invalid API provider. Supported: \\n-\": \"Fournisseur API invalide. Pris en charge : \\n-\",\n  \"Show \\\"Locked\\\" text\": \"Afficher le texte \\\"Verrouillé\\\"\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Tarification** : gratuit. La politique d'utilisation des données varie selon les paramètres de votre compte OpenRouter.\\n\\n**Instructions** : Connectez-vous à votre compte OpenRouter, allez dans Clés dans le menu en haut à droite, cliquez sur Créer une clé API\",\n  \"Not visible to model\": \"Non visible par le modèle\",\n  \"Lock screen\": \"Écran de verrouillage\",\n  \"Save to Downloads\": \"Sauvegarder dans Téléchargements\",\n  \"Expressive\": \"Expressif\",\n  \"Suspend at\": \"Suspendre à\",\n  \"Jump to current month\": \"Aller au mois actuel\",\n  \"Bold\": \"Gras\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Waifus seulement | Excellente qualité, quantité limitée\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Cliquer pour basculer mode clair/sombre\\n(appliqué lors du choix du fond d'écran)\",\n  \"Visualize region\": \"Visualiser la région\",\n  \"Quote\": \"Citation\",\n  \"Sleep\": \"Veille\",\n  \"Hit \\\"/\\\" to search\": \"Appuyer sur \\\"/\\\" pour rechercher\",\n  \"Hug\": \"Câlin\",\n  \"Report a Bug\": \"Signaler un bug\",\n  \"Precipitation\": \"Précipitations\",\n  \"Crosshair\": \"Réticule\",\n  \"Model set to %1\": \"Modèle défini sur %1\",\n  \"Rows\": \"Lignes\",\n  \"Top\": \"Haut\",\n  \"Long break\": \"Longue pause\",\n  \"Superpaste\": \"Superpaste\",\n  \"Screen round corner\": \"Arrondi des coins de l'écran\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"En ligne | Modèle de Google\\nModèle plus récent, plus lent que son prédécesseur mais offrant des réponses de meilleure qualité\",\n  \"Rainbow\": \"Arc-en-ciel\",\n  \"Weeb\": \"Weeb\",\n  \"Large language models\": \"Grands modèles de langage\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Modèles en ligne désactivés\\n\\nContrôlé par l'option de configuration `policies.ai`\",\n  \"Policies\": \"Politiques\",\n  \"Temperature must be between 0 and 2\": \"La température doit être comprise entre 0 et 2\",\n  \"Automatic suspend\": \"Suspension automatique\",\n  \"Extra wallpaper zoom (%)\": \"Zoom supplémentaire du fond d'écran (%)\",\n  \"GitHub\": \"GitHub\",\n  \"%1 | Right-click to configure\": \"%1 | Clic droit pour configurer\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**Tarification** : Niveau gratuit disponible avec des limites. Voir https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions** : Générez un jeton d'accès personnel GitHub avec la permission Models, puis définissez-le comme clé API ici\\n\\n**Note** : Pour l'utiliser, vous devrez définir le paramètre de température à 1\",\n  \"Edit directory\": \"Modifier le répertoire\",\n  \"Action\": \"Action\",\n  \"Search\": \"Rechercher\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Astuce : un clic droit sur un groupe\\nl'expand également\",\n  \"Bar\": \"Barre\",\n  \"Show regions of potential interest\": \"Afficher les régions d'intérêt potentiel\",\n  \"Clipboard\": \"Presse-papiers\",\n  \"Stopwatch\": \"Chronomètre\",\n  \"Enter text to translate...\": \"Entrez le texte à traduire...\",\n  \"App\": \"Application\",\n  \"Sides\": \"Côtés\",\n  \"No active player\": \"Aucun lecteur actif\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Toutes les options ne sont pas disponibles dans cette application. Vous devriez également vérifier le fichier de configuration en cliquant sur le bouton \\\"Fichier de config\\\" dans le coin supérieur gauche ou en ouvrant %1 manuellement.\",\n  \"There might be a download in progress\": \"Un téléchargement est peut-être en cours\",\n  \"Math result\": \"Résultat mathématique\",\n  \"Fidelity\": \"Fidélité\",\n  \"Prefixes\": \"Préfixes\",\n  \"Terminal\": \"Terminal\",\n  \"Incorrect password\": \"Mot de passe incorrect\",\n  \"Line-separated\": \"Séparé par des lignes\",\n  \"Always\": \"Toujours\",\n  \"☕ Break: %1 minutes\": \"☕ Pause : %1 minutes\",\n  \"Depends on sidebars\": \"Dépend des panneaux latéraux\",\n  \"Tool set to: %1\": \"Outil défini sur : %1\",\n  \"Save chat\": \"Sauvegarder la conversation\",\n  \"Crosshair overlay\": \"Superposition du réticule\",\n  \"Keybinds\": \"Raccourcis clavier\",\n  \"Launch\": \"Lancer\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Peut être meilleur si vous faites beaucoup de fautes de frappe,\\nmais les résultats peuvent être étranges et ne pas fonctionner avec les acronymes\\n(ex : \\\"GIMP\\\" pourrait ne pas vous donner le programme de dessin)\",\n  \"Choose model\": \"Choisir le modèle\",\n  \"Base URL\": \"URL de base\",\n  \"Float\": \"Flottant\",\n  \"Wallpaper parallax\": \"Parallaxe du fond d'écran\",\n  \"Invalid arguments. Must provide `command`.\": \"Arguments invalides. Vous devez fournir `command`.\",\n  \"Fully charged\": \"Complètement chargé\",\n  \"Earbang protection\": \"Protection contre les brusques hausses de volume\",\n  \"Low warning\": \"Avertissement faible\",\n  \"Advanced\": \"Avancé\",\n  \"Scroll to change brightness\": \"Défiler pour changer la luminosité\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"L'invite système suivante a été chargée\\n\\n---\\n\\n%1\",\n  \"Show next time\": \"Afficher la prochaine fois\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Outil actuel : %1\\nDéfinissez-le avec %2tool OUTIL\",\n  \"Unread indicator: show count\": \"Indicateur non lu : afficher le nombre\",\n  \"Press Super+G to toggle appearance\": \"Appuyez sur Super+G pour basculer l'apparence\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Cela n'a pas fonctionné. Conseils :\\n- Vérifiez vos tags et paramètres NSFW\\n- Si vous n'avez pas de tag en tête, tapez un numéro de page\",\n  \"Dots\": \"Points\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Volume mixer\": \"Mixeur de volume\",\n  \"Config file\": \"Fichier de configuration\",\n  \"API key set for %1\": \"Clé API définie pour %1\",\n  \"Online via %1 | %2's model\": \"En ligne via %1 | Modèle de %2\",\n  \"Shell command\": \"Commande shell\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Ces régions peuvent être des images ou des parties de l'écran ayant une certaine délimitation.\\nPas toujours précis.\\nCela est fait avec un algorithme de traitement d'image exécuté localement, sans IA.\",\n  \"Reload Hyprland & Quickshell\": \"Recharger Hyprland et Quickshell\",\n  \"Resources\": \"Ressources\",\n  \"Brightness\": \"Luminosité\",\n  \"Unknown\": \"Inconnu\",\n  \"Polling interval (ms)\": \"Intervalle de sondage (ms)\",\n  \"Lock\": \"Verrouiller\",\n  \"Thinking\": \"Réflexion\",\n  \"Approve\": \"Approuver\",\n  \"Unfinished\": \"Non terminé\",\n  \"Random: Konachan\": \"Aléatoire : Konachan\",\n  \"Connected\": \"Connecté\",\n  \"Wallpaper safety enforced\": \"Sécurité du fond d'écran activée\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Arguments invalides. Vous devez fournir `key` et `value`.\",\n  \"24h\": \"24h\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Permet d'ouvrir les panneaux latéraux en cliquant ou en survolant les coins de l'écran, quelle que soit la position de la barre\",\n  \"Bar style\": \"Style de barre\",\n  \"Load:\": \"Charge :\",\n  \"Open file link\": \"Ouvrir le lien du fichier\",\n  \"Ignored if terminal theming is not enabled\": \"Ignoré si le thème du terminal n'est pas activé\",\n  \"Shutdown\": \"Éteindre\",\n  \"Hour marks\": \"Marques des heures\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Fond saisonnier osu! aléatoire\\nL'image est sauvegardée dans ~/Pictures/Wallpapers\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"En ligne | Modèle de Google\\nRapide, peut effectuer des recherches pour des informations actualisées\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Modèle actuel : %1\\nDéfinissez-le avec %2model MODÈLE\",\n  \"Select input device\": \"Sélectionner le périphérique d'entrée\",\n  \"Connect to Wi-Fi\": \"Se connecter au Wi-Fi\",\n  \"... and %1 more\": \"... et %1 de plus\",\n  \"Cookie clock settings\": \"Paramètres de l'horloge cookie\",\n  \"Looks a bit softer and more consistent with different number of sides,\\nbut has less impressive morphing\": \"Semble un peu plus doux et plus cohérent avec différents nombres de côtés,\\nmais le morphing est moins impressionnant\",\n  \"Makes the clock always rotate. This is extremely expensive\\n(expect 50% usage on Intel UHD Graphics) and thus impractical.\": \"Fait tourner l'horloge en permanence. C'est extrêmement gourmand en ressources\\n(attendez-vous à 50% d'utilisation sur Intel UHD Graphics) et donc peu pratique.\",\n  \"Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons\": \"Ne peut être activé qu'avec le style de cadran 'Points' ou 'Plein' pour des raisons esthétiques\",\n  \"Can't be turned on when using 'Numbers' dial style for aesthetic reasons\": \"Ne peut pas être activé avec le style de cadran 'Chiffres' pour des raisons esthétiques\",\n  \"Brightness and volume\": \"Luminosité et volume\",\n  \"Choose file\": \"Choisir un fichier\",\n  \"Invalid model. Supported: \\n```\": \"Modèle invalide. Pris en charge : \\n```\",\n  \"Task Manager\": \"Gestionnaire de tâches\",\n  \"Charging:\": \"En charge :\",\n  \"Illegal increment\": \"Incrément illégal\",\n  \"Total:\": \"Total :\",\n  \"or\": \"ou\",\n  \"Battery\": \"Batterie\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Durée d'expiration (si non définie par la notification) (ms)\",\n  \"Cancel\": \"Annuler\",\n  \"Locked\": \"Verrouillé\",\n  \"Temperature: %1\": \"Température : %1\",\n  \"Hover to trigger\": \"Survoler pour déclencher\",\n  \"Command rejected by user\": \"Commande rejetée par l'utilisateur\",\n  \"User agent (for services that require it)\": \"Agent utilisateur (pour les services qui l'exigent)\",\n  \"Saved to %1\": \"Sauvegardé dans %1\",\n  \"Emojis\": \"Emojis\",\n  \"Color generation\": \"Génération de couleurs\",\n  \"Welcome app\": \"Application de bienvenue\",\n  \"Humidity\": \"Humidité\",\n  \"Page %1\": \"Page %1\",\n  \"Feels like %1\": \"Ressenti %1\",\n  \"Distro\": \"Distro\",\n  \"Transparency\": \"Transparence\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 tâches\",\n  \"Markdown test\": \"Test Markdown\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Outil invalide. Outils pris en charge :\\n- %1\",\n  \"No notifications\": \"Aucune notification\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Le hentai | Grande quantité, beaucoup de NSFW, la qualité varie énormément\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Resume\": \"Reprendre\",\n  \"Work safety\": \"Sécurité au travail\",\n  \"Temperature\\nChange with /temp VALUE\": \"Température\\nModifiez avec /temp VALEUR\",\n  \"Terminal: Foreground boost (%)\": \"Terminal : Boost du premier plan (%)\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Veilleuse | Clic droit pour basculer le mode Auto\",\n  \"Closet\": \"Garde-robe\",\n  \"Yes\": \"Oui\",\n  \"Columns\": \"Colonnes\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Pour définir une clé API, passez-la avec la commande %4\\n\\nPour afficher la clé, passez \\\"get\\\" avec la commande<br/>\\n\\n### Pour %1 :\\n\\n**Lien** : %2\\n\\n%3\",\n  \"Kill conflicting programs?\": \"Tuer les programmes en conflit ?\",\n  \"For storing API keys and other sensitive information\": \"Pour stocker les clés API et autres informations sensibles\",\n  \"Reject\": \"Rejeter\",\n  \"Set API key\": \"Définir la clé API\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Notes pour Zerochan :\\n- Vous devez entrer une couleur\\n- Définissez votre nom d'utilisateur zerochan dans l'option de configuration `sidebar.booru.zerochan.username`. Vous [pourriez être banni si vous ne le faites pas](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.) !\",\n  \"Content\": \"Contenu\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"Vertical\": \"Vertical\",\n  \"Pick a wallpaper\": \"Choisir un fond d'écran\",\n  \"Load chat from %1\": \"Charger la conversation depuis %1\",\n  \"Launch on startup\": \"Lancer au démarrage\",\n  \"Add\": \"Ajouter\",\n  \"Style: general\": \"Style : général\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Utiliser l'algorithme basé sur la distance de Levenshtein au lieu du flou\",\n  \"Shell & utilities theming must also be enabled\": \"Le thème shell et utilitaires doit également être activé\",\n  \"Workspace\": \"Espace de travail\",\n  \"Translator\": \"Traducteur\",\n  \"Free:\": \"Libre :\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Longue pause : %1 minutes\",\n  \"Value scroll\": \"Défilement de valeur\",\n  \"Bar position\": \"Position de la barre\",\n  \"Language\": \"Langue\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Point de terminaison API actuel : %1\\nDéfinissez-le avec %2mode FOURNISSEUR\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Rappel : sur la plupart des appareils, on peut toujours maintenir le bouton d'alimentation pour forcer l'extinction\\nCela rend juste un peu plus difficile les accidents\",\n  \"AI\": \"IA\",\n  \"Task description\": \"Description de la tâche\",\n  \"Add task\": \"Ajouter une tâche\",\n  \"Donate\": \"Faire un don\",\n  \"Disable NSFW content\": \"Désactiver le contenu NSFW\",\n  \"Set the system prompt for the model.\": \"Définir l'invite système pour le modèle.\",\n  \"Done\": \"Terminé\",\n  \"Focus\": \"Focus\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"Ouvrir le fichier de configuration du shell.\\nSi le bouton ne fonctionne pas ou ne s'ouvre pas dans votre éditeur préféré,\\nvous pouvez ouvrir manuellement ~/.config/illogical-impulse/config.json\",\n  \"View Markdown source\": \"Voir la source Markdown\",\n  \"Border\": \"Bordure\",\n  \"Temperature set to %1\": \"Température définie sur %1\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"En ligne | Modèle de Google\\nLe modèle polyvalent de pointe de Google, excellant dans les tâches de codage et de raisonnement complexe.\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Envoyer un message au modèle... \\\"%1\\\" pour les commandes\",\n  \"Translation goes here...\": \"La traduction apparaît ici...\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Lorsqu'activé, garde le contenu du panneau latéral droit chargé pour réduire le délai d'ouverture,\\nau coût d'environ 15 Mo de RAM constamment utilisée. L'importance du délai dépend des performances de votre système.\\nUtiliser un noyau personnalisé comme linux-cachyos pourrait aider\",\n  \"For desktop wallpapers | Good quality\": \"Pour les fonds d'écran bureau | Bonne qualité\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Focus : %1 minutes\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"L'invite système actuelle est\\n\\n---\\n\\n%1\",\n  \"About\": \"À propos\",\n  \"Quick\": \"Rapide\",\n  \"General\": \"Général\",\n  \"UV Index\": \"Indice UV\",\n  \"Force dark mode in terminal\": \"Forcer le mode sombre dans le terminal\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Glisser ou cliquer sur une région • LMB : Copier • RMB : Modifier\",\n  \"%1 characters\": \"%1 caractères\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Tarification** : gratuit. Données utilisées pour l'entraînement.\\n\\n**Instructions** : Connectez-vous à votre compte Google, autorisez AI Studio à créer un projet Google Cloud ou ce qu'il demande, revenez et cliquez sur Obtenir une clé API\",\n  \"Monochrome\": \"Monochrome\",\n  \"Details\": \"Détails\",\n  \"Issues\": \"Problèmes\",\n  \"Keyboard toggle\": \"Basculer le clavier\",\n  \"Might look ass. Unsupported.\": \"L'apparence pourrait être mauvaise. Non pris en charge.\",\n  \"Download\": \"Télécharger\",\n  \"%1 does not require an API key\": \"%1 ne nécessite pas de clé API\",\n  \"Style & wallpaper\": \"Style et fond d'écran\",\n  \"Second precision\": \"Précision des secondes\",\n  \"Group style\": \"Style de groupe\",\n  \"Break\": \"Pause\",\n  \"Run\": \"Exécuter\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Profitez-en ! Vous pouvez rouvrir l'application de bienvenue à tout moment avec <tt>Super+Shift+Alt+/</tt>. Pour ouvrir l'application de paramètres, appuyez sur <tt>Super+I</tt>\",\n  \"Interface Language\": \"Langue de l'interface\",\n  \"Game mode\": \"Mode jeu\",\n  \"Usage: %1save CHAT_NAME\": \"Utilisation : %1save NOM_CONVERSATION\",\n  \"Thin\": \"Fin\",\n  \"Light\": \"Clair\",\n  \"When not fullscreen\": \"Lorsqu'il n'est pas en plein écran\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Commandes, modifier les configs, rechercher.\\nNécessite un tour supplémentaire pour passer en mode recherche si nécessaire\",\n  \"Privacy Policy\": \"Politique de confidentialité\",\n  \"Timeout (ms)\": \"Délai d'expiration (ms)\",\n  \"Allow NSFW content\": \"Autoriser le contenu NSFW\",\n  \"Edit\": \"Modifier\",\n  \"Digits in the middle\": \"Chiffres au centre\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"En ligne | Modèle de Google\\nUn modèle Gemini 2.5 Flash optimisé pour le rapport coût-efficacité et le débit élevé.\",\n  \"Weather Service\": \"Service météo\",\n  \"Background\": \"Arrière-plan\",\n  \"Pick random from this folder\": \"Choisir aléatoirement dans ce dossier\",\n  \"Pressure\": \"Pression\",\n  \"Save\": \"Sauvegarder\",\n  \"Run command\": \"Exécuter la commande\",\n  \"Time to empty:\": \"Temps avant décharge :\",\n  \"Place at bottom\": \"Placer en bas\",\n  \"Switched to search mode. Continue with the user's request.\": \"Passé en mode recherche. Continuer avec la demande de l'utilisateur.\",\n  \"Performance Profile toggle\": \"Basculer le profil de performance\",\n  \"Sidebars\": \"Panneaux latéraux\",\n  \"Usage: %1load CHAT_NAME\": \"Utilisation : %1load NOM_CONVERSATION\",\n  \"Auto styling with Gemini\": \"Style automatique avec Gemini\",\n  \"Simple digital\": \"Numérique simple\",\n  \"No API key set for %1\": \"Aucune clé API définie pour %1\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Entrez des tags, ou \\\"%1\\\" pour les commandes\",\n  \"%1 queries pending\": \"%1 requêtes en attente\",\n  \"Discussions\": \"Discussions\",\n  \"Tray\": \"Barre système\",\n  \"Numbers\": \"Chiffres\",\n  \"Intelligence\": \"Intelligence\",\n  \"Open network portal\": \"Ouvrir le portail réseau\",\n  \"<i>No further instruction provided</i>\": \"<i>Aucune instruction supplémentaire fournie</i>\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"Langue non listée ou traductions incomplètes ?\\nVous pouvez choisir de générer des traductions avec Gemini.\\n1. Ouvrez le panneau latéral gauche avec Super+A, définissez le modèle sur Gemini (si ce n'est pas déjà le cas)\\n2. Tapez /key, appuyez sur Entrée et suivez les instructions\\n3. Tapez /key VOTRE_CLÉ_API\\n4. Tapez le code locale de votre langue ci-dessous et appuyez sur Générer\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Code de locale, ex. fr_FR, de_DE, zh_CN...\",\n  \"Select language\": \"Sélectionner la langue\",\n  \"Generate translation with Gemini\": \"Générer la traduction avec Gemini\",\n  \"Generating...\\nDon't close this window!\": \"Génération en cours...\\nNe fermez pas cette fenêtre !\",\n  \"Generate\\nTypically takes 2 minutes\": \"Générer\\nPrend généralement 2 minutes\",\n  \"Use system file picker\": \"Utiliser le sélecteur de fichiers système\",\n  \"Wallpaper selector\": \"Sélecteur de fond d'écran\",\n  \"but force at absolute corner\": \"mais forcer au coin absolu\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"Lorsque l'option précédente est désactivée et celle-ci est activée,\\nvous pouvez toujours survoler l'extrémité du coin pour ouvrir le panneau latéral,\\net la zone restante peut être utilisée pour le défilement du volume/luminosité\",\n  \"Copy path\": \"Copier le chemin\",\n  \"Windows\": \"Fenêtres\",\n  \"Regenerate\": \"Régénérer\",\n  \"Microphone\": \"Microphone\",\n  \"Unmuted\": \"Non silencieux\",\n  \"System sound\": \"Son système\",\n  \"Enable now\": \"Activer maintenant\",\n  \"Night Light\": \"Veilleuse\",\n  \"Show aim lines\": \"Afficher les lignes de visée\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Pourquoi c'est utile :\\nPour les valeurs non nulles, cela ne se déclenche pas quand vous atteignez le\\ncoin de l'écran le long du bord horizontal, mais se déclenchera\\nquand vous le faites le long du bord vertical\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"Veuillez brancher !\\nLa suspension automatique se déclenche à %1%\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"Exemple d'utilisation : jeu eroge sur un espace de travail, fenêtre Discord sombre sur un autre\",\n  \"Couldn't recognize music\": \"Impossible de reconnaître la musique\",\n  \"Automatic\": \"Automatique\",\n  \"Hint target regions\": \"Indiquer les régions cibles\",\n  \"Devices\": \"Appareils\",\n  \"Eye protection\": \"Protection oculaire\",\n  \"Japanese\": \"Japonais\",\n  \"Layers\": \"Calques\",\n  \"Listening...\": \"Écoute en cours...\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"LMB pour activer/désactiver\\nRMB pour changer la taille\\nDéfiler pour changer la position\",\n  \"Identify Music\": \"Identifier la musique\",\n  \"Quick toggles\": \"Bascules rapides\",\n  \"Hide sussy/anime wallpapers\": \"Masquer les fonds d'écran douteux/anime\",\n  \"Android\": \"Android\",\n  \"Show\": \"Afficher\",\n  \"Muted\": \"Silencieux\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Entrée audio | Clic droit pour le mixeur de volume et le sélecteur de périphérique\",\n  \"Region selector (screen snipping/Google Lens)\": \"Sélecteur de région (capture d'écran/Google Lens)\",\n  \"Total duration timeout (s)\": \"Délai de durée totale (s)\",\n  \"Music Recognition\": \"Reconnaissance musicale\",\n  \"Night Light | Right-click to configure\": \"Veilleuse | Clic droit pour configurer\",\n  \"Anti-flashbang (experimental)\": \"Anti-flashbang (expérimental)\",\n  \"Digital clock settings\": \"Paramètres de l'horloge numérique\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Peuvent être des images ou des parties de l'écran ayant une certaine délimitation.\\nPas toujours précis.\\nCela est fait avec un algorithme de traitement d'image exécuté localement, sans IA.\",\n  \"Polling interval (m)\": \"Intervalle de sondage (m)\",\n  \"Inactive\": \"Inactif\",\n  \"Authentication\": \"Authentification\",\n  \"Full warning\": \"Avertissement complet\",\n  \"Power Profile\": \"Profil d'alimentation\",\n  \"Content region\": \"Région de contenu\",\n  \"Internet\": \"Internet\",\n  \"Record\": \"Enregistrer\",\n  \"Circle selection\": \"Sélection en cercle\",\n  \"Edit quick toggles\": \"Modifier les bascules rapides\",\n  \"Virtual Keyboard\": \"Clavier virtuel\",\n  \"Music Recognized\": \"Musique reconnue\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Make sure you have songrec installed\": \"Assurez-vous que songrec est installé\",\n  \"Dark Mode\": \"Mode sombre\",\n  \"No device\": \"Aucun appareil\",\n  \"Animate time change\": \"Animer le changement d'heure\",\n  \"It may take a few seconds to update\": \"La mise à jour peut prendre quelques secondes\",\n  \"Polling interval (s)\": \"Intervalle de sondage (s)\",\n  \"Perhaps what you're listening to is too niche\": \"Ce que vous écoutez est peut-être trop de niche\",\n  \"Fahrenheit unit\": \"Unité Fahrenheit\",\n  \"Sliders\": \"Curseurs\",\n  \"Roman\": \"Romain\",\n  \"Number style\": \"Style de numéro\",\n  \"Intensity\": \"Intensité\",\n  \"Google Lens\": \"Google Lens\",\n  \"Circle\": \"Cercle\",\n  \"Hide clipboard images copied from sussy sources\": \"Masquer les images du presse-papiers copiées depuis des sources douteuses\",\n  \"Scroll to Bottom\": \"Défiler vers le bas\",\n  \"Enabled\": \"Activé\",\n  \"Nothing\": \"Rien\",\n  \"Audio input\": \"Entrée audio\",\n  \"with vertical offset\": \"avec décalage vertical\",\n  \"Padding\": \"Rembourrage\",\n  \"Please unplug the charger\": \"Veuillez débrancher le chargeur\",\n  \"Show notifications\": \"Afficher les notifications\",\n  \"Path copied\": \"Chemin copié\",\n  \"On-screen keyboard\": \"Clavier à l'écran\",\n  \"City name\": \"Nom de la ville\",\n  \"Click to cycle through power profiles\": \"Cliquer pour parcourir les profils d'alimentation\",\n  \"Recognize music | Right-click to toggle source\": \"Reconnaître la musique | Clic droit pour changer la source\",\n  \"Use old sine wave cookie implementation\": \"Utiliser l'ancienne implémentation cookie en onde sinusoïdale\",\n  \"Rectangular selection\": \"Sélection rectangulaire\",\n  \"Audio output\": \"Sortie audio\",\n  \"Applications\": \"Applications\",\n  \"Circle to Search\": \"Cercle pour rechercher\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Sortie audio | Clic droit pour le mixeur de volume et le sélecteur de périphérique\",\n  \"Enable GPS based location\": \"Activer la localisation par GPS\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"Vous devrez d'abord saisir votre clé API Gemini.\\nTapez /key dans le panneau latéral pour les instructions.\",\n  \"Sounds\": \"Sons\",\n  \"Active\": \"Actif\",\n  \"Keep awake\": \"Rester éveillé\",\n  \"Auto,\": \"Auto,\",\n  \"Normal\": \"Normal\",\n  \"Force hover open at absolute corner\": \"Forcer l'ouverture au survol au coin absolu\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Ouvrir le fichier de configuration du shell\\nAlternativement, clic droit pour copier le chemin\",\n  \"Recognize music\": \"Reconnaître la musique\",\n  \"Stroke width\": \"Largeur du trait\",\n  \"Use varying shapes for password characters\": \"Utiliser des formes variées pour les caractères du mot de passe\",\n  \"Battery full\": \"Batterie pleine\",\n  \"Pin\": \"Épingler\",\n  \"Unpin\": \"Désépingler\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/he_HE.json",
    "content": "{\n  \"Launch\": \"הפעל\",\n  \"Columns\": \"עמודות\",\n  \"Save\": \"שמור\",\n  \"Temperature: %1\": \"טמפרטורה: %1\",\n  \"Night Light | Right-click to toggle Auto mode\": \"אור לילה\",\n  \"Silent\": \"השתק\",\n  \"To Do\": \"תזכורת\",\n  \"Action\": \"פקודות\",\n  \"Search the web\": \"חיפוש באינטרנט\",\n  \"Workspace\": \"שטח עבודה\",\n  \"Desktop\": \"שולחן עבודה\",\n  \"Settings\": \"הגדרות\",\n  \"Math result\": \"תוצאה\",\n  \"Calendar\": \"לוח שנה\",\n  \"Run\": \"הפעל\",\n  \"Cancel\": \"ביטול\",\n  \"Search\": \"חיפוש\",\n  \"Battery\": \"סוללה\",\n  \"Weather\": \"מזג אוויר\",\n  \"Brightness\": \"בהירות\",\n  \"Clear\": \"נקה\",\n  \"No notifications\": \"אין התראות\",\n  \"No media\": \"אין מדיה\",\n  \"Add task\": \"הוסף תזכורת\",\n  \"Run command\": \"הפעל פקודה\",\n  \"Game mode\": \"מצב משחק\",\n  \"Reload Hyprland & Quickshell\": \"טען מחדש Hyprland ו-Quickshell\",\n  \"Task description\": \"כותרת תזכורת\",\n  \"%1 | Right-click to configure\": \"%1\",\n  \"Done\": \"הושלם\",\n  \"Keep system awake\": \"מנע שינה\",\n  \"Search, calculate or run\": \"חפש, חשב או הרץ\",\n  \"Copy\": \"העתק\",\n  \"Rows\": \"שורות\",\n  \"Session\": \"סשן\",\n  \"Notifications\": \"התראות\",\n  \"Unfinished\": \"לא הושלם\",\n  \"Add\": \"הוסף\",\n  \"Nothing here!\": \"אין תזכורות\",\n  \"Elements\": \"אלמנטים\",\n  \"Color picker\": \"בוחר צבעים\",\n  \"Sleep\": \"שינה\",\n  \"Transparency\": \"שקיפות\",\n  \"Bluetooth\": \"בלוטות׳\",\n  \"UV Index\": \"מדד קרינת UV\",\n  \"Bar\": \"סרגל\",\n  \"Format\": \"פורמט\",\n  \"Select output device\": \"בחר התקן פלט\",\n  \"Pressure\": \"לחץ\",\n  \"Volume\": \"עוצמה\",\n  \"Volume mixer\": \"הגדרות עוצמת שמע\",\n  \"Interface\": \"ממשק\",\n  \"Workspaces\": \"שטחי עבודה\",\n  \"Dark\": \"כהה\",\n  \"%1 notifications\": \"%1 התראות\",\n  \"Reboot\": \"הפעל מחדש\",\n  \"No\": \"לא\",\n  \"Wind\": \"רוח\",\n  \"Humidity\": \"לחות\",\n  \"Select Language\": \"בחר שפה\",\n  \"Copy code\": \"העתק קוד\",\n  \"Allow NSFW\": \"הצג NSFW\",\n  \"Shutdown\": \"כיבוי\",\n  \"Translation goes here...\": \"תרגום יגיע לכאן...\",\n  \"Polling interval (ms)\": \"מרווח סקר (מילישניות)\",\n  \"System prompt\": \"פרומפט מערכת\",\n  \"Base URL\": \"כתובת בסיס\",\n  \"Always show numbers\": \"הצג מספרים תמיד\",\n  \"Wallpaper parallax\": \"אפקט parallax לטפט\",\n  \"illogical-impulse Welcome\": \"ברוך הבא ל-illogical-impulse\",\n  \"Local only\": \"מקומי בלבד\",\n  \"Dark/Light toggle\": \"מצב כהה/בהיר\",\n  \"Screen snip\": \"צילום מסך\",\n  \"Style & wallpaper\": \"סגנון וטפט\",\n  \"Show app icons\": \"הצג אייקוני אפליקציות\",\n  \"Useless buttons\": \"כפתורים מיותרים\",\n  \"Scale (%)\": \"קנה מידה (%)\",\n  \"Intelligence\": \"בינה מלאכותית\",\n  \"Enable\": \"אפשר\",\n  \"Random: Konachan\": \"אקראי: Konachan\",\n  \"Yes\": \"כן\",\n  \"Documentation\": \"תיעוד\",\n  \"Unknown Artist\": \"אמן לא ידוע\",\n  \"Help & Support\": \"עזרה ותמיכה\",\n  \"Report a Bug\": \"דווח על באג\",\n  \"Sunset\": \"שקיעה\",\n  \"Weeb\": \"אנימה\",\n  \"Workspaces shown\": \"מספר שטחי עבודה מוצגים\",\n  \"Screenshot tool\": \"כלי צילום מסך\",\n  \"Enter text to translate...\": \"הכנס טקסט לתרגום\",\n  \"Unknown Album\": \"אלבום לא ידוע\",\n  \"Hug\": \"מעוגל\",\n  \"Scroll to change brightness\": \"גלול לשינוי בהירות\",\n  \"Privacy Policy\": \"מדיניות פרטיות\",\n  \"12h AM/PM\": \"12 שעות (AM/PM)\",\n  \"No audio source\": \"אין מקור שמע\",\n  \"Download\": \"הורד\",\n  \"Prefixes\": \"קידומות\",\n  \"Show next time\": \"הצג בפעם הבאה\",\n  \"Lock\": \"נעילה\",\n  \"Scroll to change volume\": \"גלול לשינוי עוצמת שמע\",\n  \"Unknown\": \"לא ידוע\",\n  \"Jump to current month\": \"קפוץ לחודש הנוכחי\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Terminal\": \"טרמינל\",\n  \"About\": \"אודות\",\n  \"Precipitation\": \"גשם\",\n  \"Keybinds\": \"קיצורי מקשים\",\n  \"Select input device\": \"בחר התקן קלט\",\n  \"When not fullscreen\": \"כשלא במסך מלא\",\n  \"Unknown Title\": \"כותרת לא ידועה\",\n  \"Task Manager\": \"מנהל משימות\",\n  \"System\": \"מערכת\",\n  \"Choose file\": \"בחר קובץ\",\n  \"Pinned on startup\": \"נעוץ בהפעלה\",\n  \"Policies\": \"מדיניות\",\n  \"View Markdown source\": \"הצג קובץ Markdown\",\n  \"Sunrise\": \"זריחה\",\n  \"Configuration\": \"הגדרות\",\n  \"Overview\": \"סקירה כללית\",\n  \"Translator\": \"מתרגם\",\n  \"Finished tasks will go here\": \"משימות שהושלמו יוצגו כאן\",\n  \"Mic toggle\": \"מיקרופון\",\n  \"Light\": \"בהיר\",\n  \"Advanced\": \"מתקדם\",\n  \"Web search\": \"חיפוש באינטרנט\",\n  \"12h am/pm\": \"12 שעות (am/pm)\",\n  \"Services\": \"שירותים\",\n  \"Donate\": \"תרום\",\n  \"Resources\": \"משאבים\",\n  \"Float\": \"צף\",\n  \"Hibernate\": \"שינה עמוקה\",\n  \"Visibility\": \"ראות\",\n  \"Delete\": \"מחק\",\n  \"Page %1\": \"עמוד %1\",\n  \"Keyboard toggle\": \"מקלדת וירטואלית\",\n  \"24h\": \"24 שעות\",\n  \"Time\": \"שעה\",\n  \"Clipboard\": \"לוח העתקה\",\n  \"or\": \"או\",\n  \"Edit\": \"ערוך\",\n  \"Emojis\": \"אימוג'ים\",\n  \"Shell & utilities\": \"יישומים וכלי מערכת\",\n  \"Reboot to firmware settings\": \"הפעל מחדש להגדרות קושחה\",\n  \"No API key set for %1\": \"לא הוגדרה מפתח API עבור %1\",\n  \"Disable NSFW content\": \"הסתר תוכן NSFW\",\n  \"Closet\": \"מוסתר\",\n  \"Depends on sidebars\": \"תלוי בסרגלים צדדיים\",\n  \"Invalid model. Supported: \\n```\": \"מודל לא חוקי. נתמכים: \\n```\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"הקלד /key לשימוש במודלים מקוונים\\nCtrl+O לפתיחת סרגל צד\\nCtrl+P להפרדה לחלון\",\n  \"Not visible to model\": \"לא נראה למודל\",\n  \"Local Ollama model | %1\": \"מודל Ollama מקומי | %1\",\n  \"Open file link\": \"פתח קישור לקובץ\",\n  \"Cheat sheet\": \"דף עזר\",\n  \"Allow NSFW content\": \"אפשר תוכן NSFW\",\n  \"%1 characters\": \"%1 תווים\",\n  \"Model set to %1\": \"מודל הוגדר ל-%1\",\n  \"Be patient...\": \"נא להמתין...\",\n  \"User agent (for services that require it)\": \"User agent (לשירותים שדורשים זאת)\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"נקודת קצה API נוכחית: %1\\nהשתמש ב-%2mode לשנות\",\n  \"For desktop wallpapers | Good quality\": \"לטפטים | איכות טובה\",\n  \"For storing API keys and other sensitive information\": \"לשמירת מפתחות API ומידע רגיש\",\n  \"Your package manager is running\": \"מנהל החבילות פועל\",\n  \"Provider set to\": \"ספק הוגדר ל\",\n  \"Color generation\": \"יצירת צבעים\",\n  \"Temperature must be between 0 and 2\": \"ערך הטמפרטורה חייב להיות בין 0 ל-2\",\n  \"Earbang protection\": \"הגנת אוזניים\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"מודל נוכחי: %1\\nשנה עם %2model\",\n  \"Waifus only | Excellent quality, limited quantity\": \"וואייפוס בלבד | איכות מעולה, כמות מוגבלת\",\n  \"Save to Downloads\": \"שמור בהורדות\",\n  \"Thinking\": \"חושב...\",\n  \"On-screen display\": \"תצוגת מסך\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 תזכורות\",\n  \"Qt apps\": \"יישומי Qt\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"הפופולרי | כמות גדולה, איכות משתנה\",\n  \"Set the system prompt for the model.\": \"הגדר פרומפט מערכת למודל\",\n  \"Markdown test\": \"בדיקת Markdown\",\n  \"Prevents abrupt increments and restricts volume limit\": \"מונע עליות חדות ומגביל עוצמת קול\",\n  \"Preferred wallpaper zoom (%)\": \"זום טפט מועדף (%)\",\n  \"Depends on workspace\": \"תלוי במקום העבודה\",\n  \"Pick wallpaper image on your system\": \"בחר טפט מהמחשב\",\n  \"Invalid API provider. Supported: \\n-\": \"ספק API לא חוקי. נתמכים: \\n-\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"הנטאי | כמות רבה, NSFW, איכות משתנה\",\n  \"Saved to %1\": \"נשמר ל-%1\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"הגדר טמפרטורה (אקראיות) למודל. ערכים 0–2 ל-Gemini, 0–1 לאחרים. ברירת מחדל 0.5\",\n  \"Close\": \"סגור\",\n  \"Tint icons\": \"אייקונים בגוון\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"סך כל הטוקנים\\nקלט: %1\\nפלט: %2\",\n  \"... and %1 more\": \"... ועוד %1\",\n  \"Lap\": \"הקפה\",\n  \"%1 does not require an API key\": \"%1 אינו דורש מפתח API\",\n  \"To Do:\": \"רשימת משימות:\",\n  \"Timer\": \"טיימר\",\n  \"AI\": \"בינה מלאכותית\",\n  \"Clear the current list of images\": \"נקה את רשימת התמונות הנוכחית\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"כדי להגדיר מפתח API, העבר אותו עם הפקודה %4\\n\\nכדי להציג את המפתח, העבר \\\"get\\\" עם הפקודה<br/>\\n\\n### עבור %1:\\n\\n**קישור**: %2\\n\\n%3\",\n  \"Time to full:\": \"זמן עד טעינה מלאה:\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"מקוון | מודל של %1 | מספק תשובות מהירות, מגיבות ומעוצבות היטב. חסרונות: לא תמיד יוזם פעולות; עלול להמציא קריאות פונקציה לא מוכרות\",\n  \"Refreshing (manually triggered)\": \"מרענן (הופעל ידנית)\",\n  \"Kill conflicting programs?\": \"להפסיק תוכנות מתנגשות?\",\n  \"System uptime:\": \"משך זמן פעילות המערכת:\",\n  \"Monochrome\": \"מונוכרום\",\n  \"Issues\": \"בעיות\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"מקוון | מודל של גוגל\\nמודל חדש יותר, איטי יותר מקודמו אך אמור לספק תשובות איכותיות יותר\",\n  \"Temperature set to %1\": \"הטמפרטורה הוגדרה ל-%1\",\n  \"Fruit Salad\": \"סלט פירות\",\n  \"No pending tasks\": \"אין משימות ממתינות\",\n  \"Vertical\": \"אנכי\",\n  \"Set the tool to use for the model.\": \"הגדר את הכלי לשימוש עבור המודל.\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"לא נמצא שירות GPS. משתמש בשיטה חלופית.\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"מפתח API:\\n\\n```txt\\n%1\\n```\",\n  \"Large images | God tier quality, no NSFW.\": \"תמונות גדולות | איכות גבוהה מאוד, ללא NSFW.\",\n  \"Language\": \"שפה\",\n  \"Weather Service\": \"שירות מזג אוויר\",\n  \"Hover to reveal\": \"העבר עכבר לחשיפה\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"גרור או לחץ על אזור • לחצן שמאלי: העתק • לחצן ימני: ערוך\",\n  \"Enter password\": \"הזן סיסמה\",\n  \"Switched to search mode. Continue with the user's request.\": \"הועבר למצב חיפוש. המשך בבקשת המשתמש.\",\n  \"Save chat to %1\": \"שמור שיחה ל-%1\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"הכלי הנוכחי: %1\\nהגדר אותו עם %2tool TOOL\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"הפרומפט הנוכחי של המערכת הוא\\n\\n---\\n\\n%1\",\n  \"Save chat\": \"שמור שיחה\",\n  \"Conflicts with the shell's notification implementation\": \"מתנגש עם מימוש ההתראות של ה-shell\",\n  \"Critically low battery\": \"סוללה נמוכה מאוד\",\n  \"Shell conflicts killer\": \"מנטרל התנגשויות shell\",\n  \"Number show delay when pressing Super (ms)\": \"השהיית הצגת מספרים בלחיצת Super (מילישניות)\",\n  \"Start\": \"התחל\",\n  \"Config file\": \"קובץ הגדרות\",\n  \"☕ Break: %1 minutes\": \"☕ הפסקה: %1 דקות\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". הערות ל-Zerochan:\\n- חובה להזין צבע\\n- הגדר את שם המשתמש שלך ב-zerochan באופציית הקונפיג `sidebar.booru.zerochan.username`. ייתכן שתחסם אם לא תעשה זאת!\",\n  \"Used:\": \"בשימוש:\",\n  \"Hi there! First things first...\": \"שלום! קודם כל...\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"ההרשמה נכשלה. נא לבדוק ידנית עם הפקודה <tt>warp-cli</tt>\",\n  \"Usage\": \"שימוש\",\n  \"Medium\": \"בינוני\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"הזן תגיות, או \\\"%1\\\" לפקודות\",\n  \"Auto (System)\": \"אוטומטי (מערכת)\",\n  \"Content\": \"תוכן\",\n  \"Performance Profile toggle\": \"החלף פרופיל ביצועים\",\n  \"Low battery\": \"סוללה חלשה\",\n  \"Region height\": \"גובה אזור\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"כלי לא חוקי. כלים נתמכים:\\n- %1\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"מפתח API מוגדר\\nשנה עם /key YOUR_API_KEY\",\n  \"Shell & utilities theming must also be enabled\": \"יש להפעיל גם ערכת נושא ליישומי מערכת\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"טפט אנימה אקראי מ-Konachan\\nהתמונה נשמרת ב-~/Pictures/Wallpapers\",\n  \"Automatic suspend\": \"השהיה אוטומטית\",\n  \"Gives the model search capabilities (immediately)\": \"נותן למודל יכולות חיפוש (מידית)\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"מקוון | מודל מתקדם של גוגל שמצטיין בקידוד ובמשימות חשיבה מורכבות.\",\n  \"Consider plugging in your device\": \"שקול לחבר את המכשיר לטעינה\",\n  \"Show regions of potential interest\": \"הצג אזורים בעלי עניין פוטנציאלי\",\n  \"<i>No further instruction provided</i>\": \"<i>לא ניתנה הנחיה נוספת</i>\",\n  \"Dock\": \"מעגן\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"נטען הפרומפט הבא של המערכת\\n\\n---\\n\\n%1\",\n  \"Always\": \"תמיד\",\n  \"Input\": \"קלט\",\n  \"Expressive\": \"מביע\",\n  \"Audio\": \"שמע\",\n  \"Usage: %1tool TOOL_NAME\": \"שימוש: %1tool TOOL_NAME\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**מחיר**: חינם. מדיניות השימוש בנתונים משתנה לפי הגדרות החשבון שלך ב-OpenRouter.\\n\\n**הוראות**: התחבר לחשבון OpenRouter, עבור ל-Keys בתפריט העליון, לחץ על Create API Key\",\n  \"Auto\": \"אוטומטי\",\n  \"Sidebars\": \"סרגלים צדדיים\",\n  \"Stopwatch\": \"סטופר\",\n  \"Distro\": \"הפצה\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"פתח את קובץ הקונפיג של ה-shell.\\nאם הכפתור לא עובד או לא נפתח בעורך המועדף עליך, תוכל לפתוח ידנית את ~/.config/illogical-impulse/config.json\",\n  \"🌿 Long break: %1 minutes\": \"🌿 הפסקה ארוכה: %1 דקות\",\n  \"Max allowed increase\": \"העלייה המרבית המותרת\",\n  \"Conflicts with the shell's system tray implementation\": \"מתנגש עם מימוש מגש המערכת של ה-shell\",\n  \"Time to empty:\": \"זמן עד התרוקנות:\",\n  \"Discharging:\": \"בהתרוקנות:\",\n  \"Pomodoro\": \"פומודורו\",\n  \"API key set for %1\": \"מפתח API הוגדר עבור %1\",\n  \"%1 queries pending\": \"%1 שאילתות ממתינות\",\n  \"Reset\": \"איפוס\",\n  \"Automatically hide\": \"הסתר אוטומטית\",\n  \"Might look ass. Unsupported.\": \"עשוי להיראות גרוע. לא נתמך.\",\n  \"Anime\": \"אנימה\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"מודלים מקוונים אינם מותרים\\n\\nנשלט על ידי אפשרות הקונפיג `policies.ai`\",\n  \"Thought\": \"מחשבה\",\n  \"GitHub\": \"גיטהאב\",\n  \"Clear chat history\": \"נקה היסטוריית שיחות\",\n  \"Resume\": \"המשך\",\n  \"Set the current API provider\": \"הגדר את ספק ה-API הנוכחי\",\n  \"Attach a file. Only works with Gemini.\": \"צרף קובץ. עובד רק עם Gemini.\",\n  \"Critical warning\": \"אזהרה קריטית\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"החיבור נכשל. נא לבדוק ידנית עם הפקודה <tt>warp-cli</tt>\",\n  \"Usage: %1load CHAT_NAME\": \"שימוש: %1load CHAT_NAME\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"מאפשר לפתוח סרגלים צדדיים בלחיצה או מעבר עכבר על פינות המסך ללא קשר למיקום הסרגל\",\n  \"Anime boorus\": \"קולקציית אנימה\",\n  \"Message the model... \\\"%1\\\" for commands\": \"שלח הודעה למודל... \\\"%1\\\" לפקודות\",\n  \"OK\": \"אישור\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"כאשר מופעל, שומר את תוכן הסרגל הצדדי הימני טעון כדי להפחית את ההשהיה בפתיחה, במחיר של כ-15MB זיכרון RAM קבוע. משמעות ההשהיה תלויה בביצועי המערכת שלך. שימוש בליבה מותאמת אישית כמו linux-cachyos עשוי לעזור\",\n  \"Rainbow\": \"קשת\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"ארגומנטים לא חוקיים. חובה לספק `key` ו-`value`.\",\n  \"Up %1\": \"מעלה %1\",\n  \"Charging:\": \"בטעינה:\",\n  \"Break\": \"הפסקה\",\n  \"App\": \"אפליקציה\",\n  \"Temperature\\nChange with /temp VALUE\": \"טמפרטורה\\nשנה עם /temp VALUE\",\n  \"Unknown command:\": \"פקודה לא ידועה:\",\n  \"There might be a download in progress\": \"ייתכן שיש הורדה בתהליך\",\n  \"Go to source (%1)\": \"עבור למקור (%1)\",\n  \"Networking\": \"רשת\",\n  \"Incorrect password\": \"סיסמה שגויה\",\n  \"Dotfiles\": \"קבצי הגדרות (dotfiles)\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**מחיר**: חינם. נתונים משמשים לאימון.\\n\\n**הוראות**: התחבר לחשבון Google, אפשר ל-AI Studio ליצור פרויקט Google Cloud או כל מה שיבקש, חזור ולחץ על Get API key\",\n  \"Pause\": \"השהה\",\n  \"Load:\": \"טען:\",\n  \"Volume limit\": \"מגבלת עוצמה\",\n  \"Focus\": \"פוקוס\",\n  \"Download complete\": \"ההורדה הושלמה\",\n  \"Welcome app\": \"אפליקציית קבלת פנים\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Low\": \"נמוך\",\n  \"Tool set to: %1\": \"הכלי הוגדר ל-%1\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"עשוי להיות טוב יותר אם תעשה הרבה טעויות הקלדה, אך התוצאות עשויות להיות מוזרות ואולי לא יעבדו עם ראשי תיבות (למשל \\\"GIMP\\\" לא בהכרח ייתן את תוכנת הציור)\",\n  \"Choose model\": \"בחר מודל\",\n  \"Large language models\": \"מודלי שפה גדולים\",\n  \"%1 Safe Storage\": \"%1 אחסון בטוח\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"אין מפתח API\\nהגדר אותו עם /key YOUR_API_KEY\",\n  \"Logout\": \"התנתק\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"תמונות נקיות | איכות מעולה, ללא NSFW\",\n  \"Total:\": \"סך הכל:\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"פקודות, עריכת קונפיגים, חיפוש.\\nנדרש סיבוב נוסף למעבר למצב חיפוש אם צריך\",\n  \"Tonal Spot\": \"נקודת טון\",\n  \"Output\": \"פלט\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**הוראות**: התחבר לחשבון Mistral, עבור ל-Keys בסרגל הצד, לחץ על Create new key\",\n  \"Unknown function call: %1\": \"קריאת פונקציה לא ידועה: %1\",\n  \"Interface Language\": \"שפת ממשק\",\n  \"Fully charged\": \"טעון במלואו\",\n  \"Free:\": \"פנוי:\",\n  \"Online via %1 | %2's model\": \"מקוון דרך %1 | מודל של %2\",\n  \"Neutral\": \"נייטרלי\",\n  \"Usage: %1save CHAT_NAME\": \"שימוש: %1save CHAT_NAME\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"מקוון | מודל Gemini 2.5 Flash של גוגל, מותאם ליעילות עלות ותפוקה גבוהה.\",\n  \"Feels like %1\": \"מרגיש כמו %1\",\n  \"Discussions\": \"דיונים\",\n  \"Low warning\": \"אזהרה ברמה נמוכה\",\n  \"Keep right sidebar loaded\": \"הסרגל הצדדי הימני תמיד יוצג\",\n  \"Hover to trigger\": \"עבור עםְ העכבר להפעלה\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"תהנה! תוכל לפתוח מחדש את אפליקציית הברוכים הבאים בכל עת עם <tt>Super+Shift+Alt+/</tt>. כדי לפתוח את אפליקציית ההגדרות, לחץ <tt>Super+I</tt>\",\n  \"Timeout (ms)\": \"פסק זמן (מילישניות)\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"אזורים כאלה יכולים להיות תמונות או חלקים מהמסך שיש בהם תוכן.\\nייתכן שלא תמיד יהיה מדויק. זה נעשה עם אלגוריתם עיבוד תמונה מקומי וללא שימוש בבינה מלאכותית.\",\n  \"Long break\": \"הפסקה ארוכה\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"מקשי החצים לניווט, Enter לבחירה\\nEsc או לחיצה בכל מקום לביטול\",\n  \"Load prompt from %1\": \"טען פרומפט מ-%1\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"זה לא עבד. טיפים:\\n- בדוק את התגיות והגדרות NSFW\\n- אם אין לך תגית, הקלד מספר עמוד\",\n  \"Corner open\": \"פינה פתוחה\",\n  \"Disable tools\": \"השבת כלים\",\n  \"Reject\": \"דחה\",\n  \"Visualize region\": \"הדמיית אזור\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**מחיר**: יש פרופיל חינם עם תקצב מוגבל. ראה https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**הוראות**: צור אסימון גישה אישי ב-GitHub עם הרשאת Models, ואז הגדר אותו כאן כמפתח API\\n\\n**הערה**: כדי להשתמש בזה תצטרך להגדיר את פרמטר הטמפרטורה ל-1\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"בחר את השפה לממשק המשתמש. \\\"אוטומטי\\\" ישתמש בשפת המערכת שלך.\",\n  \"Automatically suspends the system when battery is low\": \"משהה אוטומטית את המערכת כאשר הסוללה נמוכה\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"השתמש באלגוריתם מבוסס מרחק Levenshtein במקום חיפוש מטושטש\",\n  \"Tray\": \"אפליקציות רקע\",\n  \"Approve\": \"אשר\",\n  \"Fidelity\": \"דיוק\",\n  \"Load chat\": \"טען שיחה\",\n  \"All-rounder | Good quality, decent quantity\": \"כללי | איכות טובה, כמות אדירה\",\n  \"Info\": \"מידע\",\n  \"Corner style\": \"סגנון פינות\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"מקוון | מודל של גוגל\\nמהיר, יכול לבצע חיפושים למידע עדכני\",\n  \"Command rejected by user\": \"הפקודה נדחתה על ידי המשתמש\",\n  \"Region width\": \"רוחב אזור\",\n  \"High\": \"גבוה\",\n  \"Tint app icons\": \"אייקוני אפליקציות בגוון\",\n  \"Get the next page of results\": \"קבל את עמוד התוצאות הבא\",\n  \"Invalid arguments. Must provide `command`.\": \"ארגומנטים לא חוקיים. חובה לספק `פקודה`.\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"נא להטעין!\\nהשהיה אוטומטית תופעל ב-%1\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | קליק ימני להגדרות\",\n  \"🔴 Focus: %1 minutes\": \"🔴 פוקוס: %1 דקות\",\n  \"Set API key\": \"הגדר מפתח API\",\n  \"Load chat from %1\": \"טען שיחה מ-%1\",\n  \"Code saved to file\": \"הקוד נשמר לקובץ\",\n  \"Show clock\": \"הצגת שעון\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"השתמש בבוחר הקבצים של המערכת במקום\\nלחיצה ימנית תהפוך את התנהגות זו לברירת המחדל\",\n  \"Hour marks\": \"סימוני שעה\",\n  \"Unread indicator: show count\": \"מתריע על אי-קריאה: הצגת כמות\",\n  \"Cookie clock settings\": \"הגדרות שעון עוגיה\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"לא כל האפשרויות נמצאות באפליקציה הזו. מומלץ לבדוק את קובץ ההגדרות על ידי לחיצה על הכפתור \\\"קובץ הגדרות\\\" הממוקם למעלה בפינה השמאלית. ניתן גם לפתוח את %1 באופן ידני.\",\n  \"Border\": \"גבול\",\n  \"Classic\": \"קלאסי\",\n  \"Connected\": \"מחובר\",\n  \"Hit \\\"/\\\" to search\": \"לחץ \\\"/\\\" לחיפוש\",\n  \"Wallpaper & Colors\": \"רקע וצבעים\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\":   \"ניתן לשנות בכל עת עם /dark, /light, /wallpaper במפעיל\\nאם צבעי ה-shell לא משתנים:\\n    1. פתח את הסרגל הצדדי הימני עם Super+N\\n    2. לחץ על \\\"טען מחדש Hyprland & Quickshell\\\" בפינה הימנית העליונה\",\n  \"Launch on startup\": \"הפעל בעת הפעלה\",\n  \"Terminal: Harmonize threshold\": \"טרמינל: Harmonize threshold\",\n  \"Quick\": \"מהיר\",\n  \"Pills\": \"גלולות\",\n  \"Terminal: Harmony (%)\": \"טרמינל: הרמוניה (%)\",\n  \"Bar position\": \"מיקום הסרגל\",\n  \"Connect to Wi-Fi\": \"התחבר ל-WiFi\",\n  \"Force dark mode in terminal\": \"כפה על מצב כהה בטרמינל\",\n  \"Wallpaper safety enforced\": \"בטיחות הרקע נאכפה\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"לחץ לשינוי בין מצב בהיר/כהה\\n(מיושם לאחר בחירת רקע)\",\n  \"Dots\": \"Dots\",\n  \"Minute hand\": \"מחוג הדקות\",\n  \"Open editor\": \"פתח עורך\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"זה בדרך כלל בטוח ונדרש עבור הדפדפן שלך וסרגל הצד של הבינה המלאכותית.\\nבעיקר שימושי עבור מי שמשתמש בנעילה בעת ההפעלה במקום מנהל תצוגה שעושה זאת (GDM, SDDM וכו').\",\n  \"Security\": \"בטיחות\",\n  \"Work safety\": \"בטיחות עבודה\",\n  \"Bar & screen\": \"סרגל ומסך\",\n  \"Crosshair overlay\": \"שכבת כוונת\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"וודא שהנגן שלך תומך ב-MPRIS\\nאו נסה לכבות סינון נגנים כפולים\",\n  \"Bottom\": \"מטה\",\n  \"Press Super+G to toggle appearance\": \"לחץ סופר+G לתצוגה או הסתרה\",\n  \"Thin\": \"דק\",\n  \"Sides\": \"צדדים\",\n  \"Hollow\": \"חלול\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\":   \"שימוש: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nהוסף <tt>i</tt> כאשר תרצה תמונות\\nדוגמאות:\\n<tt>%1superpaste 4i</tt> עבור 4 התמונות האחרונות\\n<tt>%1superpaste 7</tt> עבור 7 הערכים האחרונים\",\n  \"Center clock\": \"מרכוז השעון\",\n  \"Tip: Close a window with Super+Q\": \"טיפ: סגור חלון עם Super+Q\",\n  \"Clock style\": \"סגנון השעון\",\n  \"Right\": \"ימינה\",\n  \"Exceeded max allowed\": \"חריגה מהמקסימום המותר\",\n  \"Bold\": \"מודגש\",\n  \"Group style\": \"סגנון קבוצה\",\n  \"Numbers\": \"מספרים\",\n  \"Disconnect\": \"התנתקות\",\n  \"Ignored if terminal theming is not enabled\": \"מתעלם אם ערכת נושא לטרמינל אינה מופעלת\",\n  \"Dial style\": \"סגנון חוגה\",\n  \"Auto styling with Gemini\": \"עיצוב אוטומטי עם Gemini\",\n  \"Screen round corner\": \"עיגול פינות מסך\",\n  \"When this is off you'll have to click\": \"כאשר אפשרות זו כבויה, תצטרך ללחוץ\",\n  \"Paired\": \"מחובר\",\n  \"Details\": \"פרטים\",\n  \"Crosshair code (in Valorant's format)\": \"קוד כוונת (בפורמט של Valorant)\",\n  \"Positioning\": \"מיקום\",\n  \"Also unlock keyring\": \"פתח גם את שרשרת המפתחות (keyring)\",\n  \"Make icons pinned by default\": \"הצמד אייקונים כברירת מחדל\",\n  \"Background\": \"רקע\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"רקע osu! עונתי רנדומלי\\nהתמונה נשמרת ב- ~/Pictures/Wallpapers\",\n  \"Cancel wallpaper selection\": \"בטל בחירת רקע\",\n  \"Enable if you want clocks to show seconds accurately\": \"הפעל כדי שהשעון יראה שניות במדויק\",\n  \"at\": \"ב\",\n  \"Quote settings\": \"הגדרות ציטוט\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"משך זמן (אם לא מוגדר על ידי ההתראה) (באלפיות השנייה)\",\n  \"Connect\": \"התחבר\",\n  \"Digits in the middle\": \"ספרות באמצע\",\n  \"Rect\": \"מלבן\",\n  \"Unknown device\": \"מכשיר לא ידוע\",\n  \"Back\": \"אחורה\",\n  \"General\": \"כללי\",\n  \"Bluetooth devices\": \"מכשירי Bluetooth\",\n  \"Bubble\": \"בועה\",\n  \"Hour hand\": \"מחוג השעות\",\n  \"Enable translator\": \"הפעלת מתרגם\",\n  \"Utility buttons\": \"כפתורי תועלת\",\n  \"Value scroll\": \"ערך גלילה\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"זכור כי ברוב המכשירים תמיד ניתן להחזיק את כפתור ההפעלה כדי לכבות בכוח\\nזה רק מקשה מעט על טעויות מקריות לקרות\",\n  \"Material cookie\": \"עוגיה\",\n  \"Locked\": \"נעול\",\n  \"Left\": \"שמאלה\",\n  \"Place at bottom\": \"מקם למטה\",\n  \"Lock screen\": \"מסך נעילה\",\n  \"Show \\\"Locked\\\" text\": \"הצג את הטקסט \\\"נעול\\\"\",\n  \"Open network portal\": \"פתח את פורטל הרשת\",\n  \"No active player\": \"אין נגן פעיל\",\n  \"Simple digital\": \"דיגיטלי פשוט\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"טיפ: לחיצה ימנית על קבוצה\\nמרחיבה אותה גם כן\",\n  \"Pick random from this folder\": \"בחר באקראיות מהתיקייה\",\n  \"Illegal increment\": \"הוספה לא חוקית\",\n  \"Style: Blurred\": \"סגנון: חיבור\",\n  \"Full\": \"מלא\",\n  \"Style: general\": \"סגנון: כללי\",\n  \"Use Hyprlock (instead of Quickshell)\": \"השתמש ב-Hyprlock (במקום ב-Quickshell)\",\n  \"Top\": \"מעלה\",\n  \"Random: osu! seasonal\": \"רנדומלי: osu! עונתי\",\n  \"Require password to power off/restart\": \"דרוש סיסמה לכיבוי/הפעלה מחדש\",\n  \"Fill\": \"מלא\",\n  \"Constantly rotate\": \"הסתובב לעד\",\n  \"Forget\": \"שכח\",\n  \"Pick a wallpaper\": \"בחירת רקע\",\n  \"Show quote\": \"הצג ציטוט\",\n  \"Line-separated\": \"הפרדת קו\",\n  \"Shell command\": \"פקודת של (shell)\",\n  \"Extra wallpaper zoom (%)\": \"הגדלת רקע (%)\",\n  \"Search wallpapers\": \"חיפוש רקעים\",\n  \"Bar style\": \"סגנון הסרגל\",\n  \"Dot\": \"נקודה\",\n  \"Second precision\": \"דיוק בשנייה\",\n  \"Line\": \"קו\",\n  \"Place the corners to trigger at the bottom\": \"מקם את הפינות להפעלה בתחתית\",\n  \"Second hand\": \"מחוג השניות\",\n  \"Math\": \"מתמטיקה\",\n  \"Edit directory\": \"ערוך תיקייה\",\n  \"Password\": \"סיסמה\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Foreground boost (%)\",\n  \"If you want to somehow use fingerprint unlock...\": \"אם תרצה איכשהו לפתוח בעזרת טביעת אצבע...\",\n  \"Superpaste\": \"Superpaste\",\n  \"Date style\": \"סגנון תאריך\",\n  \"Enable blur\": \"הפעל טשטוש\",\n  \"Brightness and volume\": \"בהירות ועוצמת שמע\",\n  \"Quote\": \"ציטוט\",\n  \"Mo\": \"Mo/*keep*/\",\n  \"Su\": \"Su/*keep*/\",\n  \"Sa\": \"Sa/*keep*/\",\n  \"Fr\": \"Fr/*keep*/\",\n  \"Tu\": \"Tu/*keep*/\",\n  \"Th\": \"Th/*keep*/\",\n  \"We\": \"We/*keep*/\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/id_ID.json",
    "content": "{\n  \"Material cookie\": \"Material cookie\",\n  \"Style: Blurred\": \"Gaya: Buram\",\n  \"Unknown device\": \"Perangkat tidak dikenal\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Ubah kapan saja dengan /dark, /light, /wallpaper di peluncur\\nJika warna shell tidak berubah:\\n    1. Buka sidebar kanan dengan Super+N\\n    2. Klik \\\"Muat Ulang Hyprland & Quickshell\\\" di pojok kanan atas\",\n  \"No pending tasks\": \"Tidak ada tugas tertunda\",\n  \"Positioning\": \"Posisi\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Atur temperatur (keacakan) model. Nilai berkisar antara 0 hingga 2 untuk Gemini, 0 hingga 1 untuk model lain. Bawaan adalah 0.5.\",\n  \"Critical warning\": \"Peringatan kritis\",\n  \"Unknown Artist\": \"Artis tidak dikenal\",\n  \"Web search\": \"Pencarian web\",\n  \"Load prompt from %1\": \"Muat prompt dari %1\",\n  \"Attach a file. Only works with Gemini.\": \"Lampirkan file. Hanya berfungsi dengan Gemini.\",\n  \"Reboot\": \"Mulai ulang\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"Kunci API:\\n\\n```txt\\n%1\\n```\",\n  \"Pinned on startup\": \"Disematkan saat startup\",\n  \"Right\": \"Kanan\",\n  \"Reboot to firmware settings\": \"Mulai ulang ke pengaturan firmware\",\n  \"Automatically hide\": \"Sembunyikan otomatis\",\n  \"Waiting for response...\": \"Menunggu respons...\",\n  \"To Do\": \"Tugas\",\n  \"Full\": \"Penuh\",\n  \"Select Language\": \"Pilih Bahasa\",\n  \"Password\": \"Kata sandi\",\n  \"Bluetooth devices\": \"Perangkat Bluetooth\",\n  \"Enable\": \"Aktifkan\",\n  \"Elements\": \"Elemen\",\n  \"Start\": \"Mulai\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Wallpaper Anime SFW acak dari Konachan\\nGambar disimpan di ~/Pictures/Wallpapers\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Paling populer | Kuantitas tinggi, kualitas bisa sangat bervariasi\",\n  \"System uptime:\": \"Waktu aktif sistem:\",\n  \"illogical-impulse Welcome\": \"Selamat datang di illogical-impulse\",\n  \"Code saved to file\": \"Kode disimpan ke file\",\n  \"Info\": \"Info\",\n  \"Preferred wallpaper zoom (%)\": \"Preferensi perbesaran wallpaper (%)\",\n  \"Time\": \"Waktu\",\n  \"Help & Support\": \"Bantuan & Dukungan\",\n  \"Bubble\": \"Gelembung\",\n  \"Large images | God tier quality, no NSFW.\": \"Gambar besar | Kualitas terbaik, tanpa NSFW.\",\n  \"Dark\": \"Gelap\",\n  \"Center clock\": \"Jam tengah\",\n  \"Search, calculate or run\": \"Cari, hitung atau jalankan\",\n  \"Region height\": \"Tinggi wilayah\",\n  \"Load chat\": \"Muat obrolan\",\n  \"Gives the model search capabilities (immediately)\": \"Memberikan kemampuan pencarian pada model (langsung)\",\n  \"Depends on workspace\": \"Bergantung pada ruang kerja\",\n  \"Blurred style\": \"Gaya buram\",\n  \"Screenshot tool\": \"Alat tangkapan layar\",\n  \"Enter password\": \"Masukkan kata sandi\",\n  \"Search the web\": \"Cari di web\",\n  \"Local only\": \"Lokal saja\",\n  \"at\": \"di\",\n  \"Math\": \"Matematika\",\n  \"Consider plugging in your device\": \"Pertimbangkan untuk mengisi ulang daya perangkat Anda\",\n  \"Workspaces shown\": \"Ruang kerja yang ditampilkan\",\n  \"Place the corners to trigger at the bottom\": \"Tempatkan sudut pemicu di bawah\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Tidak ada kunci API\\nAtur dengan /key KUNCI_API_ANDA\",\n  \"Auto (System)\": \"Otomatis (Sistem)\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Tombol panah untuk navigasi, Enter untuk memilih\\nEsc atau klik di mana saja untuk membatalkan\",\n  \"Critically low battery\": \"Baterai sangat rendah\",\n  \"Open editor\": \"Buka editor\",\n  \"%1 notifications\": \"%1 notifikasi\",\n  \"Region width\": \"Lebar wilayah\",\n  \"Max allowed increase\": \"Peningkatan maksimal yang diizinkan\",\n  \"Enable translator\": \"Aktifkan penerjemah\",\n  \"Constantly rotate\": \"Putar terus-menerus\",\n  \"Automatically suspends the system when battery is low\": \"Menangguhkan sistem secara otomatis ketika baterai rendah\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Tidak dapat menemukan layanan GPS. Menggunakan metode cadangan.\",\n  \"Qt apps\": \"Aplikasi Qt\",\n  \"Color picker\": \"Pemilih warna\",\n  \"Interface\": \"Antarmuka\",\n  \"Tint app icons\": \"Warnai ikon aplikasi\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Pilih bahasa untuk antarmuka pengguna.\\n\\\"Otomatis\\\" akan menggunakan pengaturan lokal sistem Anda.\",\n  \"Show quote\": \"Tampilkan kutipan\",\n  \"Local Ollama model | %1\": \"Model Ollama lokal | %1\",\n  \"Show clock\": \"Tampilkan jam\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Penggunaan: <tt>%1superpaste JUMLAH_ENTRI[i]</tt>\\nGunakan <tt>i</tt> jika Anda menginginkan gambar\\nContoh:\\n<tt>%1superpaste 4i</tt> untuk 4 gambar terakhir\\n<tt>%1superpaste 7</tt> untuk 7 entri terakhir\",\n  \"Audio\": \"Audio\",\n  \"Corner style\": \"Gaya sudut\",\n  \"No media\": \"Tidak ada media\",\n  \"Unknown function call: %1\": \"Panggilan fungsi tidak dikenal: %1\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Online | Model %1 | Memberikan jawaban cepat, responsif dan terformat dengan baik. Kekurangan: tidak terlalu bersemangat melakukan hal-hal; mungkin membuat panggilan fungsi yang tidak dikenal\",\n  \"Volume\": \"Volume\",\n  \"Medium\": \"Sedang\",\n  \"Copy code\": \"Salin kode\",\n  \"Exceeded max allowed\": \"Melebihi batas maksimal\",\n  \"Keep right sidebar loaded\": \"Pertahankan sidebar kanan dimuat\",\n  \"Left\": \"Kiri\",\n  \"High\": \"Tinggi\",\n  \"Rect\": \"Persegi\",\n  \"Lap\": \"Putaran\",\n  \"Clear\": \"Hapus\",\n  \"Screen snip\": \"Cuplikan layar\",\n  \"Reset\": \"Atur ulang\",\n  \"Back\": \"Kembali\",\n  \"Dark/Light toggle\": \"Toggle Gelap/Terang\",\n  \"12h am/pm\": \"12j am/pm\",\n  \"Download complete\": \"Unduhan selesai\",\n  \"Enable blur\": \"Aktifkan blur\",\n  \"Second hand\": \"Jarum detik\",\n  \"Bar & screen\": \"Bilah & layar\",\n  \"Discharging:\": \"Menguras:\",\n  \"Up %1\": \"Naik %1\",\n  \"Low\": \"Rendah\",\n  \"Hour hand\": \"Jarum jam\",\n  \"Clear chat history\": \"Hapus riwayat obrolan\",\n  \"Fruit Salad\": \"Salad Buah\",\n  \"%1 Safe Storage\": \"Penyimpanan Aman %1\",\n  \"Hibernate\": \"Hibernasi\",\n  \"Delete\": \"Hapus\",\n  \"OK\": \"OK\",\n  \"Settings\": \"Pengaturan\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"Ini biasanya aman dan diperlukan untuk browser dan sidebar AI Anda\\nSebagian besar berguna bagi mereka yang menggunakan kunci saat startup daripada display manager yang melakukannya (GDM, SDDM, dll.)\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Gunakan Hyprlock (menggantikan Quickshell)\",\n  \"Crosshair code (in Valorant's format)\": \"Kode Crosshair (dalam format Valorant)\",\n  \"Silent\": \"Sunyi\",\n  \"Useless buttons\": \"Tombol tidak berguna\",\n  \"Hover to reveal\": \"Arahkan untuk menampilkan\",\n  \"Wallpaper & Colors\": \"Wallpaper & Warna\",\n  \"Auto\": \"Otomatis\",\n  \"Visibility\": \"Visibilitas\",\n  \"Shell & utilities\": \"Shell & utilitas\",\n  \"Hollow\": \"Berongga\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Gunakan pemilih file sistem\\nKlik kanan untuk menjadikan ini perilaku bawaan\",\n  \"On-screen display\": \"Tampilan di layar\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Search wallpapers\": \"Cari wallpaper\",\n  \"Mic toggle\": \"Toggle mikrofon\",\n  \"Input\": \"Input\",\n  \"Also unlock keyring\": \"Juga buka kunci keyring\",\n  \"Configuration\": \"Konfigurasi\",\n  \"Keep system awake\": \"Jaga sistem tetap terjaga\",\n  \"Unknown command:\": \"Perintah tidak dikenal:\",\n  \"Anime boorus\": \"Booru anime\",\n  \"To Do:\": \"Tugas:\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Menggunakan Gemini untuk mengkategorikan wallpaper lalu memilih preset yang sesuai berdasarkan kategori.\\nAnda perlu mengatur kunci API Gemini di sidebar kiri terlebih dahulu.\\nGambar diperkecil untuk performa, tetapi agar tetap aman,\\njangan pilih wallpaper dengan informasi sensitif.\",\n  \"Bottom\": \"Bawah\",\n  \"Clear the current list of images\": \"Hapus daftar gambar saat ini\",\n  \"Sunrise\": \"Matahari terbit\",\n  \"Show app icons\": \"Tampilkan ikon aplikasi\",\n  \"Format\": \"Format\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Pastikan pemutar Anda memiliki dukungan MPRIS\\natau coba matikan penyaringan pemutar duplikat\",\n  \"Pause\": \"Jeda\",\n  \"Desktop\": \"Desktop\",\n  \"Conflicts with the shell's system tray implementation\": \"Konflik dengan implementasi system tray shell\",\n  \"Your package manager is running\": \"Manajer paket Anda sedang berjalan\",\n  \"Conflicts with the shell's notification implementation\": \"Konflik dengan implementasi notifikasi shell\",\n  \"Unknown Album\": \"Album Tidak Dikenal\",\n  \"Pick wallpaper image on your system\": \"Pilih gambar wallpaper di sistem Anda\",\n  \"Used:\": \"Digunakan:\",\n  \"Cheat sheet\": \"Lembar contekan\",\n  \"Clock style\": \"Gaya jam\",\n  \"No audio source\": \"Tidak ada sumber audio\",\n  \"Paired\": \"Dipasangkan\",\n  \"Documentation\": \"Dokumentasi\",\n  \"No\": \"Tidak\",\n  \"Pills\": \"Pil\",\n  \"Thought\": \"Pemikiran\",\n  \"When this is off you'll have to click\": \"Ketika ini dimatikan Anda harus mengklik\",\n  \"Select output device\": \"Pilih perangkat output\",\n  \"Logout\": \"Keluar\",\n  \"Tip: Close a window with Super+Q\": \"Tip: Tutup jendela dengan Super+Q\",\n  \"Finished tasks will go here\": \"Tugas terselesaikan akan ada di sini\",\n  \"Terminal: Harmony (%)\": \"Terminal: Harmoni (%)\",\n  \"Corner open\": \"Sudut pembuka\",\n  \"Shell conflicts killer\": \"Pemecah konflik shell\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Konten bersih | Kualitas sangat baik, tanpa NSFW\",\n  \"Scroll to change volume\": \"Gulir untuk mengubah volume\",\n  \"Wind\": \"Angin\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"Kunci API sudah diatur\\nUbah dengan /key KUNCI_API_ANDA\",\n  \"Neutral\": \"Netral\",\n  \"12h AM/PM\": \"12j AM/PM\",\n  \"Number show delay when pressing Super (ms)\": \"Tunda tampilan angka saat menekan Super (ms)\",\n  \"Fill\": \"Isi\",\n  \"Always show numbers\": \"Selalu tampilkan angka\",\n  \"Dot\": \"Titik\",\n  \"Provider set to\": \"Penyedia diatur ke\",\n  \"Unknown Title\": \"Judul tidak dikenal\",\n  \"Anime\": \"Anime\",\n  \"Refreshing (manually triggered)\": \"Menyegarkan (dipicu secara manual)\",\n  \"Dock\": \"Dok\",\n  \"Require password to power off/restart\": \"Memerlukan kata sandi untuk mematikan/mulai ulang\",\n  \"Line\": \"Garis\",\n  \"Weather\": \"Cuaca\",\n  \"All-rounder | Good quality, decent quantity\": \"Serbaguna | Kualitas bagus, kuantitas lumayan\",\n  \"Scale (%)\": \"Skala (%)\",\n  \"Copy\": \"Salin\",\n  \"Usage\": \"Penggunaan\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Ketik /key untuk memulai dengan model online\\nCtrl+O untuk memperluas sidebar\\nCtrl+P untuk melepas sidebar ke jendela\",\n  \"Set the tool to use for the model.\": \"Atur alat untuk digunakan oleh model.\",\n  \"Disable tools\": \"Nonaktifkan alat\",\n  \"Connect\": \"Hubungkan\",\n  \"Allow NSFW\": \"Izinkan NSFW\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Pendaftaran gagal. Silakan periksa secara manual dengan perintah <tt>warp-cli</tt>\",\n  \"Time to full:\": \"Waktu hingga penuh:\",\n  \"Session\": \"Sesi\",\n  \"Services\": \"Layanan\",\n  \"Nothing here!\": \"Di sini kosong!\",\n  \"Overview\": \"Ikhtisar\",\n  \"Random: osu! seasonal\": \"Acak: osu! musiman\",\n  \"If you want to somehow use fingerprint unlock...\": \"Jika Anda ingin menggunakan buka kunci sidik jari...\",\n  \"Minute hand\": \"Jarum menit\",\n  \"Notifications\": \"Notifikasi\",\n  \"Enable if you want clocks to show seconds accurately\": \"Aktifkan jika Anda ingin jam menampilkan detik dengan akurat\",\n  \"Timer\": \"Timer\",\n  \"Quote settings\": \"Pengaturan kutipan\",\n  \"System prompt\": \"Prompt sistem\",\n  \"Classic\": \"Klasik\",\n  \"Close\": \"Tutup\",\n  \"Disconnect\": \"Putuskan\",\n  \"Go to source (%1)\": \"Pergi ke sumber (%1)\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Klik kanan untuk mengonfigurasi\",\n  \"Forget\": \"Lupakan\",\n  \"Output\": \"Output\",\n  \"Date style\": \"Gaya tanggal\",\n  \"System\": \"Sistem\",\n  \"Usage: %1tool TOOL_NAME\": \"Penggunaan: %1tool NAMA_ALAT\",\n  \"Workspaces\": \"Ruang kerja\",\n  \"Calendar\": \"Kalender\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Instruksi**: Masuk ke akun Mistral, buka Keys di sidebar, klik Create new key\",\n  \"Volume limit\": \"Batas volume\",\n  \"Sunset\": \"Matahari terbenam\",\n  \"Dial style\": \"Gaya dial\",\n  \"Hi there! First things first...\": \"Hai! Pertama-tama...\",\n  \"Save chat to %1\": \"Simpan obrolan ke %1\",\n  \"Security\": \"Keamanan\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Total jumlah token\\nInput: %1\\nOutput: %2\",\n  \"Cancel wallpaper selection\": \"Batalkan pemilihan wallpaper\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Silakan isi ulang daya!\\nPenangguhan otomatis terpicu di %1\",\n  \"Terminal: Harmonize threshold\": \"Terminal: Ambang harmonisasi\",\n  \"Be patient...\": \"Harap bersabar...\",\n  \"Utility buttons\": \"Tombol utilitas\",\n  \"Tonal Spot\": \"Titik Tonal\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Mencegah peningkatan mendadak dan membatasi batas volume\",\n  \"Set the current API provider\": \"Atur penyedia API saat ini\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Koneksi gagal. Silakan periksa secara manual dengan perintah <tt>warp-cli</tt>\",\n  \"Networking\": \"Jaringan\",\n  \"Tint icons\": \"Warnai ikon\",\n  \"Low battery\": \"Baterai rendah\",\n  \"Make icons pinned by default\": \"Jadikan ikon disematkan secara bawaan\",\n  \"Get the next page of results\": \"Dapatkan halaman hasil berikutnya\",\n  \"Invalid API provider. Supported: \\n-\": \"Penyedia API tidak valid. Yang didukung: \\n-\",\n  \"Show \\\"Locked\\\" text\": \"Tampilkan teks \\\"Terkunci\\\"\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Harga**: gratis. Kebijakan penggunaan data bervariasi tergantung pada pengaturan akun OpenRouter Anda.\\n\\n**Instruksi**: Masuk ke akun OpenRouter, buka Keys di menu kanan atas, klik Create API Key\",\n  \"Not visible to model\": \"Tidak terlihat oleh model\",\n  \"Lock screen\": \"Kunci layar\",\n  \"Save to Downloads\": \"Simpan ke Unduhan\",\n  \"Expressive\": \"Ekspresif\",\n  \"Suspend at\": \"Tangguhkan di\",\n  \"Jump to current month\": \"Lompat ke bulan ini\",\n  \"Bold\": \"Tebal\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Hanya waifu | Kualitas sangat baik, kuantitas terbatas\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Klik untuk beralih mode terang/gelap\\n(diterapkan saat wallpaper dipilih)\",\n  \"Visualize region\": \"Visualisasikan wilayah\",\n  \"Quote\": \"Kutipan\",\n  \"Sleep\": \"Tidur\",\n  \"Hit \\\"/\\\" to search\": \"Tekan \\\"/\\\" untuk mencari\",\n  \"Hug\": \"Peluk\",\n  \"Report a Bug\": \"Laporkan Bug\",\n  \"Precipitation\": \"Curah hujan\",\n  \"Crosshair\": \"Crosshair\",\n  \"Model set to %1\": \"Model diatur ke %1\",\n  \"Rows\": \"Baris\",\n  \"Top\": \"Atas\",\n  \"Long break\": \"Istirahat panjang\",\n  \"Superpaste\": \"Superpaste\",\n  \"Screen round corner\": \"Lengkungan sudut layar\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Online | Model Google\\nModel lebih baru yang lebih lambat dari pendahulunya tapi memberikan jawaban kualitas lebih tinggi\",\n  \"Rainbow\": \"Pelangi\",\n  \"Weeb\": \"Wibu\",\n  \"Large language models\": \"Model bahasa besar\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Model online tidak diizinkan\\n\\nDikontrol oleh opsi konfigurasi `policies.ai`\",\n  \"Policies\": \"Kebijakan\",\n  \"Temperature must be between 0 and 2\": \"Temperatur harus antara 0 dan 2\",\n  \"Automatic suspend\": \"Penangguhan otomatis\",\n  \"Extra wallpaper zoom (%)\": \"Zoom wallpaper ekstra (%)\",\n  \"GitHub\": \"GitHub\",\n  \"%1 | Right-click to configure\": \"%1 | Klik kanan untuk mengonfigurasi\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**Harga**: Akses gratis tersedia  dengan batasan rate. Lihat https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instruksi**: Buat token akses pribadi GitHub dengan izin Models, lalu atur sebagai kunci API di sini\\n\\n**Catatan**: Untuk menggunakan ini Anda harus mengatur parameter temperatur ke 1\",\n  \"Edit directory\": \"Edit direktori\",\n  \"Action\": \"Aksi\",\n  \"Search\": \"Cari\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Tip: klik kanan grup\\njuga memperluasnya\",\n  \"Bar\": \"Bilah\",\n  \"Show regions of potential interest\": \"Tampilkan wilayah yang berpotensi menarik\",\n  \"Clipboard\": \"Papan klip\",\n  \"Stopwatch\": \"Stopwatch\",\n  \"Enter text to translate...\": \"Masukkan teks untuk diterjemahkan...\",\n  \"App\": \"Aplikasi\",\n  \"Sides\": \"Sisi\",\n  \"No active player\": \"Tidak ada pemutar aktif\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Tidak semua opsi tersedia di aplikasi ini. Anda juga harus memeriksa file konfigurasi dengan menekan tombol \\\"File konfigurasi\\\" di pojok kiri atas atau membuka %1 secara manual.\",\n  \"There might be a download in progress\": \"Mungkin ada unduhan yang sedang berlangsung\",\n  \"Math result\": \"Hasil matematika\",\n  \"Fidelity\": \"Fidelitas\",\n  \"Prefixes\": \"Awalan\",\n  \"Terminal\": \"Terminal\",\n  \"Incorrect password\": \"Kata sandi salah\",\n  \"Line-separated\": \"Dipisahkan baris\",\n  \"Always\": \"Selalu\",\n  \"☕ Break: %1 minutes\": \"☕ Istirahat: %1 menit\",\n  \"Depends on sidebars\": \"Bergantung pada sidebar\",\n  \"Tool set to: %1\": \"Alat diatur ke: %1\",\n  \"Save chat\": \"Simpan obrolan\",\n  \"Crosshair overlay\": \"Overlay Crosshair\",\n  \"Keybinds\": \"Pintasan keyboard\",\n  \"Launch\": \"Luncurkan\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Hasil bisa lebih baik jika Anda membuat banyak kesalahan ketik,\\nnamun hasilnya dapat menjadi aneh dan mungkin tidak bekerja dengan akronim\\n(misal: \\\"GIMP\\\" mungkin tidak merujuk ke aplikasi pengolah gambar)\",\n  \"Choose model\": \"Pilih model\",\n  \"Base URL\": \"URL Dasar\",\n  \"Float\": \"Mengambang\",\n  \"Wallpaper parallax\": \"Paralaks wallpaper\",\n  \"Invalid arguments. Must provide `command`.\": \"Argumen tidak valid. Harus menyediakan `command`.\",\n  \"Fully charged\": \"Terisi penuh\",\n  \"Earbang protection\": \"Perlindungan suara keras\",\n  \"Low warning\": \"Peringatan rendah\",\n  \"Advanced\": \"Lanjutan\",\n  \"Scroll to change brightness\": \"Gulir untuk mengubah kecerahan\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Memuat prompt sistem berikut\\n\\n---\\n\\n%1\",\n  \"Show next time\": \"Tampilkan lain kali\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Alat saat ini: %1\\nAtur dengan %2tool ALAT\",\n  \"Unread indicator: show count\": \"Indikator belum dibaca: tampilkan jumlah\",\n  \"Press Super+G to toggle appearance\": \"Tekan Super+G untuk toggle tampilan\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Itu tidak berhasil. Tips:\\n- Periksa tag dan pengaturan NSFW\\n- Jika Anda tidak memasukkan tag, masukkan nomor halaman\",\n  \"Dots\": \"Titik\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Volume mixer\": \"Mixer volume\",\n  \"Config file\": \"File konfigurasi\",\n  \"API key set for %1\": \"Kunci API diatur untuk %1\",\n  \"Online via %1 | %2's model\": \"Online via %1 | Model %2\",\n  \"Shell command\": \"Perintah shell\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Wilayah tersebut bisa berupa gambar atau bagian layar yang memiliki kandungan.\\nMungkin tidak selalu akurat.\\nIni dilakukan dengan algoritma pemrosesan gambar yang berjalan lokal dan tidak ada AI yang digunakan.\",\n  \"Reload Hyprland & Quickshell\": \"Muat Ulang Hyprland & Quickshell\",\n  \"Resources\": \"Sumber daya\",\n  \"Brightness\": \"Kecerahan\",\n  \"Unknown\": \"Tidak dikenal\",\n  \"Polling interval (ms)\": \"Interval polling (ms)\",\n  \"Lock\": \"Kunci\",\n  \"Thinking\": \"Berpikir\",\n  \"Approve\": \"Setujui\",\n  \"Unfinished\": \"Belum selesai\",\n  \"Random: Konachan\": \"Acak: Konachan\",\n  \"Connected\": \"Terhubung\",\n  \"Wallpaper safety enforced\": \"Keamanan wallpaper diberlakukan\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Argumen tidak valid. Harus menyediakan `key` dan `value`.\",\n  \"24h\": \"24j\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Memungkinkan Anda membuka sidebar dengan mengklik atau mengarahkan ke sudut layar terlepas dari posisi bilah\",\n  \"Bar style\": \"Gaya bilah\",\n  \"Load:\": \"Beban:\",\n  \"Open file link\": \"Buka tautan file\",\n  \"Ignored if terminal theming is not enabled\": \"Diabaikan jika tema terminal tidak diaktifkan\",\n  \"Shutdown\": \"Matikan\",\n  \"Hour marks\": \"Tanda jam\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Latar belakang musiman osu! acak\\nGambar disimpan ke ~/Pictures/Wallpapers\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Online | Model Google\\nCepat, dapat melakukan pencarian untuk informasi terkini\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Model saat ini: %1\\nAtur dengan %2model MODEL\",\n  \"Select input device\": \"Pilih perangkat input\",\n  \"Connect to Wi-Fi\": \"Hubungkan ke Wi-Fi\",\n  \"... and %1 more\": \"... dan %1 lainnya\",\n  \"Cookie clock settings\": \"Pengaturan jam cookie\",\n  \"Brightness and volume\": \"Kecerahan dan volume\",\n  \"Choose file\": \"Pilih file\",\n  \"Invalid model. Supported: \\n```\": \"Model tidak valid. Didukung: \\n```\",\n  \"Task Manager\": \"Manajer Tugas\",\n  \"Charging:\": \"Mengisi:\",\n  \"Illegal increment\": \"Peningkatan ilegal\",\n  \"Total:\": \"Total:\",\n  \"or\": \"atau\",\n  \"Battery\": \"Baterai\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Durasi waktu habis (jika tidak ditentukan oleh notifikasi) (ms)\",\n  \"Cancel\": \"Batal\",\n  \"Locked\": \"Terkunci\",\n  \"Temperature: %1\": \"Temperatur: %1\",\n  \"Hover to trigger\": \"Arahkan untuk memicu\",\n  \"Command rejected by user\": \"Perintah ditolak oleh pengguna\",\n  \"User agent (for services that require it)\": \"Agen pengguna (untuk layanan yang memerlukannya)\",\n  \"Saved to %1\": \"Disimpan ke %1\",\n  \"Emojis\": \"Emoji\",\n  \"Color generation\": \"Generasi warna\",\n  \"Welcome app\": \"Aplikasi selamat datang\",\n  \"Humidity\": \"Kelembaban\",\n  \"Page %1\": \"Halaman %1\",\n  \"Feels like %1\": \"Terasa seperti %1\",\n  \"Distro\": \"Distro\",\n  \"Transparency\": \"Transparansi\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 tugas\",\n  \"Markdown test\": \"Tes Markdown\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Alat tidak valid. Alat yang didukung:\\n- %1\",\n  \"No notifications\": \"Tidak ada notifikasi\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Yang hentai | Kuantitas besar, banyak NSFW, kualitas sangat bervariasi\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Resume\": \"Lanjutkan\",\n  \"Work safety\": \"Keamanan kerja\",\n  \"Temperature\\nChange with /temp VALUE\": \"Temperatur\\nUbah dengan /temp NILAI\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Peningkatan latar depan (%)\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Cahaya Malam | Klik kanan untuk toggle mode Otomatis\",\n  \"Closet\": \"Lemari\",\n  \"Yes\": \"Ya\",\n  \"Columns\": \"Kolom\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Untuk mengatur kunci API, gunakan perintah %4\\n\\nUntuk melihat kunci, gunakan \\\"get\\\" dengan perintah<br/>\\n\\n### Untuk %1:\\n\\n**Tautan**: %2\\n\\n%3\",\n  \"Kill conflicting programs?\": \"Matikan program yang berkonflik?\",\n  \"For storing API keys and other sensitive information\": \"Untuk menyimpan kunci API dan informasi sensitif lainnya\",\n  \"Reject\": \"Tolak\",\n  \"Set API key\": \"Atur kunci API\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Catatan untuk Zerochan:\\n- Anda harus memasukkan warna\\n- Atur nama pengguna zerochan Anda di opsi konfigurasi `sidebar.booru.zerochan.username`. [Akses Anda mungkin akan ditangguhkan jika tidak dilakukan](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Content\": \"Konten\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"Vertical\": \"Vertikal\",\n  \"Pick a wallpaper\": \"Pilih wallpaper\",\n  \"Load chat from %1\": \"Muat obrolan dari %1\",\n  \"Launch on startup\": \"Luncurkan saat startup\",\n  \"Add\": \"Tambah\",\n  \"Style: general\": \"Gaya: umum\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Gunakan algoritma berbasis jarak Levenshtein daripada fuzzy\",\n  \"Shell & utilities theming must also be enabled\": \"Tema shell & utilitas juga harus diaktifkan\",\n  \"Workspace\": \"Ruang kerja\",\n  \"Translator\": \"Penerjemah\",\n  \"Free:\": \"Gratis:\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Istirahat panjang: %1 menit\",\n  \"Value scroll\": \"Gulir nilai\",\n  \"Bar position\": \"Posisi bilah\",\n  \"Language\": \"Bahasa\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Endpoint API saat ini: %1\\nAtur dengan %2mode PENYEDIA\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Ingat bahwa di sebagian besar perangkat Anda selalu dapat menahan tombol daya untuk memaksa mematikan\\nIni hanya membuat sedikit lebih sulit untuk kecelakaan terjadi\",\n  \"AI\": \"AI\",\n  \"Task description\": \"Deskripsi tugas\",\n  \"Add task\": \"Tambah tugas\",\n  \"Donate\": \"Donasi\",\n  \"Disable NSFW content\": \"Nonaktifkan konten NSFW\",\n  \"Set the system prompt for the model.\": \"Atur prompt sistem untuk model.\",\n  \"Done\": \"Selesai\",\n  \"Focus\": \"Fokus\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"Buka file konfigurasi shell.\\nJika tombol tidak berfungsi atau tidak terbuka di editor favorit Anda,\\nAnda dapat membuka ~/.config/illogical-impulse/config.json secara manual\",\n  \"View Markdown source\": \"Lihat sumber Markdown\",\n  \"Border\": \"Bingkai\",\n  \"Temperature set to %1\": \"Temperatur diatur ke %1\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"Online | Model Google\\nModel serbaguna canggih Google yang unggul dalam tugas coding dan penalaran kompleks.\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Kirim pesan ke model... \\\"%1\\\" untuk perintah\",\n  \"Translation goes here...\": \"Terjemahan ada di sini...\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Ketika diaktifkan membuat konten sidebar kanan tetap dimuat untuk mengurangi keterlambatan saat membuka,\\ndengan penambahan penggunaan RAM konsisten sebanyak 15 MB. Signifikansi keterlambatan tergantung pada kinerja sistem Anda.\\nMenggunakan kernel kustom seperti linux-cachyos mungkin dapat membantu\",\n  \"For desktop wallpapers | Good quality\": \"Untuk wallpaper desktop | Kualitas bagus\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Fokus: %1 menit\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Prompt sistem saat ini adalah\\n\\n---\\n\\n%1\",\n  \"About\": \"Tentang\",\n  \"Quick\": \"Cepat\",\n  \"General\": \"Umum\",\n  \"UV Index\": \"Indeks UV\",\n  \"Force dark mode in terminal\": \"Paksa mode gelap di terminal\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Seret atau klik wilayah • Klik Kiri: Salin • Klik Kanan: Edit\",\n  \"%1 characters\": \"%1 karakter\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Harga**: gratis. Data digunakan untuk pelatihan model.\\n\\n**Instruksi**: Masuk ke akun Google, izinkan AI Studio membuat proyek Google Cloud atau apa pun yang diminta, kembali dan klik Get API key\",\n  \"Monochrome\": \"Monokrom\",\n  \"Details\": \"Detail\",\n  \"Issues\": \"Masalah\",\n  \"Keyboard toggle\": \"Toggle keyboard\",\n  \"Might look ass. Unsupported.\": \"Mungkin terlihat buruk. Tidak didukung.\",\n  \"Download\": \"Unduh\",\n  \"%1 does not require an API key\": \"%1 tidak memerlukan kunci API\",\n  \"Style & wallpaper\": \"Gaya & wallpaper\",\n  \"Second precision\": \"Presisi detik\",\n  \"Group style\": \"Gaya grup\",\n  \"Break\": \"Istirahat\",\n  \"Run\": \"Jalankan\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Enjoy! Anda dapat membuka kembali aplikasi selamat datang kapan saja dengan <tt>Super+Shift+Alt+/</tt>. Untuk membuka aplikasi pengaturan, tekan <tt>Super+I</tt>\",\n  \"Interface Language\": \"Bahasa Antarmuka\",\n  \"Game mode\": \"Mode game\",\n  \"Usage: %1save CHAT_NAME\": \"Penggunaan: %1save NAMA_OBROLAN\",\n  \"Thin\": \"Tipis\",\n  \"Light\": \"Terang\",\n  \"When not fullscreen\": \"Ketika tidak layar penuh\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Perintah, edit konfigurasi, cari.\\nMemerlukan giliran ekstra untuk beralih ke mode pencarian jika diperlukan\",\n  \"Privacy Policy\": \"Kebijakan Privasi\",\n  \"Timeout (ms)\": \"Waktu habis (ms)\",\n  \"Allow NSFW content\": \"Izinkan konten NSFW\",\n  \"Edit\": \"Edit\",\n  \"Digits in the middle\": \"Angka di tengah\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Online | Model Google\\nModel Gemini 2.5 Flash yang dioptimalkan untuk efisiensi biaya dan throughput tinggi.\",\n  \"Never\": \"Tidak pernah\",\n  \"Percentage\": \"Persentase\",\n  \"Expressive colors\": \"Warna ekspresif\",\n  \"No workspaces\": \"Tidak ada ruang kerja\",\n  \"Screen corner actions\": \"Aksi sudut layar\",\n  \"Random color generation\": \"Generasi warna acak\",\n  \"Disable session lock\": \"Nonaktifkan kunci sesi\",\n  \"Short break\": \"Istirahat pendek\",\n  \"Colors\": \"Warna\",\n  \"Monitors\": \"Monitor\",\n  \"Fuzzy\": \"Fuzzy\",\n  \"Discord\": \"Discord\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/it_IT.json",
    "content": "{\n  \"Launch\": \"Avvia\",\n  \"Columns\": \"Colonne\",\n  \"Save\": \"Salva\",\n  \"Temperature: %1\": \"Temperatura: %1\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Modalità notte\",\n  \"Silent\": \"Silenzia\",\n  \"To Do\": \"Promemoria\",\n  \"Action\": \"Comandi\",\n  \"Search the web\": \"Cerca sul web\",\n  \"Workspace\": \"Spazio di lavoro\",\n  \"Desktop\": \"Scrivania\",\n  \"Settings\": \"Impostazioni\",\n  \"Math result\": \"Risultato\",\n  \"Calendar\": \"Calendario\",\n  \"Run\": \"Esegui\",\n  \"Cancel\": \"Cancella\",\n  \"Search\": \"Cerca\",\n  \"Battery\": \"Batteria\",\n  \"Weather\": \"Meteo\",\n  \"Brightness\": \"Luminosità\",\n  \"Clear\": \"Cancella\",\n  \"No notifications\": \"Nessuna notifica\",\n  \"No media\": \"Non in riproduzione\",\n  \"Add task\": \"Aggiungi promemoria\",\n  \"Run command\": \"Esegui comando\",\n  \"Game mode\": \"Modalità gioco\",\n  \"Reload Hyprland & Quickshell\": \"Riavvia Hyprland e Quickshell\",\n  \"Task description\": \"Titolo promemoria\",\n  \"%1 | Right-click to configure\": \"%1\",\n  \"Done\": \"Completati\",\n  \"Keep system awake\": \"Mantieni schermo attivo\",\n  \"Search, calculate or run\": \"Cerca, calcola o esegui\",\n  \"Copy\": \"Copia\",\n  \"Rows\": \"Righe\",\n  \"Session\": \"Sessione\",\n  \"Notifications\": \"Notifiche\",\n  \"Unfinished\": \"Da completare\",\n  \"Add\": \"Aggiungi\",\n  \"Nothing here!\": \"Nessun promemoria\",\n  \"Mo\": \"Lu\",\n  \"Tu\": \"Ma\",\n  \"We\": \"Me\",\n  \"Th\": \"Gi\",\n  \"Fr\": \"Ve\",\n  \"Sa\": \"Sa\",\n  \"Su\": \"Do\",\n  \"Edit config\": \"Apri config.\",\n  \"Center title\": \"Titolo centrato\",\n  \"Elements\": \"Elementi\",\n  \"Color picker\": \"Selettore colore\",\n  \"Title bar\": \"Barra del titolo\",\n  \"Sleep\": \"Sospendi\",\n  \"Transparency\": \"Trasparenza\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"UV Index\": \"Indice UV\",\n  \"Bar\": \"Barra\",\n  \"Format\": \"Formato\",\n  \"Select output device\": \"Seleziona dispositivo di output\",\n  \"Pressure\": \"Pressione\",\n  \"Volume\": \"Volume\",\n  \"Volume mixer\": \"Mixer volume\",\n  \"Interface\": \"Interfaccia\",\n  \"Workspaces\": \"Spazi di lavoro\",\n  \"Dark\": \"Scuro\",\n  \"%1 notifications\": \"%1 notifiche\",\n  \"Reboot\": \"Riavvia\",\n  \"No\": \"No\",\n  \"Wind\": \"Vento\",\n  \"Humidity\": \"Umidità\",\n  \"Select Language\": \"Seleziona lingua\",\n  \"Wallpaper\": \"Sfondo\",\n  \"Copy code\": \"Copia codice\",\n  \"Allow NSFW\": \"Mostra NSFW\",\n  \"Colors & Wallpaper\": \"Sfondo e stile\",\n  \"Shutdown\": \"Spegni\",\n  \"Decorations & Effects\": \"Decorazioni e effetti\",\n  \"Translation goes here...\": \"Traduzione\",\n  \"Polling interval (ms)\": \"Intervallo di polling (ms)\",\n  \"System prompt\": \"Prompt di sistema\",\n  \"Base URL\": \"URL base\",\n  \"Always show numbers\": \"Mostra numeri\",\n  \"Wallpaper parallax\": \"Effetto parallasse sfondo\",\n  \"Plain rectangle\": \"Semplice\",\n  \"illogical-impulse Welcome\": \"Benvenuto su illogical-impulse\",\n  \"Local only\": \"Solo locale\",\n  \"Dark/Light toggle\": \"Modalità chiaro/scuro\",\n  \"Screen snip\": \"Cattura schermo\",\n  \"Style & wallpaper\": \"Sfondo e stile\",\n  \"Show app icons\": \"Mostra icone app\",\n  \"Useless buttons\": \"Tasti inutili\",\n  \"Scale (%)\": \"Dimensione (%)\",\n  \"Show background\": \"Mostra sfondo\",\n  \"Intelligence\": \"AI\",\n  \"Enable\": \"Abilita\",\n  \"Borderless\": \"Senza bordi\",\n  \"Random: Konachan\": \"Casuale: Konachan\",\n  \"Yes\": \"Sì\",\n  \"Documentation\": \"Documentazione\",\n  \"Unknown Artist\": \"Artista sconosciuto\",\n  \"Help & Support\": \"Aiuto e supporto\",\n  \"Report a Bug\": \"Segnala un bug\",\n  \"Sunset\": \"Tramonto\",\n  \"Weeb\": \"Anime\",\n  \"Shell windows\": \"Finestre\",\n  \"Workspaces shown\": \"Numero spazi di lavoro\",\n  \"Screenshot tool\": \"Cattura schermo\",\n  \"Enter text to translate...\": \"Inserisci il testo qui\",\n  \"Unknown Album\": \"Album sconosciuto\",\n  \"Hug\": \"Stondato\",\n  \"Scroll to change brightness\": \"Scorri per cambiare luminosità\",\n  \"Privacy Policy\": \"Privacy policy\",\n  \"12h AM/PM\": \"12 ore (AM/PM)\",\n  \"Material palette\": \"Tavolozza dei colori\",\n  \"No audio source\": \"Nessuna sorgente audio\",\n  \"Download\": \"Scarica\",\n  \"Prefixes\": \"Prefissi\",\n  \"Show next time\": \"Mostra al prossimo avvio\",\n  \"Lock\": \"Blocca\",\n  \"Scroll to change volume\": \"Scorri per cambiare volume\",\n  \"Unknown\": \"Sconosciuto\",\n  \"Jump to current month\": \"Torna al mese corrente\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Terminal\": \"Terminale\",\n  \"About\": \"Informazioni\",\n  \"Precipitation\": \"Precipitazioni\",\n  \"Keybinds\": \"Comandi\",\n  \"Select input device\": \"Seleziona dispositivo di input\",\n  \"When not fullscreen\": \"Tranne a schermo intero\",\n  \"Unknown Title\": \"Titolo sconosciuto\",\n  \"Task Manager\": \"Gestione attività\",\n  \"System\": \"Sistema\",\n  \"Choose file\": \"Scegli file\",\n  \"Pinned on startup\": \"Fissa sullo schermo\",\n  \"Policies\": \"Servizi\",\n  \"View Markdown source\": \"Mostra Markdown\",\n  \"Sunrise\": \"Alba\",\n  \"Configuration\": \"Configurazione\",\n  \"Overview\": \"Panoramica\",\n  \"Translator\": \"Traduttore\",\n  \"Finished tasks will go here\": \"Nessun promemoria\",\n  \"Mic toggle\": \"Microfono\",\n  \"Light\": \"Chiaro\",\n  \"Advanced\": \"Avanzate\",\n  \"Web search\": \"Cerca sul web\",\n  \"12h am/pm\": \"12 ore (am/pm)\",\n  \"Services\": \"Servizi\",\n  \"Donate\": \"Supporta\",\n  \"Resources\": \"Risorse\",\n  \"Float\": \"Fluttuante\",\n  \"Fake screen rounding\": \"Bordi curvi schermo\",\n  \"Hibernate\": \"Iberna\",\n  \"Visibility\": \"Visibilità\",\n  \"Delete\": \"Elimina\",\n  \"Style\": \"Stile\",\n  \"Page %1\": \"Pagina %1\",\n  \"Keyboard toggle\": \"Tastiera virtuale\",\n  \"Buttons\": \"Pulsanti\",\n  \"24h\": \"24 ore\",\n  \"Time\": \"Ora\",\n  \"Clipboard\": \"Appunti\",\n  \"or\": \"o\",\n  \"Edit\": \"Modifica\",\n  \"Emojis\": \"Emoji\",\n  \"Shell & utilities\": \"App e shell\",\n  \"Reboot to firmware settings\": \"Riavvia alle impostazioni firmware\",\n  \"No API key set for %1\": \"Chiave API non impostata per %1\",\n  \"Disable NSFW content\": \"Nascondi contenuti NSFW\",\n  \"Closet\": \"Nascosto\",\n  \"Depends on sidebars\": \"Abilita per barre laterali\",\n  \"Invalid model. Supported: \\n```\": \"Modello non valido. Supportati: \\n```\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Usa /key per utilizzare i modelli online\\nCtrl+O per espandere\\nCtrl+P per separare in finestra\",\n  \"Not visible to model\": \"Non visible al modello\",\n  \"Local Ollama model | %1\": \"Modello Ollama locale | %1\",\n  \"Open file link\": \"Apri link al file\",\n  \"Waiting for response...\": \"In attesa di risposta...\",\n  \"Cheat sheet\": \"Prontuario\",\n  \"Allow NSFW content\": \"Mostra contenuti NSFW\",\n  \"%1 characters\": \"%1 caratteri\",\n  \"Model set to %1\": \"Modello impostato su %1\",\n  \"Be patient...\": \"Attendi...\",\n  \"User agent (for services that require it)\": \"User agent (usato dai servizi)\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Endpoint API: %1\\nUsa %2mode per cambiarlo\",\n  \"For desktop wallpapers | Good quality\": \"Per sfondi del desktop | Buona qualità\",\n  \"For storing API keys and other sensitive information\": \"Gestione credenziali e informazioni sensibili\",\n  \"Your package manager is running\": \"Il package manager è in esecuzione\",\n  \"Provider set to\": \"Provider impostato su\",\n  \"Color generation\": \"Tavolozza dei colori\",\n  \"Temperature must be between 0 and 2\": \"Il valore della temperatura deve essere fra 0 e 2\",\n  \"Earbang protection\": \"Protezione udito\",\n  \"Alternatively use /dark, /light, /img in the launcher\": \"Oppure usa /dark, /light, /img nel launcher\",\n  \"Change any time later with /dark, /light, /img in the launcher\": \"Oppure usa /dark, /light, /img nel launcher\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Modello attuale: %1\\nUsa %2model per cambiarlo\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Solo waifu | Qualità eccellente, quantità limitata\",\n  \"Save to Downloads\": \"Salva in Scaricati\",\n  \"Thinking\": \"Ragionando\",\n  \"On-screen display\": \"Popup di sistema\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 promemoria\",\n  \"Qt apps\": \"Applicazioni Qt\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Il più usato | Quantità elevata, ma la qualità potrebbe variare totalmente\",\n  \"Set the system prompt for the model.\": \"Imposta il prompt di sistema per il modello.\",\n  \"Markdown test\": \"Test Markdown\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Impedice aumenti improvvisi del volume e ne imposta un limite.\",\n  \"Preferred wallpaper zoom (%)\": \"Zoom sfondo (%)\",\n  \"Depends on workspace\": \"Abilita per spazi di lavoro\",\n  \"Note: turning off can hurt readability\": \"Nota: disattivarlo potrebbe ridurre la leggibilità\",\n  \"Pick wallpaper image on your system\": \"Seleziona un'immagine nel tuo sistema\",\n  \"Invalid API provider. Supported: \\n-\": \"Provider API non valido. Supportati: \\n-\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Hentai | Quantità elevata, NSFW, la qualità potrebbe variare totalmente\",\n  \"Saved to %1\": \"Salvato in %1\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Imposta la temperatura (casualità) del modello. Il valore va da 0 a 2 per Gemini, e da 0 a 1 per gli altri modelli. Il valore predefinito è 0.5.\",\n  \"Close\": \"Chiudi\",\n  \"Might look ass. Unsupported.\": \"Potrebbe fare schifo. Non supportato.\",\n  \"API key set for %1\": \"Chiave API impostata per %1\",\n  \"Temperature\\nChange with /temp VALUE\": \"Temperatura\\nUsa /temp per cambiarla\",\n  \"%1 does not require an API key\": \"%1 non richiede una chiave API\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Modelli online disattivati\\n\\nDisattiva l'opzione \\\"Solo locale\\\"\",\n  \"Set the current API provider\": \"Imposta il provider API\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Numero token totali\\nInput: %1\\nOutput: %2\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Sfondo anime SFW casuale da Konachan\\nL'immagine verrà salvata in ~/Immagini/Wallpapers\",\n  \"Hover to reveal\": \"Mostra col passaggio del mouse\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Usa i tasti freccia per navigare, Invio per selezionare\\nEsc o clicca qualsiasi punto per uscire\",\n  \"Save chat to %1\": \"Salva chat in %1\",\n  \"Choose model\": \"Seleziona modello\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Nessuna chiave API\\nUsa /key per impostarla\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"Chiave API:\\n\\n```txt\\n%1\\n```\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Usa algoritmo di Levenshtein al posto di fuzzy\",\n  \"Download complete\": \"Download completato\",\n  \"Tip: Hide icons and always show numbers for\\nthe classic illogical-impulse experience\": \"Suggerimento: Nascondi le icone e mostra i numeri se vuoi\\nun'esperienza fedele all'originale\",\n  \"Usage\": \"Istruzioni\",\n  \"Set API key\": \"Imposta chiave API\",\n  \"Networking\": \"Rete\",\n  \"Volume limit\": \"Limite volume\",\n  \"Temperature set to %1\": \"Temperatura impostata a %1\",\n  \"Large images | God tier quality, no NSFW.\": \"Immagini grandi | Qualità perfetta, no NSFW.\",\n  \"Shell & utilities theming must also be enabled\": \"\\\"App e shell\\\" deve essere abilitato\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"Chiave API impostata\\nUsa /key per cambiarla\",\n  \"All-rounder | Good quality, decent quantity\": \"Versatile | Qualità buona, quantità discreta\",\n  \"Large language models\": \"Intelligenza artificiale\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Può aiutare in caso di refusi,\\nma i risultati potrebbero non essere accurati\\n(es. \\\"GIMP\\\" potrebbe non restituire l'omonimo programma)\",\n  \"Automatic suspend\": \"Sospensione automatica\",\n  \"Max allowed increase\": \"Aumento massimo consentito\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Ricarica la batteria!\\nSospensione automatica in %1\",\n  \"Weather Service\": \"Servizio meteo\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Il prompt di sistema attuale è\\n\\n---\\n\\n%1\",\n  \"%1 Safe Storage\": \"Portachiavi di %1\",\n  \"Load prompt from %1\": \"Carica prompt da %1\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Nessun risultato. Suggerimenti:\\n- Controlla i tag e le impostazioni NSFW\\n- Se non hai un tag in mente, inserici il numero di pagina\",\n  \"Load chat\": \"Carica chat\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Prezzo**: gratuito. I tuoi dati vengono usati per training.\\n\\n**Istruzioni**: Accedi al tuo account Google, abilita i permessi richiesti da AI Studio, torna indietro e seleziona \\\"Get API key\\\"\",\n  \"Online via %1 | %2's model\": \"Online tramite %1 | Modello di %2\",\n  \"Get the next page of results\": \"Ottieni la pagina successiva dei risultati\",\n  \"Unknown function call: %1\": \"Funzione sconosciuta: %1\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Servizio GPS non disponibile. Verrà utilizzata la città preimpostata.\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Registrazione fallita. Verifica l'errore manualmente col comando <tt>warp-cli</tt>\",\n  \"Code saved to file\": \"Codice salvato\",\n  \"Low warning\": \"Livello basso\",\n  \"Clear the current list of images\": \"Elimina la lista corrente di immagini\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Argomenti non validi. Il comando richiede `key` e `value`.\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Connessione fallita. Verifica l'errore manualmente col comando <tt>warp-cli</tt>\",\n  \"Unknown command:\": \"Comando sconosciuto:\",\n  \"Message the model... \\\"%1\\\" for commands\": \"\\\"%1\\\" per mostrare i comandi\",\n  \"Load chat from %1\": \"Carica chat da %1\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Prezzo**: gratuito. Le policy di trattamento dei dati potrebbero variare in base alle impostazioni del tuo account OpenRouter.\\n\\n**Istruzioni**: Accedi al tuo account OpenRouter, vai nella sezione \\\"Keys\\\" dal menu in alto, clicca \\\"Create API Key\\\"\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"\\\"%1\\\" per mostrare i comandi\",\n  \"Show regions of potential interest\": \"Mostra regioni d'interesse\",\n  \"Critical warning\": \"Livello critico\",\n  \"Go to source (%1)\": \"Vai alla fonte (%1)\",\n  \"Automatically suspends the system when battery is low\": \"Sospende automaticamente il sistema quando il livello della batteria è basso\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Roba pulita | Qualità eccellente, no NSFW\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Note per Zerochan:\\n- Devi inserire un colore\\n- Imposta il tuo nome utente di zerochan nell'opzione `sidebar.booru.zerochan.username`. Potresti [venire bannato se non lo fai](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Critically low battery\": \"Batteria scarica\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Il seguente prompt di sistema è stato caricato\\n\\n---\\n\\n%1\",\n  \"Suspend at\": \"Sospendi al\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Queste regioni potrebbero essere immagini o parti di schermo contenute.\\nPotrebbe non essere preciso.\\nViene utilizzato un algoritmo di image processing in locale, non viene usata AI.\",\n  \"Clear chat history\": \"Elimina cronologia chat\",\n  \"Low battery\": \"Batteria quasi scarica\",\n  \"Save chat\": \"Salva chat\",\n  \"Switched to search mode. Continue with the user's request.\": \"Modalità ricerca attiva. Continua con la richiesta dell'utente.\",\n  \"Number show delay when pressing Super (ms)\": \"Mostra numeri premendo Super dopo (ms)\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Trascina o clicca una regione • LMB: Copia • RMB: Modifica\",\n  \"Consider plugging in your device\": \"Connetti il tuo dispositivo alla rete elettrica\",\n  \"%1 queries pending\": \"%1 ricerche in sospeso\",\n  \"<i>No further instruction provided</i>\": \"<i>Nessun'altra istruzione fornita</i>\",\n  \"There might be a download in progress\": \"Potrebbe esserci un download in corso\",\n  \"Approve\": \"Approva\",\n  \"Invalid arguments. Must provide `command`.\": \"Argomenti non validi. Il comando richiede `command`.\",\n  \"Reject\": \"Rifiuta\",\n  \"Thought\": \"Pensiero\",\n  \"Performance Profile toggle\": \"Profilo prestazioni\",\n  \"Command rejected by user\": \"Comando rifiutato dall'utente\",\n  \"Up %1\": \"Tempo di attività: %1\",\n  \"Overall appearance\": \"Aspetto\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"Online | Modello di Google\\nModello multiuso che eccelle in scrittura di codice e compiti di ragionamento complessi.\",\n  \"Usage: %1tool TOOL_NAME\": \"Istruzioni: %1tool TOOL_NAME\",\n  \"Set the tool to use for the model.\": \"Imposta lo strumento da usare con questo modello.\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Online | Modello di Google\\nVeloce, effettua ricerche con informazioni attuali\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Strumento non valido. Strumenti supportati:\\n- %1\",\n  \"Usage: %1load CHAT_NAME\": \"Istruzioni: %1load CHAT_NAME\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Strumento attuale: %1\\nUsa %2tool per cambiarlo\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Online | Modello di Google\\nModello Gemini 2.5 Flash ottimizzato per efficienza e throughput elevato.\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Per impostare una chiave API, utilizzala come argomento del comando %4\\n\\nPer vedere la chiave corrente, utilizza come argomento \\\"get\\\"<br/>\\n\\n### Per %1:\\n\\n**Link**: %2\\n\\n%3\",\n  \"Tool set to: %1\": \"Strumento impostato su: %1\",\n  \"Disable tools\": \"Disattiva strumenti\",\n  \"Usage: %1save CHAT_NAME\": \"Istruzioni: %1save CHAT_NAME\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Online | Modello di Google\\nPiù lento del suo predecessore ma restituisce risposte di qualità maggiore\",\n  \"Tint icons\": \"Icone monocromatiche\",\n  \"Tray\": \"Area di notifica\",\n  \"Tint app icons\": \"Icone monocromatiche\",\n  \"Sidebars\": \"Barre laterali\",\n  \"Keep right sidebar loaded\": \"Mantieni barra laterale destra in memoria\",\n  \"Automatically hide\": \"Nascondi automaticamente\",\n  \"Pause\": \"Pausa\",\n  \"Stopwatch\": \"Cronometro\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Pausa lunga: %1 minuti\",\n  \"Reset\": \"Ripristina\",\n  \"Resume\": \"Riprendi\",\n  \"Break\": \"Pausa breve\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Focus: %1 minuti\",\n  \"☕ Break: %1 minutes\": \"☕ Pausa breve: %1 minuti\",\n  \"Incorrect password\": \"Password errata\",\n  \"Start\": \"Avvia\",\n  \"Lap\": \"Parziale\",\n  \"Enter password\": \"Inserisci password\",\n  \"Long break\": \"Pausa lunga\",\n  \"Anime boorus\": \"Boorus anime\",\n  \"High\": \"Alto\",\n  \"To Do:\": \"Promemoria:\",\n  \"Charging:\": \"In carica:\",\n  \"... and %1 more\": \"... e altri %1\",\n  \"No pending tasks\": \"Nessuno\",\n  \"System uptime:\": \"Tempo di attività:\",\n  \"Total:\": \"Totale:\",\n  \"Medium\": \"Medio\",\n  \"Time to full:\": \"Tempo di ricarica:\",\n  \"Discharging:\": \"Consumo:\",\n  \"Free:\": \"Disponibile:\",\n  \"Fully charged\": \"Carica completa\",\n  \"Time to empty:\": \"Tempo rimanente:\",\n  \"Load:\": \"Carico:\",\n  \"Used:\": \"In uso:\",\n  \"Low\": \"Basso\",\n  \"Vertical\": \"Verticale\",\n  \"Horizontal\": \"Orizzontale\",\n  \"Hi there! First things first...\": \"Ciao! Per cominciare...\",\n  \"Welcome app\": \"App di benvenuto\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Puoi riaprire la schermata di benvenuto con <tt>Super+Shift+Alt+/</tt>. Per aprire le impostazioni, usa <tt>Super+I</tt>\",\n  \"Attach a file. Only works with Gemini.\": \"Allega un file. Funziona solo su Gemini.\",\n  \"Always\": \"Sempre\",\n  \"Place at the bottom/right\": \"Posiziona in basso/a destra\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**Prezzo**: Piano gratuito disponibile con utilizzo limitato. Più info su https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Istruzioni**: Genera un personal access token su GitHub con i permessi \\\"Models\\\" e impostalo come chiave API\\n\\n**Nota**: Per utilizzare questo modello imposta la temperatura a 1\",\n  \"Refreshing (manually triggered)\": \"Aggiornamento in corso (richiesto manualmente)\",\n  \"Shell conflicts killer\": \"Avviso conflitti\",\n  \"Kill conflicting programs?\": \"Terminare programmi in conflitto?\",\n  \"Conflicts with the shell's notification implementation\": \"In conflitto con il sistema di notifiche della shell\",\n  \"Conflicts with the shell's system tray implementation\": \"In conflitto con l'area di notifica della shell\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Mantiene il contenuto della barra laterale destra in memoria per ridurne il tempo di apertura,\\nrichiede circa 15MB di RAM. Il tempo di apertura dipende dalle performance del tuo sistema.\\nUsare un custom kernel come linux-cachyos può migliorare la velocità\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Istruzioni**: Accedi al tuo account Mistral, vai nella sezione \\\"Keys\\\" dal menu laterale, clicca \\\"Create new key\\\"\",\n  \"Gives the model search capabilities (immediately)\": \"Abilita modalità ricerca del modello (immediatamente)\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Comanda, modifica la richiesta, cerca.\\nRichiede un turno in più per attivare la modalità ricerca se richiesta\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Online | Modello di %1 | Ritorna risposte veloci e formattate correttamente. Svantaggi: poca voglia di svolgere compiti; potrebbe fare chiamate di funzioni inesistenti\"\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/ja_JP.json",
    "content": "{\n  \"Mo\": \"月/*keep*/\",\n  \"Tu\": \"火/*keep*/\",\n  \"We\": \"水/*keep*/\",\n  \"Th\": \"木/*keep*/\",\n  \"Fr\": \"金/*keep*/\",\n  \"Sa\": \"土/*keep*/\",\n  \"Su\": \"日/*keep*/\",\n  \"%1 characters\": \"%1 文字\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**料金**: 無料。データ利用ポリシーはOpenRouterアカウント設定によって異なります。\\n\\n**手順**: OpenRouterアカウントにログインし、右上メニューのKeysに進み、Create API Keyをクリックしてください。\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**料金**: 無料。データは学習に使用されます。\\n\\n**手順**: Googleアカウントにログインし、AI StudioにGoogle Cloudプロジェクトの作成などを許可し、戻ってAPIキーを取得してください。\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \"Zerochanの注意:\\n- 色を入力する必要があります\\n- `sidebar.booru.zerochan.username`設定でユーザー名を設定してください。設定しないと[利用停止になる場合があります](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)！\",\n  \"<i>No further instruction provided</i>\": \"<i>追加の指示はありません</i>\",\n  \"Action\": \"操作\",\n  \"Add\": \"追加\",\n  \"Add task\": \"タスクを追加\",\n  \"All-rounder | Good quality, decent quantity\": \"万能型 | 高品質・十分な量\",\n  \"Allow NSFW\": \"NSFWを許可\",\n  \"Allow NSFW content\": \"NSFWコンテンツを許可\",\n  \"Anime\": \"アニメ\",\n  \"Anime boorus\": \"アニメ画像掲示板\",\n  \"App\": \"アプリ\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"矢印キーで移動、Enterで選択\\nEsc/クリックでキャンセル\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Brightness\": \"明るさ\",\n  \"Cancel\": \"キャンセル\",\n  \"Cheat sheet\": \"チートシート\",\n  \"Choose model\": \"モデルを選択\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"健全 | 高品質・NSFWなし\",\n  \"Clear\": \"クリア\",\n  \"Clear chat history\": \"チャット履歴を消去\",\n  \"Clear the current list of images\": \"現在の画像リストをクリア\",\n  \"Close\": \"閉じる\",\n  \"Copy\": \"コピー\",\n  \"Copy code\": \"コードをコピー\",\n  \"Delete\": \"削除\",\n  \"Desktop\": \"デスクトップ\",\n  \"Disable NSFW content\": \"NSFWコンテンツを無効化\",\n  \"Done\": \"完了\",\n  \"Download\": \"ダウンロード\",\n  \"Edit\": \"編集\",\n  \"Enter text to translate...\": \"翻訳するテキストを入力...\",\n  \"Finished tasks will go here\": \"完了したタスクはここに表示されます\",\n  \"For desktop wallpapers | Good quality\": \"デスクトップ壁紙向け | 高品質\",\n  \"For storing API keys and other sensitive information\": \"APIキーや機密情報の保存用\",\n  \"Game mode\": \"ゲームモード\",\n  \"Get the next page of results\": \"次のページを取得\",\n  \"Hibernate\": \"休止\",\n  \"Input\": \"入力\",\n  \"Intelligence\": \"知能\",\n  \"Interface\": \"インターフェース\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"無効な引数です。`key`と`value`を指定してください。\",\n  \"Jump to current month\": \"現在の月へ移動\",\n  \"Keep system awake\": \"システムをスリープさせない\",\n  \"Large images | God tier quality, no NSFW.\": \"大きな画像 | 最高品質・NSFWなし\",\n  \"Large language models\": \"大規模言語モデル\",\n  \"Launch\": \"起動\",\n  \"Lock\": \"ロック\",\n  \"Logout\": \"ログアウト\",\n  \"Markdown test\": \"Markdownテスト\",\n  \"Math result\": \"計算結果\",\n  \"No audio source\": \"音声ソースなし\",\n  \"No media\": \"メディアなし\",\n  \"No notifications\": \"通知なし\",\n  \"Not visible to model\": \"モデルには表示されません\",\n  \"Nothing here!\": \"何もありません！\",\n  \"Notifications\": \"通知\",\n  \"OK\": \"OK\",\n  \"Open file link\": \"ファイルリンクを開く\",\n  \"Output\": \"出力\",\n  \"Reboot\": \"再起動\",\n  \"Reboot to firmware settings\": \"ファームウェア設定で再起動\",\n  \"Reload Hyprland & Quickshell\": \"HyprlandとQuickshellを再読み込み\",\n  \"Run\": \"実行\",\n  \"Run command\": \"コマンドを実行\",\n  \"Save\": \"保存\",\n  \"Save to Downloads\": \"ダウンロードに保存\",\n  \"Search\": \"検索\",\n  \"Search the web\": \"ウェブ検索\",\n  \"Search, calculate or run\": \"検索・計算・実行\",\n  \"Select Language\": \"言語を選択\",\n  \"Session\": \"セッション\",\n  \"Set API key\": \"APIキーを設定\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"モデルの温度（ランダム性）を設定します。Geminiは0～2、他は0～1です。初期値は0.5です。\",\n  \"Set the current API provider\": \"現在のAPIプロバイダーを設定します\",\n  \"Shutdown\": \"シャットダウン\",\n  \"Silent\": \"サイレント\",\n  \"Sleep\": \"スリープ\",\n  \"System\": \"システム\",\n  \"Task Manager\": \"タスクマネージャー\",\n  \"Task description\": \"タスクの説明\",\n  \"Temperature must be between 0 and 2\": \"温度は0～2の間で指定してください\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"成人向け | 量は多いが品質は様々・NSFW多数\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"人気 | 量は最多だが品質は様々\",\n  \"Thinking\": \"考え中\",\n  \"Translation goes here...\": \"ここに翻訳が表示されます...\",\n  \"Translator\": \"翻訳\",\n  \"Unfinished\": \"未完了\",\n  \"Unknown\": \"不明\",\n  \"Unknown Album\": \"不明なアルバム\",\n  \"Unknown Artist\": \"不明なアーティスト\",\n  \"Unknown Title\": \"不明なタイトル\",\n  \"View Markdown source\": \"Markdownソースを表示\",\n  \"Volume\": \"音量\",\n  \"Volume mixer\": \"ボリュームミキサー\",\n  \"Waifus only | Excellent quality, limited quantity\": \"キャラクター | 高品質・量は少なめ\",\n  \"Waiting for response...\": \"応答待ち...\",\n  \"Workspace\": \"ワークスペース\",\n  \"Invalid API provider. Supported: \\n-\": \"無効なAPIプロバイダー。対応: \\n-\",\n  \"Unknown command:\": \"不明なコマンド:\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"/key でオンラインモデルを開始\\nCtrl+O でサイドバーを展開\\nCtrl+P でサイドバーをウィンドウ化\",\n  \"Provider set to\": \"プロバイダーを設定しました:\",\n  \"Invalid model. Supported: \\n```\": \"無効なモデル。対応: \\n```\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"うまくいきませんでした。ヒント:\\n- タグやNSFW設定を確認\\n- タグがなければページ番号を入力\",\n  \"Switched to search mode. Continue with the user's request.\": \"検索モードに切り替えました。リクエストを続行します。\",\n  \"Settings\": \"設定\",\n  \"Save chat\": \"チャットを保存\",\n  \"Load chat\": \"チャットを読み込み\",\n  \"or\": \"または\",\n  \"Set the system prompt for the model.\": \"モデルのシステムプロンプトを設定\",\n  \"To Do\": \"やること\",\n  \"Calendar\": \"カレンダー\",\n  \"Advanced\": \"詳細\",\n  \"About\": \"このアプリについて\",\n  \"Services\": \"サービス\",\n  \"Style\": \"スタイル\",\n  \"Edit config\": \"設定を編集\",\n  \"Colors & Wallpaper\": \"色と壁紙\",\n  \"Light\": \"ライト\",\n  \"Dark\": \"ダーク\",\n  \"Material palette\": \"マテリアルパレット\",\n  \"Fidelity\": \"忠実度\",\n  \"Fruit Salad\": \"フルーツサラダ\",\n  \"Alternatively use /dark, /light, /img in the launcher\": \"ランチャーで /dark, /light, /img も使用できます\",\n  \"Fake screen rounding\": \"画面の角を丸める（疑似）\",\n  \"When not fullscreen\": \"フルスクリーンでない時\",\n  \"Choose file\": \"ファイルを選択\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Konachanから健全なアニメ壁紙をランダムで取得し、~/Pictures/Wallpapers に保存します\",\n  \"Be patient...\": \"少々お待ちください…\",\n  \"Decorations & Effects\": \"装飾と効果\",\n  \"Tonal Spot\": \"トーナルスポット\",\n  \"Shell windows\": \"シェルウィンドウ\",\n  \"Auto\": \"自動\",\n  \"Wallpaper\": \"壁紙\",\n  \"Content\": \"コンテンツ\",\n  \"Title bar\": \"タイトルバー\",\n  \"Transparency\": \"透明度\",\n  \"Expressive\": \"表現豊か\",\n  \"Yes\": \"表示\",\n  \"Enable\": \"有効化\",\n  \"Rainbow\": \"レインボー\",\n  \"Might look ass. Unsupported.\": \"表示が崩れる可能性があります（非推奨）\",\n  \"Monochrome\": \"モノクロ\",\n  \"Random: Konachan\": \"ランダム: Konachan\",\n  \"Center title\": \"タイトルを中央に\",\n  \"Neutral\": \"ニュートラル\",\n  \"Pick wallpaper image on your system\": \"システムから壁紙画像を選択\",\n  \"No\": \"非表示\",\n  \"AI\": \"AI\",\n  \"Local only\": \"ローカルのみ\",\n  \"Policies\": \"ポリシー\",\n  \"Weeb\": \"アニメファン向け\",\n  \"Closet\": \"隠し\",\n  \"Show next time\": \"次回も表示する\",\n  \"Usage\": \"使用状況\",\n  \"Plain rectangle\": \"長方形\",\n  \"Useless buttons\": \"ダミーボタン\",\n  \"GitHub\": \"GitHub\",\n  \"Style & wallpaper\": \"スタイルと壁紙\",\n  \"Configuration\": \"設定\",\n  \"Change any time later with /dark, /light, /img in the launcher\": \"ランチャーで/dark, /light, /imgでいつでも変更可能\",\n  \"Keybinds\": \"キー割り当て\",\n  \"Float\": \"フローティング\",\n  \"Hug\": \"固定\",\n  \"illogical-impulse Welcome\": \"illogical-impulse へようこそ！\",\n  \"Info\": \"情報\",\n  \"Volume limit\": \"ボリューム制限\",\n  \"Prevents abrupt increments and restricts volume limit\": \"急な音量変化を防ぎ、音量の上限を設定します\",\n  \"Resources\": \"リソース\",\n  \"12h am/pm\": \"12時間（AM/PM）\",\n  \"Base URL\": \"ベースURL\",\n  \"Audio\": \"オーディオ\",\n  \"Networking\": \"ネットワーク\",\n  \"Format\": \"フォーマット\",\n  \"Time\": \"時間\",\n  \"Battery\": \"バッテリー\",\n  \"Prefixes\": \"接頭辞\",\n  \"Emojis\": \"絵文字\",\n  \"Earbang protection\": \"聴覚保護（急な大音量防止）\",\n  \"Automatically suspends the system when battery is low\": \"バッテリー残量が少ないときに自動でスリープします\",\n  \"Automatic suspend\": \"自動スリープ\",\n  \"Suspend at\": \"スリープ開始残量（%）\",\n  \"Max allowed increase\": \"音量の最大増加幅\",\n  \"Web search\": \"ウェブ検索\",\n  \"Polling interval (ms)\": \"ポーリング間隔（ms）\",\n  \"Clipboard\": \"クリップボード\",\n  \"Low warning\": \"バッテリー低下の警告\",\n  \"24h\": \"24時間\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"あいまい検索の代わりにレーベンシュタイン距離アルゴリズムを使用\",\n  \"System prompt\": \"システムプロンプト\",\n  \"12h AM/PM\": \"12時間（AM/PM）\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\":\"入力ミスが多い場合に便利ですが、結果が意図しないものになったり、略語（例：GIMP）が正しく検索できないことがあります\",\n  \"Critical warning\": \"重大な警告\",\n  \"User agent (for services that require it)\": \"ユーザーエージェント（必要なサービス用）\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"これらの領域は、画像や画面の一部など、まとまりのある部分を指します。\\n必ずしも正確ではありません。\\nAIではなく、ローカルで実行される画像処理アルゴリズムによって検出されます。\",\n  \"Note: turning off can hurt readability\": \"注意: オフにすると可読性が損なわれる場合があります\",\n  \"Workspaces shown\": \"表示中のワークスペース\",\n  \"Dark/Light toggle\": \"ダーク/ライト切替\",\n  \"Dock\": \"ドック\",\n  \"Weather\": \"天気\",\n  \"Pinned on startup\": \"起動時にピン留め\",\n  \"Tip: Hide icons and always show numbers for\\nthe classic illogical-impulse experience\": \"ヒント: アイコンを非表示にして番号を常に表示すると、クラシックなillogical-impulseの使用感を体験できます\",\n  \"Always show numbers\": \"数字を常に表示\",\n  \"Buttons\": \"ボタン\",\n  \"Keyboard toggle\": \"キーボード切替\",\n  \"Scale (%)\": \"スケール（％）\",\n  \"Overview\": \"概要\",\n  \"Rows\": \"行数\",\n  \"Borderless\": \"枠なし\",\n  \"Screenshot tool\": \"スクリーンショットツール\",\n  \"Number show delay when pressing Super (ms)\": \"Superキー押下時の数字表示遅延（ms）\",\n  \"Timeout (ms)\": \"タイムアウト（ms）\",\n  \"Show app icons\": \"アプリアイコンを表示\",\n  \"Workspaces\": \"ワークスペース\",\n  \"Columns\": \"列数\",\n  \"On-screen display\": \"画面表示\",\n  \"Screen snip\": \"画面の切り抜き\",\n  \"Mic toggle\": \"マイク切替\",\n  \"Hover to reveal\": \"ホバーで表示\",\n  \"Bar\": \"バー\",\n  \"Show background\": \"背景を表示\",\n  \"Show regions of potential interest\": \"注目領域を表示\",\n  \"Color picker\": \"カラーピッカー\",\n  \"Help & Support\": \"ヘルプとサポート\",\n  \"Discussions\": \"ディスカッション\",\n  \"Color generation\": \"色の生成\",\n  \"Dotfiles\": \"ドットファイル\",\n  \"Distro\": \"ディストリビューション\",\n  \"Privacy Policy\": \"プライバシーポリシー\",\n  \"Documentation\": \"ドキュメント\",\n  \"Shell & utilities theming must also be enabled\": \"「シェルとユーティリティ」のテーマ設定も有効にする必要があります\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Donate\": \"寄付\",\n  \"Terminal\": \"ターミナル\",\n  \"Shell & utilities\": \"シェルとユーティリティ\",\n  \"Qt apps\": \"Qtアプリ\",\n  \"Report a Bug\": \"バグを報告\",\n  \"Issues\": \"課題\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"領域をドラッグまたはクリック • 左クリック: コピー • 右クリック: 編集\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"現在のモデル: %1\\n%2model MODELで設定\",\n  \"Message the model... \\\"%1\\\" for commands\": \"モデルにメッセージを送信... コマンドは「%1」\",\n  \"No API key set for %1\": \"%1のAPIキーが設定されていません\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"次のシステムプロンプトを読み込みました\\n\\n---\\n\\n%1\",\n  \"%1 | Right-click to configure\": \"%1 | 右クリックで設定\",\n  \"API key set for %1\": \"%1のAPIキーを設定しました\",\n  \"Online via %1 | %2's model\": \"%1経由オンライン | %2のモデル\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"現在のAPIエンドポイント: %1\\n%2mode PROVIDERで設定\",\n  \"Go to source (%1)\": \"ソースを開く（%1）\",\n  \"Temperature set to %1\": \"温度を%1に設定\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"タグを入力、またはコマンドは「%1」\",\n  \"%1 queries pending\": \"%1件のクエリが保留中\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"APIキー:\\n\\n```txt\\n%1\\n```\",\n  \"%1 Safe Storage\": \"%1 セーフストレージ\",\n  \"%1 does not require an API key\": \"%1はAPIキー不要です\",\n  \"Temperature: %1\": \"温度: %1\",\n  \"Model set to %1\": \"モデルを%1に設定\",\n  \"Page %1\": \"ページ %1\",\n  \"Local Ollama model | %1\": \"ローカルOllamaモデル | %1\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"現在のシステムプロンプトは\\n\\n---\\n\\n%1\",\n  \"Unknown function call: %1\": \"不明な関数呼び出し: %1\",\n  \"%1 notifications\": \"%1件の通知\",\n  \"Load chat from %1\": \"%1からチャットを読み込み\",\n  \"Load prompt from %1\": \"%1からプロンプトを読み込み\",\n  \"Save chat to %1\": \"チャットを%1に保存\",\n  \"Weather Service\": \"天気サービス\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"GPSサービスが見つかりません。代替手段を使用します。\",\n  \"Critically low battery\": \"バッテリー残量が非常に少ない\",\n  \"Select output device\": \"出力デバイスを選択\",\n  \"Code saved to file\": \"コードをファイルに保存しました\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"オンラインモデルは許可されていません\\n\\n`policies.ai` 設定で管理されています\",\n  \"Scroll to change volume\": \"スクロールで音量調整\",\n  \"Elements\": \"要素\",\n  \"%1   •   %2 tasks\": \"%1   •   %2件のタスク\",\n  \"Download complete\": \"ダウンロード完了\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"充電してください！\\n%1で自動的にサスペンドします\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Scroll to change brightness\": \"スクロールで明るさ調整\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"接続に失敗しました。<tt>warp-cli</tt> コマンドで手動確認してください\",\n  \"Select input device\": \"入力デバイスを選択\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"登録に失敗しました。<tt>warp-cli</tt> コマンドで手動確認してください\",\n  \"Consider plugging in your device\": \"電源を接続することを推奨します\",\n  \"Low battery\": \"バッテリー残量低下\",\n  \"Saved to %1\": \"%1に保存しました\",\n  \"Sunset\": \"日没\",\n  \"UV Index\": \"UV指数\",\n  \"Humidity\": \"湿度\",\n  \"Wind\": \"風\",\n  \"Sunrise\": \"日の出\",\n  \"Pressure\": \"気圧\",\n  \"Visibility\": \"視界\",\n  \"Precipitation\": \"降水量\",\n  \"Time to full:\": \"満充電まで:\",\n  \"Time to empty:\": \"空になるまで:\",\n  \"Fully charged\": \"充電完了\",\n  \"Charging:\": \"充電中:\",\n  \"Discharging:\": \"放電中:\",\n  \"No pending tasks\": \"保留中のタスクなし\",\n  \"... and %1 more\": \"...他%1件\",\n  \"Used:\": \"使用済み:\",\n  \"Free:\": \"空き:\",\n  \"Total:\": \"合計:\",\n  \"Swap:\": \"スワップ:\",\n  \"Not configured\": \"未設定\",\n  \"Load:\": \"負荷:\",\n  \"High\": \"高\",\n  \"Medium\": \"中\",\n  \"Low\": \"低\",\n  \"Use the system file picker instead\": \"システム標準のファイル選択ツールを使用\",\n  \"Tint icons\": \"アイコンに色付けする\",\n  \"Connect to Wi-Fi\": \"Wi-Fiに接続\",\n  \"Invalid arguments. Must provide `command`.\": \"無効な引数です。`command`を指定してください。\",\n  \"System uptime:\": \"システム稼働時間:\",\n  \"Gives the model search capabilities (immediately)\": \"モデルにすぐに検索能力を与えます\",\n  \"Click to toggle light/dark mode (applied when wallpaper is chosen)\": \"クリックでライト/ダークモード切替（壁紙選択時に適用されます）\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**料金**: 制限付きの無料枠があります。詳細は https://docs.github.com/en/billing/concepts/product-billing/github-models を参照\\n\\n**手順**: Modelsアクセス権を持つGitHub個人アクセストークンを生成し、ここでAPIキーとして設定してください\\n\\n**注意**: 使用するには温度パラメータを1に設定する必要があります\",\n  \"Depends on workspace\": \"ワークスペースに依存\",\n  \"Hi there! First things first...\": \"こんにちは！まずは始めましょう...\",\n  \"Refreshing (manually triggered)\": \"更新中（手動で開始）\",\n  \"Always\": \"常に\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"APIキーがありません\\n/key YOUR_API_KEYで設定してください\",\n  \"Usage: %1load CHAT_NAME\": \"使用法: %1load チャット名\",\n  \"Sidebars\": \"サイドバー\",\n  \"Search wallpapers\": \"壁紙を検索\",\n  \"When this is off you'll have to click\": \"オフの場合、クリックで表示します\",\n  \"Depends on sidebars\": \"サイドバーに依存\",\n  \"Incorrect password\": \"パスワードが間違っています\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"現在のツール: %1\\n%2tool TOOLで設定\",\n  \"Overall appearance\": \"全体の外観\",\n  \"To Do:\": \"やること:\",\n  \"Region height\": \"領域の高さ\",\n  \"Auto (System)\": \"自動（システム）\",\n  \"Place the corners to trigger at the bottom\": \"トリガーとなる角を下部に配置\",\n  \"Shell conflicts killer\": \"シェルの競合解消\",\n  \"Enter password\": \"パスワードを入力\",\n  \"☕ Break: %1 minutes\": \"☕ 休憩: %1分\",\n  \"Reset\": \"リセット\",\n  \"Connect\": \"接続\",\n  \"Tint app icons\": \"アプリアイコンに淡い色付けをする\",\n  \"Bar layout\": \"バーのレイアウト\",\n  \"Conflicts with the shell's notification implementation\": \"シェルの通知機能と競合することがあります\",\n  \"Corner open\": \"コーナー起動\",\n  \"🌿 Long break: %1 minutes\": \"🌿 長い休憩: %1分\",\n  \"Reject\": \"拒否\",\n  \"Command rejected by user\": \"コマンドはユーザーによって拒否されました\",\n  \"Start\": \"開始\",\n  \"Brightness and volume\": \"明るさ・音量\",\n  \"Corner style\": \"角のスタイル\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"トークン数合計\\n入力: %1\\n出力: %2\",\n  \"No active player\": \"アクティブなプレーヤーがありません\",\n  \"Performance Profile toggle\": \"パフォーマンスプロファイル切替\",\n  \"Timer\": \"タイマー\",\n  \"Conflicts with the shell's system tray implementation\": \"シェルのシステムトレイ機能と競合することがあります\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"オンライン | %1のモデル | 高速で反応が良く、整形された回答が得られます。欠点: あまり積極的でない場合があり、存在しない関数を提案することがあります\",\n  \"Welcome app\": \"ようこそアプリ\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"オンライン | Googleモデル\\nコスト効率と高スループットに最適化されたGemini 2.5 Flashモデル。\",\n  \"Wallpaper parallax\": \"壁紙パララックス効果\",\n  \"Place at the bottom\": \"下部に配置\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"無効なツールです。対応ツール:\\n- %1\",\n  \"Password\": \"パスワード\",\n  \"Details\": \"詳細\",\n  \"Edit directory\": \"ディレクトリを編集\",\n  \"Language\": \"言語\",\n  \"Visualize region\": \"領域を可視化\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"お楽しみください！ウェルカムアプリは<tt>Super+Shift+Alt+/</tt>でいつでも開けます。設定アプリを開くには<tt>Super+I</tt>を押してください\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"オンライン | Googleモデル\\nコーディングや複雑な推論タスクに優れた、Googleの最先端多目的モデル。\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"有効にすると右サイドバーのコンテンツを常に読み込んでおくことで、表示遅延を短縮します。\\n代償として約15MBのメモリを継続的に使用します。遅延の度合いはシステム性能に依存します。\\nlinux-cachyosのようなカスタムカーネルの使用が効果的な場合があります。\",\n  \"Place at bottom\": \"下部に配置\",\n  \"Tool set to: %1\": \"ツールを設定: %1\",\n  \"Set the tool to use for the model.\": \"モデルが使用するツールを設定します。\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"お使いのプレイヤーがMPRISをサポートしているか確認するか、\\n重複プレイヤーのフィルタリングを無効にしてみてください\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"インターフェース言語を選択します。\\n「自動」を選ぶとシステムのロケールが使用されます。\",\n  \"Value scroll\": \"スクロールで音量・明るさを調整\",\n  \"There might be a download in progress\": \"ダウンロードが進行中のようです\",\n  \"Approve\": \"承認\",\n  \"Tray\": \"トレイ\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**手順**: Mistralアカウントにログインし、サイドバーのKeysに進み、Create new keyをクリックしてください\",\n  \"Pomodoro\": \"ポモドーロ\",\n  \"Language setting saved. Please restart Quickshell (Ctrl+Super+R) to apply the new language.\": \"言語設定を保存しました。新しい言語を適用するにはQuickshellを再起動してください（Ctrl+Super+R）。\",\n  \"Feels like %1\": \"体感温度 %1\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"コマンド、設定編集、検索が可能。\\n検索モードへの切替が必要な場合は追加の対話が必要です\",\n  \"Resume\": \"再開\",\n  \"Preferred wallpaper zoom (%)\": \"壁紙の拡大率（%）\",\n  \"Disable tools\": \"ツールをオフ\",\n  \"Night Light | Right-click to toggle Auto mode\": \"ナイトライト | 右クリックで自動モード切替\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"オンライン | Googleモデル\\n高速で、最新情報を検索できます\",\n  \"Hover to trigger\": \"ホバーでトリガー\",\n  \"Keep right sidebar loaded\": \"右サイドバーを常に読み込む\",\n  \"Kill conflicting programs?\": \"競合するプログラムを終了しますか？\",\n  \"Pick a wallpaper\": \"壁紙を選択\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"オンライン | Googleモデル\\n旧モデルより低速ですが、より高品質な回答が期待できる新モデル\",\n  \"Hit \\\"/\\\" to search\": \"「/」で検索\",\n  \"Config file\": \"設定ファイル\",\n  \"Attach a file. Only works with Gemini.\": \"ファイルを添付 (Geminiでのみ利用可能)\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"APIキーを設定するには、%4コマンドを使用してください\\n\\nキーを確認するには、コマンドに「get」を付けてください<br/>\\n\\n### %1について:\\n\\n**リンク**: %2\\n\\n%3\",\n  \"Thought\": \"思考\",\n  \"Long break\": \"長い休憩\",\n  \"Temperature\\nChange with /temp VALUE\": \"温度\\n/temp 値で変更\",\n  \"Usage: %1tool TOOL_NAME\": \"使用法: %1tool ツール名\",\n  \"Pause\": \"一時停止\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"バーの配置に関わらず、画面コーナーのクリックまたはホバーでサイドバーを開けるようにします\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | 右クリックで設定\",\n  \"Up %1\": \"%1稼働中\",\n  \"Place at the bottom/right\": \"下部/右側に配置\",\n  \"Focus\": \"集中\",\n  \"Stopwatch\": \"ストップウォッチ\",\n  \"Interface Language\": \"インターフェース言語\",\n  \"Memory usage\": \"メモリ使用量\",\n  \"Automatically hide\": \"自動的に隠す\",\n  \"Break\": \"休憩\",\n  \"Your package manager is running\": \"パッケージマネージャーが実行中です\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"APIキーが設定済み\\n/key YOUR_API_KEYで変更できます\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"シェルの設定ファイルを開きます。\\nボタンが機能しない、または任意のエディタで開かない場合は、\\n~/.config/illogical-impulse/config.json を手動で開いてください\",\n  \"CPU usage\": \"CPU使用率\",\n  \"Swap usage\": \"スワップ使用量\",\n  \"Usage: %1save CHAT_NAME\": \"使用法: %1save チャット名\",\n  \"🔴 Focus: %1 minutes\": \"🔴 集中: %1分\",\n  \"Lap\": \"ラップ\",\n  \"Horizontal\": \"水平\",\n  \"Region width\": \"領域の幅\",\n  \"Vertical\": \"垂直\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/pt_BR.json",
    "content": "{\n  \"Attach a file. Only works with Gemini.\": \"Anexar um arquivo. Funciona apenas com Gemini.\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 tarefas\",\n  \"Large language models\": \"Grandes modelos de linguagem (LLMs)\",\n  \"Open network portal\": \"Fazer login na rede\",\n  \"Digits in the middle\": \"Dígitos no meio\",\n  \"Auto,\": \"Auto,\",\n  \"Select Language\": \"Selecionar Idioma\",\n  \"Expressive\": \"Expressivo\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Foco: %1 minutos\",\n  \"%1 | Right-click to configure\": \"%1 | Botão direito para configurar\",\n  \"Delete\": \"Excluir\",\n  \"Audio output\": \"Saída de áudio\",\n  \"Full warning\": \"Aviso de carga completa\",\n  \"Switched to search mode. Continue with the user's request.\": \"Alternado para modo de busca. Continue com a solicitação do usuário.\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Lock\": \"Bloquear\",\n  \"Apps\": \"Apps\",\n  \"Generating...\\nDon't close this window!\": \"Gerando...\\nNão feche esta janela!\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"Por favor carregue!\\nSuspensão automática em %1%\",\n  \"Font family name (e.g., Google Sans Flex)\": \"Nome da família da fonte (ex: Google Sans Flex)\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Prompt de sistema carregado\\n\\n---\\n\\n%1\",\n  \"Adjust the color temperature\": \"Ajustar temperatura de cor\",\n  \"Numbers\": \"Números\",\n  \"Style: Blurred\": \"Estilo: Desfocado\",\n  \"Usage\": \"Uso\",\n  \"Ignored if terminal theming is not enabled\": \"Ignorado se o tema do terminal não estiver ativado\",\n  \"Save to Downloads\": \"Salvar em Downloads\",\n  \"Open file link\": \"Abrir link do arquivo\",\n  \"Do you want to allow this app to make changes to your device?\": \"Deseja permitir que este app faça alterações no seu dispositivo?\",\n  \"Visibility\": \"Visibilidade\",\n  \"Set the tool to use for the model.\": \"Definir a ferramenta para o modelo usar.\",\n  \"Bar position\": \"Posição da barra\",\n  \"Open\": \"Abrir\",\n  \"Mic toggle\": \"Alternar Microfone\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"Idioma não listado ou traduções incompletas?\\nVocê pode gerar traduções com o Gemini.\\n1. Abra a barra lateral esquerda com Super+A, defina o modelo para Gemini\\n2. Digite /key, aperte Enter e siga as instruções\\n3. Digite /key SUA_API_KEY\\n4. Digite o código do seu idioma abaixo e clique em Gerar\",\n  \"Muted\": \"Mudo\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Falha no registro. Por favor inspecione manualmente com o comando <tt>warp-cli</tt>\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"Chave API definida\\nAltere com /key SUA_CHAVE_API\",\n  \"Terminal: Harmonize threshold\": \"Terminal: Limiar de harmonização\",\n  \"%1 characters\": \"%1 caracteres\",\n  \"For desktop wallpapers | Good quality\": \"Para desktop | Boa qualidade\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Pausa longa: %1 minutos\",\n  \"Set FPS limit\": \"Definir limite de FPS\",\n  \"+%1 notifications\": \"+%1 notificações\",\n  \"Shell & utilities theming must also be enabled\": \"Tema do Shell e utilitários também deve estar ativado\",\n  \"Content region\": \"Região do conteúdo\",\n  \"Padding\": \"Espaçamento (Padding)\",\n  \"Session\": \"Sessão\",\n  \"Online | Google's model\\nPro-level intelligence at the speed and pricing of Flash.\": \"Online | Modelo do Google\\nInteligência nível Pro na velocidade e preço do Flash.\",\n  \"Yes\": \"Sim\",\n  \"Listening...\": \"Ouvindo...\",\n  \"Base URL\": \"URL Base\",\n  \"Load:\": \"Uso:\",\n  \"Path copied\": \"Caminho copiado\",\n  \"Show\": \"Mostrar\",\n  \"Donate\": \"Doar\",\n  \"Keyboard toggle\": \"Alternar Teclado\",\n  \"File Explorer\": \"Explorador de Arquivos\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"Exemplo de uso: eroge em um workspace, Discord escuro em outro\",\n  \"Tooltips\": \"Dicas\",\n  \"Internet\": \"Internet\",\n  \"Keybinds\": \"Atalhos\",\n  \"Feels like %1\": \"Sensação térmica de %1\",\n  \"Wi-Fi\": \"Wi-Fi\",\n  \"Used:\": \"Usado:\",\n  \"Used for reading large blocks of text\": \"Usado para leitura de grandes blocos de texto\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Usar o selecionador de arquivos do sistema\\nBotão direito para tornar este o padrão\",\n  \"Enter password\": \"Digite a senha\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Preço**: grátis. Política de dados varia conforme configurações da conta OpenRouter.\\n\\n**Instruções**: Logue no OpenRouter, vá em Keys no menu superior direito, clique em Create API Key\",\n  \"Monospace font\": \"Fonte Monoespaçada\",\n  \"Positioning\": \"Posicionamento\",\n  \"Search, calculate or run\": \"Pesquisar, calcular ou executar\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Mensagem para o modelo... \\\"%1\\\" para comandos\",\n  \"Focus\": \"Foco\",\n  \"Font width\": \"Largura da fonte\",\n  \"Enable opening zoom animation\": \"Ativar animação de zoom ao abrir\",\n  \"Logout\": \"Sair (Logout)\",\n  \"Jump to current month\": \"Pular para mês atual\",\n  \"Keep awake\": \"Manter acordado\",\n  \"Show next time\": \"Mostrar na próxima vez\",\n  \"Date style\": \"Estilo da data\",\n  \"GitHub\": \"GitHub\",\n  \"Saved\": \"Salvo\",\n  \"Bottom-up\": \"De baixo para cima\",\n  \"Disable tools\": \"Desativar ferramentas\",\n  \"Copy code\": \"Copiar código\",\n  \"Low warning\": \"Aviso de bateria fraca\",\n  \"Saving...\": \"Salvando...\",\n  \"Interface\": \"Interface\",\n  \"Normal\": \"Normal\",\n  \"Hug\": \"Arredondado\",\n  \"Tint icons\": \"Colorir ícones\",\n  \"Turn on from sunset to sunrise\": \"Ligar do pôr do sol ao nascer do sol\",\n  \"Kill conflicting programs?\": \"Matar programas conflitantes?\",\n  \"Model set to %1\": \"Modelo definido para %1\",\n  \"Desktop %1\": \"Área de Trabalho %1\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Apenas Waifus | Excelente qualidade, quantidade limitada\",\n  \"Google Lens\": \"Google Lens\",\n  \"Battery: %1%2\": \"Bateria: %1%2\",\n  \"Calendar\": \"Calendário\",\n  \"24h\": \"24h\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Por que isso é legal:\\nPara valores não-0, não acionará quando você atingir o\\ncanto da tela pela borda horizontal, mas acionará quando\\nvocê for pela borda vertical\",\n  \"Disable NSFW content\": \"Desativar conteúdo NSFW\",\n  \"Allow NSFW\": \"Permitir NSFW\",\n  \"Workspaces\": \"Áreas de trabalho\",\n  \"When not fullscreen\": \"Quando não estiver em tela cheia\",\n  \"Run\": \"Executar\",\n  \"Sounds\": \"Sons\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"Botão Esq. p/ ativar/desativar\\nBotão Dir. p/ mudar tamanho\\nScroll p/ trocar posição\",\n  \"☕ Break: %1 minutes\": \"☕ Pausa: %1 minutos\",\n  \"Enter a valid number\": \"Digite um número válido\",\n  \"Split buttons\": \"Botões divididos\",\n  \"Best match\": \"Melhor resultado\",\n  \"Enable\": \"Ativar\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Setas para navegar, Enter para selecionar\\nEsc ou clique fora para cancelar\",\n  \"Intelligence\": \"Inteligência\",\n  \"Launch on startup\": \"Iniciar no boot\",\n  \"New desktop\": \"Nova área de trabalho\",\n  \"Intensity\": \"Intensidade\",\n  \"Paired\": \"Pareado\",\n  \"Desktop\": \"Área de Trabalho\",\n  \"Task View\": \"Visão de Tarefas\",\n  \"Not visible to model\": \"Invisível para o modelo\",\n  \"App\": \"App\",\n  \"Bar\": \"Barra\",\n  \"Show this window on all desktops\": \"Mostrar esta janela em todas as áreas\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Modelos online não permitidos\\n\\nControlado pela opção `policies.ai` na config\",\n  \"Hit \\\"/\\\" to search\": \"Aperte \\\"/\\\" para buscar\",\n  \"Font width and roundness settings are only available for some fonts like Google Sans Flex\": \"Largura e arredondamento só funcionam em fontes como Google Sans Flex\",\n  \"Use symbols for mouse\": \"Usar símbolos para o mouse\",\n  \"End session\": \"Encerrar sessão\",\n  \"... and %1 more\": \"... e mais %1\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Código de localidade, ex: pt_BR, en_US...\",\n  \"%1 Safe Storage\": \"Cofre Seguro do %1\",\n  \"Format\": \"Formato\",\n  \"Dots\": \"Dotfiles\",\n  \"There might be a download in progress\": \"Pode haver um download em andamento\",\n  \"Medium\": \"Médio\",\n  \"Math\": \"Matemática\",\n  \"Command-line-invoked Action\": \"Ação invocada por linha de comando\",\n  \"Style: general\": \"Estilo: geral\",\n  \"Time to full:\": \"Tempo até carga total:\",\n  \"Classic\": \"Clássico\",\n  \"Battery full\": \"Bateria cheia\",\n  \"Unread indicator: show count\": \"Indicador não lido: mostrar contagem\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Online | Modelo da %1 | Respostas rápidas e bem formatadas. Desvantagens: não muito proativo; pode inventar chamadas de função inexistentes\",\n  \"Workspace\": \"Workspace\",\n  \"Power Profile\": \"Perfil de Energia\",\n  \"Quick toggles\": \"Botões rápidos\",\n  \"Speakers (%1): %2\": \"Alto-falantes (%1): %2\",\n  \"Sunrise\": \"Nascer do sol\",\n  \"Clear chat history\": \"Limpar histórico do chat\",\n  \"Auto styling with Gemini\": \"Estilo automático com Gemini\",\n  \"Center clock\": \"Relógio centralizado\",\n  \"Circle\": \"Círculo\",\n  \"Han chars\": \"Caracteres Han\",\n  \"Roman\": \"Romano\",\n  \"Night Light | Right-click to configure\": \"Luz Noturna | Botão direito para configurar\",\n  \"Sliders\": \"Deslizadores\",\n  \"On-screen display\": \"Exibição na tela (OSD)\",\n  \"Show \\\"Locked\\\" text\": \"Mostrar texto \\\"Bloqueado\\\"\",\n  \"Unfinished\": \"Inacabado\",\n  \"Hour marks\": \"Marcas de hora\",\n  \"Shut down\": \"Desligar\",\n  \"Be patient...\": \"Seja paciente...\",\n  \"Also unlock keyring\": \"Também destrancar chaveiro\",\n  \"Temperature set to %1\": \"Temperatura definida para %1\",\n  \"Choose model\": \"Escolher modelo\",\n  \"Enter text to translate...\": \"Digite o texto para traduzir...\",\n  \"Finished tasks will go here\": \"Tarefas finalizadas virão para cá\",\n  \"Info\": \"Info\",\n  \"Parallax\": \"Paralaxe\",\n  \"Show date\": \"Mostrar data\",\n  \"Pills\": \"Cápsulas\",\n  \"Font family name\": \"Nome da família da fonte\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Online | Modelo do Google\\nModelo mais novo, mais lento que o antecessor mas com maior qualidade nas respostas\",\n  \"View Markdown source\": \"Ver código Markdown\",\n  \"Stroke width\": \"Largura do traço\",\n  \"Least busy\": \"Menos ocupado\",\n  \"Start\": \"Início\",\n  \"Click to cycle through power profiles\": \"Clique para alternar perfis de energia\",\n  \"Dark Mode\": \"Modo Escuro\",\n  \"Closet\": \"Closet\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Previne aumentos bruscos e restringe o limite de volume\",\n  \"Output device\": \"Dispositivo de saída\",\n  \"Stopwatch\": \"Cronômetro\",\n  \"Local only\": \"Apenas local\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Define a temperatura (aleatoriedade) do modelo. Valores entre 0 a 2 para Gemini, 0 a 1 para outros. Padrão é 0.5.\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"O prompt de sistema atual é\\n\\n---\\n\\n%1\",\n  \"with vertical offset\": \"com deslocamento vertical\",\n  \"Utilities & Tools\": \"Utilitários & Ferramentas\",\n  \"Invalid API provider. Supported: \\n-\": \"Provedor de API inválido. Suportados: \\n-\",\n  \"Font family name (e.g., Readex Pro)\": \"Nome da família da fonte (ex: Readex Pro)\",\n  \"Emojis\": \"Emojis\",\n  \"Auto (System)\": \"Auto (Sistema)\",\n  \"Cheat sheet\": \"Atalhos\",\n  \"General\": \"Geral\",\n  \"Password\": \"Senha\",\n  \"Config file\": \"Config file\",\n  \"Temperature\\nChange with /temp VALUE\": \"Temperatura\\nAltere com /temp VALOR\",\n  \"Swap\": \"Swap\",\n  \"Up %1\": \"Ligado há %1\",\n  \"Code saved to file\": \"Código salvo em arquivo\",\n  \"Android\": \"Android\",\n  \"Hint target regions\": \"Dicar regiões alvo\",\n  \"Set API key\": \"Definir chave API\",\n  \"Clear the current list of images\": \"Limpar lista atual de imagens\",\n  \"Night Light\": \"Luz Noturna\",\n  \"Font roundness\": \"Arredondamento da fonte\",\n  \"Style & wallpaper\": \"Estilo & Papel de parede\",\n  \"Nothing\": \"Nada\",\n  \"Snipping area\": \"Área de recorte\",\n  \"Exceeded max allowed\": \"Máximo permitido excedido\",\n  \"Sidebars\": \"Barras laterais\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Contagem total de tokens\\nEntrada: %1\\nSaída: %2\",\n  \"Off\": \"Desligado\",\n  \"e.g. 󱊫 for F1, 󱊶  for F12\": \"ex: 󱊫 para F1, 󱊶  para F12\",\n  \"Temperature: %1\": \"Temperatura: %1\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Abrir arquivo de config do shell\\nOu botão direito para copiar caminho\",\n  \"Action\": \"Ação\",\n  \"Force hover open at absolute corner\": \"Forçar canto absoluto\",\n  \"Fill\": \"Preencher\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Commands\": \"Comandos\",\n  \"No active player\": \"Nenhum player ativo\",\n  \"Show only when locked\": \"Mostrar apenas quando bloqueado\",\n  \"Usage: %1save CHAT_NAME\": \"Uso: %1save NOME_DO_CHAT\",\n  \"Provider set to\": \"Provedor definido para\",\n  \"Brightness\": \"Brilho\",\n  \"Fully charged\": \"Carga completa\",\n  \"Privacy Policy\": \"Política de Privacidade\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Coisas limpas (SFW) | Excelente qualidade, sem NSFW\",\n  \"UV Index\": \"Índice UV\",\n  \"No\": \"Não\",\n  \"Manage my account\": \"Gerenciar minha conta\",\n  \"Advanced\": \"Avançado\",\n  \"Emoji\": \"Emoji\",\n  \"Shell command\": \"Comando Shell\",\n  \"Transparency\": \"Transparência\",\n  \"Help & Support\": \"Ajuda & Suporte\",\n  \"More Internet settings\": \"Mais configurações de Internet\",\n  \"Generate\\nTypically takes 2 minutes\": \"Gerar\\nGeralmente leva 2 minutos\",\n  \"Unknown Application\": \"Aplicativo Desconhecido\",\n  \"Scale (%)\": \"Escala (%)\",\n  \"Go to source (%1)\": \"Ir para a fonte (%1)\",\n  \"You can also manually edit cheatsheet.superKey\": \"Você também pode editar manualmente cheatsheet.superKey\",\n  \"Unknown Title\": \"Título Desconhecido\",\n  \"%1\\nInternet access\": \"%1\\nAcesso à internet\",\n  \"Enable now\": \"Ativar agora\",\n  \"Creativity\": \"Criatividade\",\n  \"Cookie\": \"Cookie\",\n  \"Font size\": \"Tamanho da fonte\",\n  \"Sides\": \"Lados\",\n  \"Full\": \"Cheio\",\n  \"Used for code and terminal\": \"Usado para código e terminal\",\n  \"Unknown Album\": \"Álbum Desconhecido\",\n  \"Recognize music\": \"Reconhecer música\",\n  \"Your package manager is running\": \"Seu gerenciador de pacotes está rodando\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Saída de áudio | Botão direito para mixer & seletor\",\n  \"Conflicts with the shell's notification implementation\": \"Conflita com a implementação de notificação do shell\",\n  \"Total duration timeout (s)\": \"Tempo limite total (s)\",\n  \"Reading font\": \"Fonte de leitura\",\n  \"It may take a few seconds to update\": \"Pode levar alguns segundos para atualizar\",\n  \"Force dark mode in terminal\": \"Forçar modo escuro no terminal\",\n  \"Show notifications\": \"Mostrar notificações\",\n  \"Web search\": \"Busca web\",\n  \"%1 mins\": \"%1 min\",\n  \"Left to right\": \"Esquerda para direita\",\n  \"%1 does not require an API key\": \"%1 não requer chave API\",\n  \"System\": \"Sistema\",\n  \"Details\": \"Detalhes\",\n  \"Get the next page of results\": \"Obter próxima página de resultados\",\n  \"12h AM/PM\": \"12h AM/PM\",\n  \"Bluetooth devices\": \"Dispositivos Bluetooth\",\n  \"Dark\": \"Escuro\",\n  \"Neutral\": \"Neutro\",\n  \"Dot\": \"Ponto\",\n  \"Fidelity\": \"Fidelidade\",\n  \"Sunset\": \"Pôr do sol\",\n  \"Other\": \"Outro\",\n  \"Terminal: Harmony (%)\": \"Terminal: Harmonia (%)\",\n  \"Record\": \"Gravar\",\n  \"More volume settings\": \"Mais configurações de volume\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Entrada de áudio | Botão direito para mixer & seletor\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Selecione o idioma da interface.\\n\\\"Auto\\\" usará a localidade do sistema.\",\n  \"Dock\": \"Dock\",\n  \"About\": \"Sobre\",\n  \"Disconnect\": \"Desconectar\",\n  \"Widget: Weather\": \"Widget: Clima\",\n  \"Enable GPS based location\": \"Ativar localização por GPS\",\n  \"User agent (for services that require it)\": \"User agent (para serviços que exigem)\",\n  \"All-rounder | Good quality, decent quantity\": \"Geral | Boa qualidade, quantidade decente\",\n  \"Set the system prompt for the model.\": \"Definir prompt de sistema para o modelo.\",\n  \"Output\": \"Saída\",\n  \"Reboot to firmware settings\": \"Reiniciar na BIOS/Firmware\",\n  \"Hollow\": \"Oco\",\n  \"Float\": \"Flutuante\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Impulso de primeiro plano (%)\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Authentication\": \"Autenticação\",\n  \"Large images | God tier quality, no NSFW.\": \"Imagens grandes | Qualidade divina, sem NSFW.\",\n  \"Region selector (screen snipping/Google Lens)\": \"Seletor de região (recorte/Google Lens)\",\n  \"Show hidden icons\": \"Mostrar ícones ocultos\",\n  \"Sound effects\": \"Efeitos sonoros\",\n  \"Manage accounts\": \"Gerenciar contas\",\n  \"When this is off you'll have to click\": \"Quando desligado, você terá que clicar\",\n  \"of %1\": \"de %1\",\n  \"Monochrome\": \"Monocromático\",\n  \"Use adaptive alignment\": \"Usar alinhamento adaptativo\",\n  \"Close all windows\": \"Fechar todas as janelas\",\n  \"Gives the model search capabilities (immediately)\": \"Dá capacidade de busca ao modelo (imediatamente)\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Nem todas as opções estão neste app. Verifique o arquivo de config clicando no botão \\\"Arquivo de config\\\" no canto superior esquerdo ou abrindo %1 manualmente.\",\n  \"Volume limit\": \"Limite de volume\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Clique para alternar modo claro/escuro\\n(aplicado ao escolher papel de parede)\",\n  \"Local account\": \"Conta local\",\n  \"Text extractor\": \"Extrator de texto\",\n  \"Terminal\": \"Terminal\",\n  \"Font family name (e.g., Space Grotesk)\": \"Nome da família da fonte (ex: Space Grotesk)\",\n  \"Markdown test\": \"Teste Markdown\",\n  \"Most busy\": \"Mais ocupado\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Endpoint de API atual: %1\\nDefina com %2mode PROVEDOR\",\n  \"Center icons\": \"Centralizar ícones\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Quando ativado, mantém a barra lateral direita carregada para reduzir atraso ao abrir,\\nao custo de ~15MB de RAM. O impacto depende do seu sistema.\\nUsar um kernel customizado como linux-cachyos pode ajudar\",\n  \"Tip: Close a window with Super+Q\": \"Dica: Feche uma janela com Super+Q\",\n  \"Overview\": \"Visão Geral\",\n  \"Unknown device\": \"Dispositivo desconhecido\",\n  \"Volume\": \"Volume\",\n  \"Dark/Light toggle\": \"Alternar Escuro/Claro\",\n  \"Place the corners to trigger at the bottom\": \"Colocar cantos de ativação na parte inferior\",\n  \"Shell conflicts killer\": \"Matador de conflitos do Shell\",\n  \"No media\": \"Sem mídia\",\n  \"Keep right sidebar loaded\": \"Manter barra direita carregada\",\n  \"Virtual Keyboard\": \"Teclado Virtual\",\n  \"Visualize region\": \"Visualizar região\",\n  \"Bar & screen\": \"Barra & tela\",\n  \"RAM\": \"RAM\",\n  \"Timeout (ms)\": \"Tempo limite (ms)\",\n  \"Automatic\": \"Automático\",\n  \"Humidity\": \"Umidade\",\n  \"All\": \"Todos\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Conexão falhou. Inspecione manualmente com o comando <tt>warp-cli</tt>\",\n  \"Clipboard\": \"Área de transferência\",\n  \"Incorrect password\": \"Senha incorreta\",\n  \"Documentation\": \"Documentação\",\n  \"Used for decorative/expressive text\": \"Usado para texto decorativo/expressivo\",\n  \"Qt apps\": \"Apps Qt\",\n  \"To Do:\": \"A Fazer:\",\n  \"Content\": \"Adaptativo\",\n  \"Thin\": \"Fino\",\n  \"illogical-impulse Welcome\": \"Boas-vindas ao illogical-impulse\",\n  \"Timer\": \"Timer\",\n  \"Widgets\": \"Widgets\",\n  \"Performance Profile toggle\": \"Alternar Perfil de Desempenho\",\n  \"Description font size\": \"Tamanho da fonte da descrição\",\n  \"Show app icons\": \"Mostrar ícones de apps\",\n  \"Preferred wallpaper zoom (%)\": \"Zoom preferido do papel de parede (%)\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Lembre-se que na maioria dos dispositivos pode-se segurar o botão power para forçar desligamento\\nIsso só torna um pouco mais difícil acontecer acidentes\",\n  \"Translator\": \"Tradutor\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Serviço de GPS não encontrado. Usando método alternativo.\",\n  \"Image search\": \"Busca de imagem\",\n  \"Digital clock settings\": \"Configurações do relógio digital\",\n  \"Download complete\": \"Download completo\",\n  \"Download\": \"Baixar\",\n  \"Issues\": \"Problemas (Issues)\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Notas para Zerochan:\\n- Você deve inserir uma cor\\n- Defina seu usuário zerochan na config `sidebar.booru.zerochan.username`. Você [pode ser banido se não fizer isso](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Input\": \"Entrada\",\n  \"Notes\": \"Notas\",\n  \"Pin to Start\": \"Fixar no Iniciar\",\n  \"Border\": \"Borda\",\n  \"Tray\": \"Bandeja (Tray)\",\n  \"AI\": \"IA\",\n  \"Recognize music | Right-click to toggle source\": \"Reconhecer música | Botão direito p/ alternar fonte\",\n  \"Random: Konachan\": \"Aleatório: Konachan\",\n  \"Region width\": \"Largura da região\",\n  \"Bubble\": \"Bolha\",\n  \"No new notifications\": \"Nenhuma notificação nova\",\n  \"Quote\": \"Citação\",\n  \"Total:\": \"Total:\",\n  \"Snip\": \"Recortar\",\n  \"Battery\": \"Bateria\",\n  \"Pick wallpaper image on your system\": \"Escolher imagem no sistema\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Ferramenta inválida. Suportadas:\\n- %1\",\n  \"Reboot\": \"Reiniciar\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"O popular | Melhor quantidade, mas qualidade varia muito\",\n  \"Screen snip\": \"Captura de tela\",\n  \"Search\": \"Buscar\",\n  \"Connect to Wi-Fi\": \"Conectar ao Wi-Fi\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"Geralmente seguro e necessário para navegador e IA\\nÚtil principalmente para quem usa bloqueio na inicialização em vez de um gerenciador de display (GDM, SDDM, etc.)\",\n  \"Darken screen\": \"Escurecer tela\",\n  \"Anti-flashbang (experimental)\": \"Anti-clarão (experimental)\",\n  \"Sign out\": \"Sair\",\n  \"Usage: %1load CHAT_NAME\": \"Uso: %1load NOME_DO_CHAT\",\n  \"Color generation\": \"Geração de cores\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Uso: <tt>%1superpaste NUM_ENTRADAS[i]</tt>\\nUse <tt>i</tt> quando quiser imagens\\nExemplos:\\n<tt>%1superpaste 4i</tt> para as últimas 4 imagens\\n<tt>%1superpaste 7</tt> para as últimas 7 entradas\",\n  \"Search wallpapers\": \"Buscar papéis de parede\",\n  \"For storing API keys and other sensitive information\": \"Para armazenar chaves API e outras informações sensíveis\",\n  \"Cancel\": \"Cancelar\",\n  \"Polling interval (s)\": \"Intervalo de atualização (s)\",\n  \"Expressive font\": \"Fonte expressiva\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Rectangular selection\": \"Seleção retangular\",\n  \"Hi there! First things first...\": \"Olá! Primeiramente...\",\n  \"Saved to %1\": \"Salvo em %1\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"Chave API:\\n\\n```txt\\n%1\\n```\",\n  \"Line\": \"Linha\",\n  \"Number show delay when pressing Super (ms)\": \"Atraso numérico ao pressionar Super (ms)\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Mude depois com /dark, /light, /wallpaper no launcher\\nSe as cores não mudarem:\\n    1. Abra a barra direita com Super+N\\n    2. Clique em \\\"Recarregar Hyprland & Quickshell\\\" no canto superior direito\",\n  \"Columns\": \"Colunas\",\n  \"Animate time change\": \"Animar mudança de hora\",\n  \"Audio\": \"Áudio\",\n  \"Weeb\": \"Otaku\",\n  \"Image source\": \"Fonte da imagem\",\n  \"Work safety\": \"Segurança no trabalho (SFW)\",\n  \"Super key symbol\": \"Símbolo da tecla Super\",\n  \"Select language\": \"Selecionar idioma\",\n  \"Free:\": \"Livre:\",\n  \"Anime\": \"Anime\",\n  \"Wallpaper & Colors\": \"Papel de Parede & Cores\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Dica: clicar com botão direito num grupo\\ntambém o expande\",\n  \"System prompt\": \"Prompt do sistema\",\n  \"Automatic suspend\": \"Suspensão automática\",\n  \"Thought\": \"Pensamento\",\n  \"Depends on sidebars\": \"Depende das barras laterais\",\n  \"Hour hand\": \"Ponteiro das horas\",\n  \"Security\": \"Segurança\",\n  \"Report a Bug\": \"Reportar um Bug\",\n  \"Reset\": \"Resetar\",\n  \"Command\": \"Comando\",\n  \"Active\": \"Ativo\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Ferramenta atual: %1\\nDefina com %2tool FERRAMENTA\",\n  \"Used for headings and titles\": \"Usado para cabeçalhos e títulos\",\n  \"Left\": \"Esquerda\",\n  \"Keybind font size\": \"Tamanho da fonte de atalhos\",\n  \"Region height\": \"Altura da região\",\n  \"Unpin from taskbar\": \"Desafixar da barra de tarefas\",\n  \"Search for apps\": \"Buscar apps\",\n  \"Use symbols for function keys\": \"Usar símbolos para teclas de função\",\n  \"Hover to reveal\": \"Passar mouse para revelar\",\n  \"Perhaps what you're listening to is too niche\": \"Talvez o que você está ouvindo seja muito nichado\",\n  \"at\": \"em\",\n  \"Value scroll\": \"Scroll de valor\",\n  \"Used for general UI text\": \"Usado para texto geral da UI\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Usar Hyprlock (em vez de Quickshell)\",\n  \"Anime boorus\": \"Anime boorus\",\n  \"Sleep\": \"Dormir\",\n  \"Overlay: Floating Image\": \"Overlay: Imagem Flutuante\",\n  \"Time to empty:\": \"Tempo para descarregar:\",\n  \"Last refresh: %1\": \"Última atualização: %1\",\n  \"Music Recognized\": \"Música Reconhecida\",\n  \"Music Recognition\": \"Reconhecimento de Música\",\n  \"Consider plugging in your device\": \"Considere conectar seu dispositivo\",\n  \"Load chat from %1\": \"Carregar chat de %1\",\n  \"Edit directory\": \"Editar diretório\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Para definir chave API, use o comando %4\\n\\nPara ver a chave, use \\\"get\\\" com o comando<br/>\\n\\n### Para %1:\\n\\n**Link**: %2\\n\\n%3\",\n  \"Polling interval (ms)\": \"Intervalo de atualização (ms)\",\n  \"or\": \"ou\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Duração do tempo limite (se não definido pela notificação) (ms)\",\n  \"Low battery\": \"Bateria fraca\",\n  \"Reload Hyprland & Quickshell\": \"Recarregar Hyprland & Quickshell\",\n  \"Input device\": \"Dispositivo de entrada\",\n  \"Make sure you have songrec installed\": \"Certifique-se de ter o songrec instalado\",\n  \"Hover to trigger\": \"Passar mouse para ativar\",\n  \"Please unplug the charger\": \"Por favor desconecte o carregador\",\n  \"Tool set to: %1\": \"Ferramenta definida para: %1\",\n  \"Unmuted\": \"Com som\",\n  \"Rectangle\": \"Retângulo\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Argumentos inválidos. Deve fornecer `key` e `value`.\",\n  \"Corner style\": \"Estilo do canto\",\n  \"Screenshot Path (leave empty to just copy)\": \"Caminho do Print (vazio para apenas copiar)\",\n  \"Generate translation with Gemini\": \"Gerar tradução com Gemini\",\n  \"Widget: Clock\": \"Widget: Relógio\",\n  \"Clock style (locked)\": \"Estilo do relógio (bloqueado)\",\n  \"Thinking\": \"Pensando\",\n  \"Cancel wallpaper selection\": \"Cancelar seleção de papel de parede\",\n  \"Superpaste\": \"Supercolar\",\n  \"Bold\": \"Negrito\",\n  \"Brightness and volume\": \"Brilho e volume\",\n  \"Close window\": \"Fechar janela\",\n  \"Overlay: General\": \"Overlay: Geral\",\n  \"Sound output\": \"Saída de som\",\n  \"Conflicts with the shell's system tray implementation\": \"Conflita com a implementação de bandeja do sistema do shell\",\n  \"Elements\": \"Elementos\",\n  \"Audio input\": \"Entrada de áudio\",\n  \"Rows\": \"Linhas\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Sem chave API\\nDefina com /key SUA_CHAVE_API\",\n  \"CPU\": \"CPU\",\n  \"Time\": \"Hora\",\n  \"Cookie clock settings\": \"Configurações do relógio Cookie\",\n  \"Resources\": \"Recursos\",\n  \"Enable blur\": \"Ativar desfoque (blur)\",\n  \"Set the current API provider\": \"Definir provedor de API atual\",\n  \"Bar style\": \"Estilo da barra\",\n  \"Configuration\": \"Configuração\",\n  \"Tonal Spot\": \"Tonal Spot\",\n  \"Dial style\": \"Estilo do mostrador\",\n  \"API key set for %1\": \"Chave API definida para %1\",\n  \"Pinned\": \"Fixado\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Verifique se seu player tem suporte MPRIS\\nou tente desligar o filtro de player duplicado\",\n  \"Discussions\": \"Discussões\",\n  \"Precipitation\": \"Precipitação\",\n  \"Pick a wallpaper\": \"Escolha um papel de parede\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Fundo aleatório sazonal de osu!\\nImagem salva em ~/Pictures/Wallpapers\",\n  \"Press Super+G to open the overlay and pin the crosshair\": \"Aperte Super+G para abrir o overlay e fixar a mira\",\n  \"Save\": \"Salvar\",\n  \"Crosshair code (in Valorant's format)\": \"Código da mira (formato Valorant)\",\n  \"Windows\": \"Janelas\",\n  \"Copy path\": \"Copiar caminho\",\n  \"Make icons pinned by default\": \"Fixar ícones por padrão\",\n  \"Extra wallpaper zoom (%)\": \"Zoom extra do papel de parede (%)\",\n  \"Task Manager\": \"Gerenciador de Tarefas\",\n  \"Font weight\": \"Peso da fonte\",\n  \"On\": \"Ligado\",\n  \"Used for displaying numbers\": \"Usado para exibir números\",\n  \"Unknown command:\": \"Comando desconhecido:\",\n  \"Networking\": \"Rede\",\n  \"Scroll to Bottom\": \"Rolar para o fundo\",\n  \"If you want to somehow use fingerprint unlock...\": \"Se você quiser usar desbloqueio por digital de alguma forma...\",\n  \"Use system file picker\": \"Usar selecionador de arquivo do sistema\",\n  \"Fruit Salad\": \"Fruit Salad\",\n  \"Line-separated\": \"Separado por linha\",\n  \"Pin to taskbar\": \"Fixar na barra de tarefas\",\n  \"Use old sine wave cookie implementation\": \"Usar implementação antiga de onda senoidal cookie\",\n  \"Pinned on startup\": \"Fixado na inicialização\",\n  \"Welcome app\": \"App de Boas-vindas\",\n  \"Unknown function call: %1\": \"Chamada de função desconhecida: %1\",\n  \"Shell & utilities\": \"Shell & utilitários\",\n  \"Require password to power off/restart\": \"Exigir senha para desligar/reiniciar\",\n  \"Scroll to change brightness\": \"Rolar para mudar brilho\",\n  \"Lap\": \"Volta\",\n  \"Health:\": \"Saúde:\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"Se a opção anterior estiver desligada e esta ligada,\\nvocê ainda pode passar o mouse no fim do canto para abrir a barra lateral,\\ne a área restante pode ser usada para rolagem de volume/brilho\",\n  \"Approve\": \"Aprovar\",\n  \"Font family name (e.g., JetBrains Mono NF)\": \"Nome da família da fonte (ex: JetBrains Mono NF)\",\n  \"Quick\": \"Básico\",\n  \"Max allowed increase\": \"Aumento máximo permitido\",\n  \"Video Recording Path\": \"Caminho de Gravação de Vídeo\",\n  \"Corner open\": \"Abrir pelo canto\",\n  \"Enjoy your empty sidebar...\": \"Aproveite sua barra lateral vazia...\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Type /key to get started with online models\\nCtrl+O to expand sidebar\\nCtrl+P to pin sidebar\\nCtrl+D to detach sidebar\": \"Digite /key para começar com modelos online\\nCtrl+O para expandir barra lateral\\nCtrl+P para fixar\\nCtrl+D para destacar\",\n  \"Digital\": \"Digital\",\n  \"Balance brightness based on content\": \"Balancear brilho baseado no conteúdo\",\n  \"Open editor\": \"Abrir editor\",\n  \"Replace 󱕐   for \\\"Scroll ↓\\\", 󱕑   \\\"Scroll ↑\\\", L󰍽   \\\"LMB\\\", R󰍽   \\\"RMB\\\", 󱕒   \\\"Scroll ↑/↓\\\" and ⇞/⇟ for \\\"Page_↑/↓\\\"\": \"Substitui 󱕐   por \\\"Scroll ↓\\\", 󱕑   \\\"Scroll ↑\\\", L󰍽   \\\"Botão Esq.\\\", R󰍽   \\\"Botão Dir.\\\", 󱕒   \\\"Scroll ↑/↓\\\" e ⇞/⇟ por \\\"Page_↑/↓\\\"\",\n  \"Window\": \"Janela\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Botão direito para configurar\",\n  \"Refreshing (manually triggered)\": \"Atualizando (acionado manualmente)\",\n  \"Distro\": \"Distro\",\n  \"Prefixes\": \"Prefixos\",\n  \"e.g. 󰘴  for Ctrl, 󰘵  for Alt, 󰘶  for Shift, etc\": \"ex: 󰘴  para Ctrl, 󰘵  para Alt, 󰘶  para Shift, etc\",\n  \"Pressure\": \"Pressão\",\n  \"Second hand\": \"Ponteiro de segundos\",\n  \"To Do\": \"A Fazer\",\n  \"Silent\": \"Silencioso\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Usa Gemini para categorizar o papel de parede e escolhe um preset.\\nVocê precisa definir a chave API do Gemini na barra esquerda antes.\\nImagens são reduzidas para desempenho, mas por segurança,\\nnão selecione papéis de parede com informações sensíveis.\",\n  \"Move right\": \"Mover para direita\",\n  \"Auto\": \"Auto\",\n  \"Move to front\": \"Mover para frente\",\n  \"Top\": \"Topo\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"Copy\": \"Copiar\",\n  \"Weather\": \"Clima\",\n  \"Couldn't recognize music\": \"Não foi possível reconhecer a música\",\n  \"More comfortable viewing at night\": \"Visualização mais confortável à noite\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Não funcionou. Dicas:\\n- Verifique suas tags e configurações NSFW\\n- Se não tiver uma tag em mente, digite um número de página\",\n  \"Polling interval (m)\": \"Intervalo de atualização (m)\",\n  \"%1 notifications\": \"%1 notificações\",\n  \"Long break\": \"Pausa longa\",\n  \"Keep system awake\": \"Manter sistema acordado\",\n  \"Place at bottom\": \"Colocar no fundo\",\n  \"Wallpaper safety enforced\": \"Segurança de papel de parede forçada\",\n  \"Shutdown\": \"Desligar\",\n  \"Game mode\": \"Modo de jogo\",\n  \"<i>No further instruction provided</i>\": \"<i>Nenhuma instrução adicional fornecida</i>\",\n  \"Add task\": \"Adicionar tarefa\",\n  \"Policies\": \"Políticas\",\n  \"Discharging:\": \"Descarregando:\",\n  \"Translation goes here...\": \"Tradução vai aqui...\",\n  \"Depends on workspace\": \"Depende do workspace\",\n  \"Open recordings folder\": \"Abrir pasta de gravações\",\n  \"Vertical\": \"Vertical\",\n  \"Hide sussy/anime wallpapers\": \"Esconder wallpapers suspeitos/anime\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Aproveite! Você pode reabrir o app de boas-vindas a qualquer momento com <tt>Super+Shift+Alt+/</tt>. Para abrir as configurações, aperte <tt>Super+I</tt>\",\n  \"Math result\": \"Resultado matemático\",\n  \"Nothing here!\": \"Nada aqui!\",\n  \"Font used for Nerd Font icons\": \"Fonte usada para ícones Nerd Font\",\n  \"Illegal increment\": \"Incremento ilegal\",\n  \"Microphone\": \"Microfone\",\n  \"System uptime:\": \"Tempo de atividade:\",\n  \"Automatically suspends the system when battery is low\": \"Suspende automaticamente o sistema quando a bateria está fraca\",\n  \"Notifications\": \"Notificações\",\n  \"Pick random from this folder\": \"Escolher aleatório desta pasta\",\n  \"Interface Language\": \"Idioma da Interface\",\n  \"Reject\": \"Rejeitar\",\n  \"Clock style\": \"Estilo do relógio\",\n  \"Click to show\": \"Clique para mostrar\",\n  \"Hide clipboard images copied from sussy sources\": \"Esconder imagens da área de transferência de fontes suspeitas\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Wallpaper Anime SFW aleatório do Konachan\\nImagem salva em ~/Pictures/Wallpapers\",\n  \"Wind\": \"Vento\",\n  \"Bottom\": \"Inferior\",\n  \"Background\": \"Fundo\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Digite tags, ou \\\"%1\\\" para comandos\",\n  \"Usage: %1tool TOOL_NAME\": \"Uso: %1tool NOME_FERRAMENTA\",\n  \"Automatically hide\": \"Ocultar automaticamente\",\n  \"Focusing\": \"Focando\",\n  \"Charging:\": \"Carregando:\",\n  \"Task description\": \"Descrição da tarefa\",\n  \"See fewer\": \"Ver menos\",\n  \"Enable translator\": \"Ativar tradutor\",\n  \"Weather Service\": \"Serviço de Clima\",\n  \"Settings\": \"Configurações\",\n  \"Allow NSFW content\": \"Permitir conteúdo NSFW\",\n  \"Always show numbers\": \"Sempre mostrar números\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Podem ser imagens ou partes da tela com alguma contenção.\\nPode não ser sempre preciso.\\nIsso é feito com um algoritmo de processamento de imagem local, sem IA.\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Modelo atual: %1\\nDefina com %2model MODELO\",\n  \"System sound\": \"Som do sistema\",\n  \"City name\": \"Nome da cidade\",\n  \"Write something here...\\nUse '-' to create copyable bullet points, like this:\\n\\nSheep fricker\\n- 4x Slab\\n- 1x Boat\\n- 4x Redstone Dust\\n- 1x Sticky Piston\\n- 1x End Rod\\n- 4x Redstone Repeater\\n- 1x Redstone Torch\\n- 1x Sheep\": \"Escreva algo aqui...\\nUse '-' para criar marcadores copiáveis, assim:\\n\\nFarm de Ovelhas\\n- 4x Laje\\n- 1x Barco\\n- 4x Pó de Redstone\\n- 1x Pistão Aderente\\n- 1x Lâmpada do End\\n- 4x Repetidor de Redstone\\n- 1x Tocha de Redstone\\n- 1x Ovelha\",\n  \"Right to left\": \"Direita para esquerda\",\n  \"Choose file\": \"Escolher arquivo\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Pode ser melhor se você cometer muitos erros de digitação,\\nmas os resultados podem ser estranhos e falhar com siglas\\n(ex: \\\"GIMP\\\" pode não retornar o editor de imagem)\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Comandos, editar configs, buscar.\\nLeva um turno extra para mudar para modo de busca se necessário\",\n  \"Media\": \"Mídia\",\n  \"Constantly rotate\": \"Girar constantemente\",\n  \"No applications\": \"Nenhum aplicativo\",\n  \"Not connected\": \"Não conectado\",\n  \"Use macOS-like symbols for mods keys\": \"Usar símbolos estilo macOS para mods de teclas\",\n  \"Network\": \"Rede\",\n  \"Useless buttons\": \"Botões inúteis\",\n  \"Top-down\": \"De cima para baixo\",\n  \"Sound input\": \"Entrada de som\",\n  \"Close (Esc)\": \"Fechar (Esc)\",\n  \"No pending tasks\": \"Nenhuma tarefa pendente\",\n  \"Save chat\": \"Salvar chat\",\n  \"Clear all\": \"Limpar tudo\",\n  \"Unpin from Start\": \"Desafixar do Iniciar\",\n  \"Rect\": \"Retângulo\",\n  \"Aligns the date and quote to left, center or right depending on its position on the screen.\": \"Alinha data e citação à esquerda, centro ou direita dependendo da posição na tela.\",\n  \"Overlay: Crosshair\": \"Overlay: Mira\",\n  \"Circle to Search\": \"Circular para Pesquisar\",\n  \"Online via %1 | %2's model\": \"Online via %1 | Modelo de %2\",\n  \"(Plugged in)\": \"(Conectado)\",\n  \"Earbang protection\": \"Proteção auditiva\",\n  \"Edit quick toggles\": \"Editar botões rápidos\",\n  \"Numbers font\": \"Fonte dos números\",\n  \"Temperature must be between 0 and 2\": \"Temperatura deve ser entre 0 e 2\",\n  \"Productivity\": \"Produtividade\",\n  \"Eye protection\": \"Proteção ocular\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"Você precisa inserir sua chave API Gemini primeiro.\\nDigite /key na barra lateral para instruções.\",\n  \"Minute hand\": \"Ponteiro de minutos\",\n  \"Second precision\": \"Precisão de segundos\",\n  \"Wallpaper selector\": \"Seletor de papel de parede\",\n  \"Close\": \"Fechar\",\n  \"Critical warning\": \"Aviso crítico\",\n  \"Connect\": \"Conectar\",\n  \"Polkit\": \"Polkit\",\n  \"Locked\": \"Bloqueado\",\n  \"Connected\": \"Conectado\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Permite abrir barras laterais clicando ou passando mouse nos cantos, independente da posição da barra\",\n  \"Volume mixer\": \"Mixer de volume\",\n  \"Done\": \"Feito\",\n  \"12h am/pm\": \"12h am/pm\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Preço**: grátis. Dados usados para treinamento.\\n\\n**Instruções**: Logue na conta Google, permita o AI Studio criar projeto Google Cloud, volte e clique em Get API key\",\n  \"Regenerate\": \"Regenerar\",\n  \"Screen round corner\": \"Canto arredondado da tela\",\n  \"Nerd font icons\": \"Ícones Nerd Font\",\n  \"Unknown\": \"Desconhecido\",\n  \"Critically low battery\": \"Bateria criticamente fraca\",\n  \"Enable if you want clocks to show seconds accurately\": \"Ative se quiser que relógios mostrem segundos com precisão\",\n  \"Break\": \"Pausa\",\n  \"Quick markup (Ctrl+E)\": \"Marcação rápida (Ctrl+E)\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Usar algoritmo baseado em distância Levenshtein em vez de fuzzy\",\n  \"Identify Music\": \"Identificar Música\",\n  \"Fahrenheit unit\": \"Unidade Fahrenheit\",\n  \"Scroll to change volume\": \"Rolar para mudar volume\",\n  \"Number style\": \"Estilo do número\",\n  \"Circle selection\": \"Seleção circular\",\n  \"Workspaces shown\": \"Workspaces mostrados\",\n  \"Actions\": \"Ações\",\n  \"OK\": \"OK\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"O de hentai | Grande quantidade, muito NSFW, qualidade varia muito\",\n  \"Rainbow\": \"Arco-íris\",\n  \"Back\": \"Voltar\",\n  \"Layers\": \"Camadas\",\n  \"Random: osu! seasonal\": \"Aleatório: osu! seasonal\",\n  \"Pause\": \"Pausar\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Services\": \"Serviços\",\n  \"Title font\": \"Fonte do título\",\n  \"Use varying shapes for password characters\": \"Usar formas variadas para caracteres de senha\",\n  \"Group style\": \"Estilo de grupo\",\n  \"Main font\": \"Fonte principal\",\n  \"Show aim lines\": \"Mostrar linhas de mira\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Instruções**: Logue na conta Mistral, vá em Keys na barra lateral, clique em Create new key\",\n  \"Change password\": \"Alterar senha\",\n  \"Get the latest features and security improvements with\\nthe newest feature update.\\n\\n%1 packages\": \"Obtenha os últimos recursos e melhorias de segurança com\\na atualização mais recente.\\n\\n%1 pacotes\",\n  \"Load prompt from %1\": \"Carregar prompt de %1\",\n  \"On-screen keyboard\": \"Teclado virtual\",\n  \"No API key set for %1\": \"Nenhuma chave API definida para %1\",\n  \"Invalid model. Supported: \\n```\": \"Modelo inválido. Suportados: \\n```\",\n  \"Command rejected by user\": \"Comando rejeitado pelo usuário\",\n  \"Language\": \"Idioma\",\n  \"Add\": \"Adicionar\",\n  \"Save paths\": \"Caminhos de salvamento\",\n  \"Lock screen\": \"Tela de bloqueio\",\n  \"Light\": \"Claro\",\n  \"Right\": \"Direita\",\n  \"Edit\": \"Editar\",\n  \"Draggable\": \"Arrastável\",\n  \"Local Ollama model | %1\": \"Modelo local Ollama | %1\",\n  \"Display modifiers and keys in multiple keycap (e.g., \\\"Ctrl + A\\\" instead of \\\"Ctrl A\\\" or \\\"󰘴 + A\\\" instead of \\\"󰘴 A\\\")\": \"Mostrar modificadores e teclas em teclas múltiplas (ex: \\\"Ctrl + A\\\" em vez de \\\"Ctrl A\\\")\",\n  \"Restart\": \"Reiniciar\",\n  \"Move left\": \"Mover para esquerda\",\n  \"Forget\": \"Esquecer\",\n  \"More Bluetooth settings\": \"Mais configurações de Bluetooth\",\n  \"Fonts\": \"Fontes\",\n  \"Secured\": \"Seguro\",\n  \"Utility buttons\": \"Botões utilitários\",\n  \"Hibernate\": \"Hibernar\",\n  \"Unknown Artist\": \"Artista Desconhecido\",\n  \"Load chat\": \"Carregar chat\",\n  \"Anti-flashbang\": \"Proteção de brilho\",\n  \"Enabled\": \"Ativado\",\n  \"Always\": \"Sempre\",\n  \"Not secured\": \"Não seguro\",\n  \"Invalid arguments. Must provide `command`.\": \"Argumentos inválidos. Deve fornecer `command`.\",\n  \"Font family\": \"Família da fonte\",\n  \"Page %1\": \"Página %1\",\n  \"Resume\": \"Resumir\",\n  \"Web\": \"Web\",\n  \"Save chat to %1\": \"Salvar chat em %1\",\n  \"Tint app icons\": \"Colorir ícones de app\",\n  \"Color picker\": \"Seletor de cores\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Luz Noturna | Botão direito para alternar modo Auto\",\n  \"Inactive\": \"Inativo\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/ru_RU.json",
    "content": "{\n  \"Style: Blurred\": \"Размытие\",\n  \"Unknown device\": \"Неизв. устройство\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"Измените позже с /dark, /light, /wallpaper в лаунчере\\nЕсли цвета оболочки не меняются:\\n    1. Откройте правую панель через Super+N\\n    2. Нажмите «Рестарт Hyprland и Quickshell» в правом верхнем углу\",\n  \"No pending tasks\": \"Тут пусто!\",\n  \"Positioning\": \"Расположение\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Установить температуру (случайность) модели. Диапазон: 0–2 для Gemini, 0–1 для других. По умолчанию 0,5\",\n  \"Critical warning\": \"Критический %\",\n  \"Unknown Artist\": \"Неизвестный исполнитель\",\n  \"Web search\": \"Найти\",\n  \"Load prompt from %1\": \"Загрузка промпта из %1\",\n  \"Attach a file. Only works with Gemini.\": \"Прикрепить файл (только Gemini)\",\n  \"Reboot\": \"Перезагрузка\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"API-ключ:\\n\\n```txt\\n%1\\n```\",\n  \"Pinned on startup\": \"Закрепить при включении\",\n  \"Right\": \"Справа\",\n  \"Reboot to firmware settings\": \"Загрузка в UEFI\",\n  \"Automatically hide\": \"Автоскрытие\",\n  \"To Do\": \"Задачи\",\n  \"Full\": \"Полый\",\n  \"Select Language\": \"Выбор языка\",\n  \"Password\": \"Пароль\",\n  \"Bluetooth devices\": \"Bluetooth-устройства\",\n  \"Enable\": \"Вкл.\",\n  \"Elements\": \"Элементы\",\n  \"Start\": \"Пуск\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Случайные SFW аниме обои с Konachan\\nСохраняется в ~/Pictures/Wallpapers\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Популярный | Больше всего контента, качество нестабильно\",\n  \"System uptime:\": \"Аптайм:\",\n  \"illogical-impulse Welcome\": \"Добро пожаловать в illogical-impulse\",\n  \"Code saved to file\": \"Код сохранён\",\n  \"Info\": \"Инфо\",\n  \"Preferred wallpaper zoom (%)\": \"Зум обоев (%)\",\n  \"Time\": \"Время\",\n  \"Help & Support\": \"Помощь\",\n  \"Bubble\": \"Пузырчатый\",\n  \"Large images | God tier quality, no NSFW.\": \"Большие изображения | Отличное качество, без NSFW.\",\n  \"Dark\": \"Тёмный\",\n  \"Center clock\": \"Часы по центру экрана\",\n  \"Search, calculate or run\": \"Поиск, расчёт, запуск\",\n  \"Region height\": \"Высота области\",\n  \"Load chat\": \"Загрузить чат\",\n  \"Gives the model search capabilities (immediately)\": \"Вкл. поиск для модели (сразу)\",\n  \"Depends on workspace\": \"Зависит от пространства\",\n  \"Enter password\": \"Введите пароль\",\n  \"Local only\": \"Только локальные\",\n  \"at\": \"в\",\n  \"Math\": \"Математика\",\n  \"Consider plugging in your device\": \"Подключите зарядку\",\n  \"Workspaces shown\": \"Видимые пространства\",\n  \"Place the corners to trigger at the bottom\": \"Триггер-углы снизу\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Нет API-ключа\\nУстановите: /key ВАШ_КЛЮЧ\",\n  \"Auto (System)\": \"Авто (система)\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Стрелки - навигация, Enter - выбор\\nEsc или клик - отмена\",\n  \"Critically low battery\": \"Критически низкий заряд\",\n  \"Open editor\": \"Открыть редактор\",\n  \"%1 notifications\": \"%1 уведомлений\",\n  \"Region width\": \"Ширина области\",\n  \"Max allowed increase\": \"Макс. разница\",\n  \"Enable translator\": \"Переводчик в левой панели\",\n  \"Constantly rotate\": \"Постоянное вращение\",\n  \"Automatically suspends the system when battery is low\": \"Авто-сон при низком зар.\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"GPS не найден. Резервный метод.\",\n  \"Qt apps\": \"Qt-приложений\",\n  \"Color picker\": \"Пипетка\",\n  \"Interface\": \"Интерфейс\",\n  \"Tint app icons\": \"Тонировать иконки\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Выбор языка интерфейса.\\n«Авто» - системная локаль.\",\n  \"Local Ollama model | %1\": \"Локальная модель Ollama | %1\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Исп: <tt>%1superpaste КОЛИЧЕСТВО[i]</tt>\\nДобавьте <tt>i</tt> для изображений\\nПримеры:\\n<tt>%1superpaste 4i</tt> - последние 4 изображения\\n<tt>%1superpaste 7</tt> - последние 7 записей\",\n  \"Audio\": \"Звук\",\n  \"Corner style\": \"Стиль угла\",\n  \"No media\": \"Нет медиа\",\n  \"Unknown function call: %1\": \"Неизвестный вызов функции: %1\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Онлайн | Модель %1 | Быстрые, структурированные ответы. Недостатки: может уклоняться от задач; иногда придумывает вызовы функций\",\n  \"Volume\": \"Громкость\",\n  \"Medium\": \"Средний\",\n  \"Copy code\": \"Копировать код\",\n  \"Exceeded max allowed\": \"Превышен максимум\",\n  \"Keep right sidebar loaded\": \"Держать правую панель в ОЗУ\",\n  \"Left\": \"Слева\",\n  \"Rect\": \"Прямоугольное\",\n  \"Lap\": \"Круг\",\n  \"Screen snip\": \"Скриншот\",\n  \"Reset\": \"Ресет\",\n  \"Back\": \"Назад\",\n  \"Dark/Light toggle\": \"Тоггл темы\",\n  \"12h am/pm\": \"12ч am/pm\",\n  \"Download complete\": \"Загрузка завершена\",\n  \"Enable blur\": \"Включить размытие\",\n  \"Second hand\": \"Стиль секундной стрелки\",\n  \"Bar & screen\": \"Панель и экран\",\n  \"Discharging:\": \"Разряд:\",\n  \"Up %1\": \"Аптайм %1\",\n  \"Hour hand\": \"Стиль часовой стрелки\",\n  \"Clear chat history\": \"Очистить лог чата\",\n  \"Fruit Salad\": \"Фруктовый салат\",\n  \"%1 Safe Storage\": \"Безопасное хранилище %1\",\n  \"Hibernate\": \"Гибернация\",\n  \"Delete\": \"Удалить\",\n  \"OK\": \"OK\",\n  \"Settings\": \"Настройки\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"Обычно безопасно и нужно для браузера и AI-панели\\nПолезно при блокировке при запуске вместо менеджера дисплея (GDM, SDDM и т.д)\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Использовать Hyprlock (вместо Quickshell)\",\n  \"Crosshair code (in Valorant's format)\": \"Код прицела (формат Valorant)\",\n  \"Silent\": \"Тихий\",\n  \"Useless buttons\": \"Бесполезные кнопки\",\n  \"Hover to reveal\": \"Наведите для раскрытия\",\n  \"Wallpaper & Colors\": \"Обои и цвета\",\n  \"Auto\": \"Авто\",\n  \"Visibility\": \"Видимость\",\n  \"Shell & utilities\": \"Оболочки и утилит\",\n  \"Hollow\": \"Полый\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Сист. диалог файлов\\nПКМ - сделать поведением по умолчанию\",\n  \"On-screen display\": \"Экранное отображение\",\n  \"Dotfiles\": \"Dotfile\",\n  \"Search wallpapers\": \"Поиск обоев\",\n  \"Mic toggle\": \"Тоггл микрофона\",\n  \"Input\": \"Вход\",\n  \"Also unlock keyring\": \"Также разблокировать связку ключей\",\n  \"Configuration\": \"Конфиг\",\n  \"Keep system awake\": \"Оставлять систему включённой\",\n  \"Unknown command:\": \"Неизвестная команда:\",\n  \"Anime boorus\": \"Аниме-боору\",\n  \"To Do:\": \"Задачи:\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Gemini определяет тип обоев и подбирает пресет.\\nСначала укажите API-ключ Gemini в левой панели.\\nИзображения уменьшаются для производительности -\\nне выбирайте обои с конфиденциальными данными.\",\n  \"Bottom\": \"Снизу\",\n  \"Clear the current list of images\": \"Очистить список изображений\",\n  \"Sunrise\": \"Рассвет\",\n  \"Show app icons\": \"Показывать иконки приложений\",\n  \"Format\": \"Формат\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Убедитесь, что плеер поддерживает MPRIS\\nили отключите в конфиге «filterDuplicatePlayers»\",\n  \"Pause\": \"Пауза\",\n  \"Desktop\": \"Рабочий стол\",\n  \"Conflicts with the shell's system tray implementation\": \"Конфликт с треем оболочки\",\n  \"Your package manager is running\": \"Запущен пакетный менеджер\",\n  \"Conflicts with the shell's notification implementation\": \"Конфликт с уведомлениями оболочки\",\n  \"Unknown Album\": \"Неизв. альбом\",\n  \"Pick wallpaper image on your system\": \"Выбрать обои из файлов\",\n  \"Used:\": \"Исп:\",\n  \"Cheat sheet\": \"Шпаргалка\",\n  \"Clock style\": \"Стиль часов\",\n  \"Paired\": \"Сопряжён\",\n  \"Documentation\": \"Документация\",\n  \"No\": \"Нет\",\n  \"Pills\": \"Таблеточный\",\n  \"Thought\": \"Размышление\",\n  \"When this is off you'll have to click\": \"При отключении требуется клик\",\n  \"Logout\": \"Выйти\",\n  \"Tip: Close a window with Super+Q\": \"Совет: Super+Q - закрыть окно\",\n  \"Finished tasks will go here\": \"Выполненные задачи будут тут\",\n  \"Terminal: Harmony (%)\": \"Терминал: гармония (%)\",\n  \"Corner open\": \"Открытие из угла\",\n  \"Shell conflicts killer\": \"Устранение конфликтов\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Чисто | Отличное качество, без NSFW\",\n  \"Scroll to change volume\": \"Скролл - громкость\",\n  \"Wind\": \"Ветер\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API-ключ установлен\\nИзменить: /key ВАШ_КЛЮЧ\",\n  \"Neutral\": \"Нейтральность\",\n  \"12h AM/PM\": \"12ч AM/PM\",\n  \"Number show delay when pressing Super (ms)\": \"Задержка номеров Super (мс)\",\n  \"Fill\": \"Залитый\",\n  \"Always show numbers\": \"Всегда показывать номера\",\n  \"Dot\": \"Точка\",\n  \"Provider set to\": \"Провайдер:\",\n  \"Unknown Title\": \"Неизв. название\",\n  \"Anime\": \"Аниме\",\n  \"Refreshing (manually triggered)\": \"Обновление (вручную)\",\n  \"Dock\": \"Докбар\",\n  \"Require password to power off/restart\": \"Пароль для выключения/перезагрузки\",\n  \"Line\": \"Линейный\",\n  \"Weather\": \"Погода\",\n  \"All-rounder | Good quality, decent quantity\": \"Универсал | Хорошее качество, много контента\",\n  \"Scale (%)\": \"Масштаб (%)\",\n  \"Copy\": \"Копировать\",\n  \"Usage\": \"Исп.\",\n  \"Set the tool to use for the model.\": \"Задать инструмент модели\",\n  \"Disable tools\": \"Выкл. инструменты\",\n  \"Connect\": \"Подключить\",\n  \"Allow NSFW\": \"Разрешить NSFW\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Ошибка регистрации. Проверьте вручную командой <tt>warp-cli</tt>\",\n  \"Time to full:\": \"До полного заряда:\",\n  \"Session\": \"Сессия\",\n  \"Services\": \"Службы\",\n  \"Nothing here!\": \"Тут пусто!\",\n  \"Overview\": \"Обзор\",\n  \"Random: osu! seasonal\": \"Случайные: сезонные osu!\",\n  \"If you want to somehow use fingerprint unlock...\": \"Если хотите разблокировку по отпечатку пальца...\",\n  \"Minute hand\": \"Стиль минутной стрелки\",\n  \"Notifications\": \"Уведомления\",\n  \"Enable if you want clocks to show seconds accurately\": \"Включите для отображения секунд на часах\",\n  \"Timer\": \"Таймер\",\n  \"System prompt\": \"Системный промпт\",\n  \"Classic\": \"Классический\",\n  \"Close\": \"Закрыть\",\n  \"Disconnect\": \"Отключить\",\n  \"Go to source (%1)\": \"Перейти к источнику (%1)\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | ПКМ для настройки\",\n  \"Forget\": \"Забыть\",\n  \"Output\": \"Выход\",\n  \"Date style\": \"Стиль даты\",\n  \"System\": \"Система\",\n  \"Usage: %1tool TOOL_NAME\": \"Исп: %1tool ИМЯ_ИНСТРУМЕНТА\",\n  \"Workspaces\": \"Пространства на панели\",\n  \"Calendar\": \"Дата\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Инструкция**: войдите в Mistral, откройте «Ключи» на боковой панели, нажмите «Создать ключ»\",\n  \"Volume limit\": \"Порог звука\",\n  \"Sunset\": \"Закат\",\n  \"Dial style\": \"Стиль циферблата\",\n  \"Hi there! First things first...\": \"Привет! Начнём с главного...\",\n  \"Save chat to %1\": \"Сохранить чат в %1\",\n  \"Security\": \"Безопасность\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Всего токенов\\nВход: %1\\nВыход: %2\",\n  \"Cancel wallpaper selection\": \"Отменить выбор обоев\",\n  \"Terminal: Harmonize threshold\": \"Терминал: порог гармонизации\",\n  \"Be patient...\": \"Подождите...\",\n  \"Utility buttons\": \"Служебные кнопки\",\n  \"Tonal Spot\": \"Тональное пятно\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Плавное изменение и ограничение громкости\",\n  \"Set the current API provider\": \"Задать API-провайдер\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Ошибка подключения. Проверьте вручную командой <tt>warp-cli</tt>\",\n  \"Networking\": \"Сеть\",\n  \"Tint icons\": \"Тонировать иконки\",\n  \"Low battery\": \"Низкий заряд\",\n  \"Make icons pinned by default\": \"Закреплять все иконки\",\n  \"Get the next page of results\": \"Следующая стр. результатов\",\n  \"Invalid API provider. Supported: \\n-\": \"Неверный провайдер. Поддерживаются:\\n-\",\n  \"Show \\\"Locked\\\" text\": \"Показывать текст «Заблокировано»\",\n  \"Not visible to model\": \"Не видно модели\",\n  \"Lock screen\": \"Экран блокировки\",\n  \"Save to Downloads\": \"В «Загрузки»\",\n  \"Expressive\": \"Выразительность\",\n  \"Jump to current month\": \"Перейти к тек. месяцу\",\n  \"Bold\": \"Жирный\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Только вайфу | Отличное качество, мало контента\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Переключить тему\\n(применяется при выборе обоев)\",\n  \"Visualize region\": \"Визуализация области\",\n  \"Quote\": \"Цитата\",\n  \"Sleep\": \"Сон\",\n  \"Hit \\\"/\\\" to search\": \"Нажмите «/» для поиска\",\n  \"Hug\": \"Захват\",\n  \"Report a Bug\": \"Сообщить об ошибке\",\n  \"Precipitation\": \"Осадки\",\n  \"Model set to %1\": \"Модель: %1\",\n  \"Rows\": \"Строки\",\n  \"Top\": \"Сверху\",\n  \"Long break\": \"Длинный перерыв\",\n  \"Superpaste\": \"Superpaste\",\n  \"Screen round corner\": \"Скруглённые углы\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Онлайн | Модель Google\\nНовее, медленнее предшественника, но более качественные ответы\",\n  \"Rainbow\": \"Радуга\",\n  \"Weeb\": \"Аниме-боору\",\n  \"Large language models\": \"Большие языковые модели\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Онлайн-модели запрещены\\n\\nУправляется параметром `policies.ai`\",\n  \"Policies\": \"Политики\",\n  \"Temperature must be between 0 and 2\": \"Температура должна быть от 0 до 2\",\n  \"Automatic suspend\": \"Авто-сон\",\n  \"Extra wallpaper zoom (%)\": \"Дополнительный зум обоев (%)\",\n  \"GitHub\": \"GitHub\",\n  \"%1 | Right-click to configure\": \"%1 | ПКМ для настройки\",\n  \"Edit directory\": \"Изменить папку\",\n  \"Action\": \"Действие\",\n  \"Search\": \"Поиск\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"Совет: ПКМ по группе - раскрыть\",\n  \"Bar\": \"Панель\",\n  \"Clipboard\": \"Буфер обмена\",\n  \"Stopwatch\": \"Секундомер\",\n  \"Enter text to translate...\": \"Введите текст для перевода...\",\n  \"App\": \"Приложение\",\n  \"Sides\": \"Стороны\",\n  \"No active player\": \"Нет активного плеера\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Не все параметры доступны здесь. Проверьте конфиг через кнопку «Конфиг» в углу или откройте %1 вручную.\",\n  \"Math result\": \"Результат вычисления\",\n  \"Fidelity\": \"Точность\",\n  \"Prefixes\": \"Префиксы\",\n  \"Terminal\": \"Терминала\",\n  \"Incorrect password\": \"Неверный пароль\",\n  \"Line-separated\": \"Разделение по строкам\",\n  \"Always\": \"Всегда\",\n  \"☕ Break: %1 minutes\": \"☕ Перерыв: %1 мин\",\n  \"Depends on sidebars\": \"Зависит от панелей\",\n  \"Tool set to: %1\": \"Инструмент: %1\",\n  \"Save chat\": \"Сохранить чат\",\n  \"Keybinds\": \"Бинды\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Лучше при частых опечатках,\\nно результаты могут быть странными - аббревиатуры не всегда работают\\n(например, «GIMP» может не найти редактор)\",\n  \"Choose model\": \"Выбрать модель\",\n  \"Base URL\": \"Базовый URL\",\n  \"Float\": \"Флоат\",\n  \"Invalid arguments. Must provide `command`.\": \"Неверные аргументы. Укажите `command`.\",\n  \"Fully charged\": \"Полностью заряжена\",\n  \"Earbang protection\": \"Защита ушей\",\n  \"Low warning\": \"Увед. о низком %\",\n  \"Advanced\": \"Прочее\",\n  \"Scroll to change brightness\": \"Скролл - яркость\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Загружен системный промпт\\n\\n---\\n\\n%1\",\n  \"Show next time\": \"Показать в след. раз\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Текущий инструмент: %1\\nУстановить: %2tool ИНСТРУМЕНТ\",\n  \"Unread indicator: show count\": \"Счётчик непрочитанных\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Не удалось. Советы:\\n- Проверьте теги и настройки NSFW\\n- Если тег неизвестен, введите номер страницы\",\n  \"Dots\": \"С точками\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Volume mixer\": \"Микшер\",\n  \"Config file\": \"Конфиг\",\n  \"API key set for %1\": \"API-ключ установлен для %1\",\n  \"Shell command\": \"Команда оболочки\",\n  \"Reload Hyprland & Quickshell\": \"Рестарт Hyprland и Quickshell\",\n  \"Resources\": \"Ресурсы\",\n  \"Brightness\": \"Яркость\",\n  \"Unknown\": \"Неизвестно\",\n  \"Polling interval (ms)\": \"Интервал опроса (мс)\",\n  \"Lock\": \"Блокировка\",\n  \"Thinking\": \"Размышление\",\n  \"Approve\": \"Одобрить\",\n  \"Unfinished\": \"Незавершённые\",\n  \"Random: Konachan\": \"Случайные: Konachan\",\n  \"Connected\": \"Подключено\",\n  \"Wallpaper safety enforced\": \"Фильтр безоп. обоев вкл\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Неверные аргументы. Укажите `key` и `value`.\",\n  \"24h\": \"24ч\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Открытие панелей через углы экрана\",\n  \"Bar style\": \"Стиль панели\",\n  \"Load:\": \"Исп:\",\n  \"Open file link\": \"Открыть ссылку на файл\",\n  \"Ignored if terminal theming is not enabled\": \"Игнорируется без темы терминала\",\n  \"Shutdown\": \"Выключить\",\n  \"Hour marks\": \"Деления часов\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Случайный сезонный фон osu!\\nСохраняется в ~/Pictures/Wallpapers\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Текущая модель: %1\\nУстановить: %2model МОДЕЛЬ\",\n  \"Connect to Wi-Fi\": \"Подкл. к Wi-Fi\",\n  \"... and %1 more\": \"... и ещё %1\",\n  \"Cookie clock settings\": \"Настройки «Cookie»-часов\",\n  \"Looks a bit softer and more consistent with different number of sides,\\nbut has less impressive morphing\": \"Выглядит мягче и уместнее с разным количеством углов,\\nно сжатие может выглядить менее впечатляюще\",\n  \"Makes the clock always rotate. This is extremely expensive\\n(expect 50% usage on Intel UHD Graphics) and thus impractical.\": \"Часы будут постоянно вращаться. Это крайне ресурсозатратно\\n(примерно 50% использования Intel UHD Graphics) и фактически бесполезно.\",\n  \"Can only be turned on using the 'Dots' or 'Full' dial style for aesthetic reasons\": \"Может быть активировано только в режимах 'С точками' и 'Полностью залитый' по эстетическим причинам\",\n  \"Can't be turned on when using 'Numbers' dial style for aesthetic reasons\": \"Не может быть включено, когда используется стиль 'С числами' по эстетическим причинам\",\n  \"Brightness and volume\": \"Яркость и громкость\",\n  \"Choose file\": \"Выбор обоев\",\n  \"Invalid model. Supported: \\n```\": \"Неверная модель. Поддерживаемые: \\n```\",\n  \"Task Manager\": \"Диспетчер задач\",\n  \"Charging:\": \"Заряд:\",\n  \"Illegal increment\": \"Недопустимый шаг\",\n  \"Total:\": \"Всего:\",\n  \"or\": \"или\",\n  \"Battery\": \"Батарея\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Время уведомления (мс)\",\n  \"Cancel\": \"Отмена\",\n  \"Locked\": \"Заблокировано\",\n  \"Temperature: %1\": \"Температура: %1\",\n  \"Hover to trigger\": \"Навед. для триггера\",\n  \"Command rejected by user\": \"Команда отклонена\",\n  \"User agent (for services that require it)\": \"User agent (для сервисов)\",\n  \"Saved to %1\": \"Сохранено в %1\",\n  \"Emojis\": \"Эмодзи\",\n  \"Color generation\": \"Генерация цвета для...\",\n  \"Welcome app\": \"Приветствие\",\n  \"Humidity\": \"Влажность\",\n  \"Page %1\": \"Страница %1\",\n  \"Feels like %1\": \"Ощущается как %1\",\n  \"Distro\": \"Дистрибутив\",\n  \"Transparency\": \"Включить прозрачность\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 задач\",\n  \"Markdown test\": \"Тест Markdown\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Неверный инструмент. Поддерживаемые:\\n- %1\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Хентай | Много контента, много NSFW, качество нестабильно\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Resume\": \"Продолжить\",\n  \"Work safety\": \"Безопасность\",\n  \"Temperature\\nChange with /temp VALUE\": \"Температура\\nИзменить: /temp ЗНАЧЕНИЕ\",\n  \"Terminal: Foreground boost (%)\": \"Терминал: усиление переднего плана (%)\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Ночной режим | ПКМ для авторежима\",\n  \"Closet\": \"Скрыто\",\n  \"Yes\": \"Да\",\n  \"Columns\": \"Столбцы\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Установить API-ключ командой %4\\n\\nПосмотреть ключ: передайте «get»<br/>\\n\\n### Для %1:\\n\\n**Ссылка**: %2\\n\\n%3\",\n  \"Kill conflicting programs?\": \"Убить конфликтующие процессы?\",\n  \"For storing API keys and other sensitive information\": \"Для хранения API-ключей и конфид. данных\",\n  \"Reject\": \"Отклонить\",\n  \"Set API key\": \"Установить API-ключ\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Заметки для Zerochan:\\n- Необходимо указать цвет\\n- Укажите имя пользователя в `sidebar.booru.zerochan.username`. [Без этого вас могут заблокировать](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous)!\",\n  \"Content\": \"В контенте\",\n  \"Pomodoro\": \"Помодоро\",\n  \"Vertical\": \"Вертикальный\",\n  \"Pick a wallpaper\": \"Выбрать обои\",\n  \"Load chat from %1\": \"Загрузить чат из %1\",\n  \"Launch on startup\": \"Запускать при старте системы\",\n  \"Add\": \"Добавить\",\n  \"Style: general\": \"Основное\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Алг. Левенштейна вместо нечёткого\",\n  \"Shell & utilities theming must also be enabled\": \"Также нужна тема оболочки\",\n  \"Workspace\": \"Пространство\",\n  \"Translator\": \"Перевод\",\n  \"Free:\": \"Есть:\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Длинный перерыв: %1 мин\",\n  \"Value scroll\": \"Скролл значений\",\n  \"Bar position\": \"Положение панели\",\n  \"Language\": \"Язык\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Текущий API: %1\\nУстановить: %2mode ПРОВАЙДЕР\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"На большинстве устройств можно зажать кнопку питания для принудительного выключения\\nЭто лишь немного снижает риск случайного выключения\",\n  \"AI\": \"ИИ\",\n  \"Task description\": \"Описание задачи\",\n  \"Add task\": \"Добавить задачу\",\n  \"Donate\": \"Поддержать\",\n  \"Disable NSFW content\": \"Выкл. NSFW\",\n  \"Set the system prompt for the model.\": \"Задать системный промпт\",\n  \"Done\": \"Готово\",\n  \"Focus\": \"Фокус\",\n  \"View Markdown source\": \"Исходник Markdown\",\n  \"Border\": \"Круговой\",\n  \"Temperature set to %1\": \"Температура: %1\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Сообщение модели... «%1» для команд\",\n  \"Translation goes here...\": \"Перевод появится здесь...\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Держит правую панель в памяти для быстрого открытия\\n(~15 МБ ОЗУ постоянно). Задержка зависит от произв. системы.\\nКастомное ядро (linux-cachyos) может помочь\",\n  \"For desktop wallpapers | Good quality\": \"Для обоев рабочего стола | Хорошее качество\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Фокус: %1 мин\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Текущий системный промпт\\n\\n---\\n\\n%1\",\n  \"About\": \"Система\",\n  \"Quick\": \"Главное\",\n  \"General\": \"Общее\",\n  \"UV Index\": \"УФ-индекс\",\n  \"Force dark mode in terminal\": \"Тёмная тема в терминале\",\n  \"%1 characters\": \"%1 символов\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Цена**: бесплатно. Данные используются для обучения.\\n\\n**Инструкция**: войдите в Google, разрешите AI Studio создать проект, вернитесь и нажмите «Получить API-ключ»\",\n  \"Monochrome\": \"Монохром\",\n  \"Details\": \"Детали\",\n  \"Issues\": \"Проблемы\",\n  \"Keyboard toggle\": \"Тоггл клавиатуры\",\n  \"Download\": \"Скачать\",\n  \"%1 does not require an API key\": \"%1 не требует API-ключа\",\n  \"Style & wallpaper\": \"Стиль и обои\",\n  \"Second precision\": \"С секундами\",\n  \"Group style\": \"Стиль групп\",\n  \"Break\": \"Перерыв\",\n  \"Run\": \"Запуск\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Приятного использования! Открыть приветствие: <tt>Super+Shift+Alt+/</tt>. Настройки: <tt>Super+I</tt>\",\n  \"Interface Language\": \"Язык интерфейса\",\n  \"Game mode\": \"Игровой режим\",\n  \"Usage: %1save CHAT_NAME\": \"Исп: %1save ИМЯ_ЧАТА\",\n  \"Thin\": \"Тонкий\",\n  \"Light\": \"Светлый\",\n  \"When not fullscreen\": \"Не в фулл-скрине\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Команды, редактирование конфигов, поиск.\\nПри необходимости переключается в режим поиска\",\n  \"Privacy Policy\": \"Политика конфиденциальности\",\n  \"Timeout (ms)\": \"Таймаут (мс)\",\n  \"Allow NSFW content\": \"Разрешить NSFW\",\n  \"Edit\": \"Изменить\",\n  \"Digits in the middle\": \"Цифры по центру\",\n  \"Weather Service\": \"Погодный сервис\",\n  \"Background\": \"Обои\",\n  \"Pick random from this folder\": \"Случайный из папки\",\n  \"Pressure\": \"Давление\",\n  \"Save\": \"Сохранить\",\n  \"Time to empty:\": \"До разряда:\",\n  \"Place at bottom\": \"Разместить снизу\",\n  \"Switched to search mode. Continue with the user's request.\": \"Переключено в поиск. Продолжайте.\",\n  \"Performance Profile toggle\": \"Тоггл профиля произв.\",\n  \"Sidebars\": \"Боковые панели\",\n  \"Usage: %1load CHAT_NAME\": \"Исп: %1load ИМЯ_ЧАТА\",\n  \"Auto styling with Gemini\": \"Авто-стиль с Gemini\",\n  \"No API key set for %1\": \"API-ключ не установлен для %1\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Введите теги или «%1» для команд\",\n  \"Discussions\": \"Обсуждения\",\n  \"Tray\": \"Трей\",\n  \"Numbers\": \"С числами\",\n  \"Intelligence\": \"ИИ\",\n  \"Open network portal\": \"Открыть сетевой портал\",\n  \"<i>No further instruction provided</i>\": \"<i>Инструкция не предоставлена</i>\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"Языка нет или перевод неполный?\\nМожно сгенерировать перевод через Gemini.\\n1. Откройте левую панель (Super+A), выберите модель Gemini\\n2. Введите /key и следуйте инструкциям\\n3. Введите /key ВАШ_КЛЮЧ\\n4. Введите код локали ниже и нажмите «Сгенерировать»\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Код локали, напр. ru_RU, de_DE, zh_CN...\",\n  \"Select language\": \"Выбрать язык\",\n  \"Generate translation with Gemini\": \"Сгенерировать перевод с Gemini\",\n  \"Generating...\\nDon't close this window!\": \"Генерация...\\nНе закрывайте окно!\",\n  \"Generate\\nTypically takes 2 minutes\": \"Сгенерировать\\nОбычно ~2 мин\",\n  \"Use system file picker\": \"Системный файл-менеджер\",\n  \"Wallpaper selector\": \"Выбор обоев\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"Если предыдущий параметр выключен, а этот включён,\\nможно навести на край угла для открытия панели,\\nоставшаяся область работает для прокрутки громкости/яркости\",\n  \"Copy path\": \"Копировать путь\",\n  \"Windows\": \"Окон\",\n  \"Regenerate\": \"Перегенерировать\",\n  \"Microphone\": \"Микрофон\",\n  \"Unmuted\": \"Размут\",\n  \"System sound\": \"Системный звук\",\n  \"Enable now\": \"Вкл. сейчас\",\n  \"Night Light\": \"Ночной свет\",\n  \"Show aim lines\": \"Показать линии прицела\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Зачем это нужно:\\nПри ненулевых значениях не срабатывает при достижении угла\\nпо горизонтальному краю, но срабатывает по вертикальному\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"Зарядите устройство!\\nАвто-сон при %1%\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"Пример: eroge на одном пространстве, тёмный Discord на другом\",\n  \"Couldn't recognize music\": \"Музыка не распознана\",\n  \"Automatic\": \"Автоматически\",\n  \"Hint target regions\": \"Обводка областей\",\n  \"Eye protection\": \"Защита глаз\",\n  \"Layers\": \"Слоёв\",\n  \"Listening...\": \"Слушаю...\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"ЛКМ - вкл/выкл\\nПКМ - изменить размер\\nПрокрутка - поменять позицию\",\n  \"Identify Music\": \"Определить музыку\",\n  \"Quick toggles\": \"Быстрые тогглы\",\n  \"Hide sussy/anime wallpapers\": \"Скрывать подозрительные/аниме обои\",\n  \"Android\": \"Android\",\n  \"Show\": \"Показать\",\n  \"Muted\": \"Мут\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Аудиовход | ПКМ - микшер и выбор устройства\",\n  \"Region selector (screen snipping/Google Lens)\": \"Выбор области (Скрин / Google Lens)\",\n  \"Total duration timeout (s)\": \"Макс. длительность (сек)\",\n  \"Music Recognition\": \"Поиск музыки\",\n  \"Night Light | Right-click to configure\": \"Ночной свет | ПКМ для настройки\",\n  \"Anti-flashbang (experimental)\": \"Анти-вспышка (эксперим)\",\n  \"Digital clock settings\": \"Настройки цифровых часов\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Могут быть изображения или части экрана с содержимым.\\nМожет быть неточно.\\nИспользуется локальный алгоритм обработки изображений, без ИИ.\",\n  \"Polling interval (m)\": \"Интервал опроса (мин)\",\n  \"Inactive\": \"Неактивно\",\n  \"Authentication\": \"Аутентификация\",\n  \"Full warning\": \"Уведомление о полном заряде при проценте\",\n  \"Power Profile\": \"Профиль питания\",\n  \"Content region\": \"Обл. контента\",\n  \"Internet\": \"Интернет\",\n  \"Record\": \"Запись\",\n  \"Circle selection\": \"Круговое выдел.\",\n  \"Edit quick toggles\": \"Изменить быстрые тогглы\",\n  \"Virtual Keyboard\": \"Вирт. клавиатура\",\n  \"Music Recognized\": \"Музыка распознана\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Make sure you have songrec installed\": \"Убедитесь, что songrec установлен\",\n  \"Dark Mode\": \"Тёмный режим\",\n  \"Animate time change\": \"Анимировать смену времени\",\n  \"It may take a few seconds to update\": \"Обновление займёт неск. секунд\",\n  \"Polling interval (s)\": \"Интервал опроса (сек)\",\n  \"Perhaps what you're listening to is too niche\": \"Возможно, это слишком нишевая музыка\",\n  \"Fahrenheit unit\": \"По Фаренгейту\",\n  \"Sliders\": \"Ползунки\",\n  \"Roman\": \"Римский\",\n  \"Number style\": \"Стиль чисел\",\n  \"Intensity\": \"Интенсивность\",\n  \"Google Lens\": \"Google Lens\",\n  \"Circle\": \"Круговое\",\n  \"Hide clipboard images copied from sussy sources\": \"Скрывать изображения из подозрительных источников\",\n  \"Scroll to Bottom\": \"В конец\",\n  \"Enabled\": \"Вкл.\",\n  \"Nothing\": \"Тут пусто!\",\n  \"Audio input\": \"Аудиовход\",\n  \"with vertical offset\": \"Смещением по вертикали\",\n  \"Padding\": \"Отступ\",\n  \"Please unplug the charger\": \"Отключите зарядку\",\n  \"Show notifications\": \"Показ. уведомления\",\n  \"Path copied\": \"Путь скопирован\",\n  \"On-screen keyboard\": \"Экр. клавиатура\",\n  \"City name\": \"Название города (латиница)\",\n  \"Click to cycle through power profiles\": \"Клик - смена профиля питания\",\n  \"Recognize music | Right-click to toggle source\": \"Найти музыку | ПКМ - сменить источник\",\n  \"Use old sine wave cookie implementation\": \"Старая реализация Cookie (синусоида)\",\n  \"Rectangular selection\": \"Обычное выделение\",\n  \"Audio output\": \"Аудиовыход\",\n  \"Circle to Search\": \"Обведите для поиска\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Аудиовыход | ПКМ - микшер и выбор устройства\",\n  \"Enable GPS based location\": \"Вкл. геолокацию по GPS\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"Сначала введите API-ключ Gemini.\\nНаберите /key в панели для инструкций.\",\n  \"Sounds\": \"Звуки\",\n  \"Active\": \"Активно\",\n  \"Keep awake\": \"Не давать спать\",\n  \"Auto,\": \"Авто,\",\n  \"Normal\": \"Арабский\",\n  \"Force hover open at absolute corner\": \"Форс-открытие по углу\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Открыть конфиг оболочки\\nПКМ - скопировать путь\",\n  \"Recognize music\": \"Найти музыку\",\n  \"Stroke width\": \"Толщина обводки\",\n  \"Use varying shapes for password characters\": \"Разные фигуры для символов пароля\",\n  \"Battery full\": \"Батарея заряжена\",\n  \"Image source\": \"Источник изображения (ссылка)\",\n  \"Restart\": \"Перезапустить\",\n  \"Close (Esc)\": \"Закрыть (Esc)\",\n  \"Actions\": \"Действия\",\n  \"Font used for Nerd Font icons\": \"Шрифт иконок Nerd Font\",\n  \"CPU\": \"ЦП\",\n  \"Online | Google's model\\nPro-level intelligence at the speed and pricing of Flash.\": \"Онлайн | Модель Google\\nИнтеллект уровня Pro по скорости и цене Flash.\",\n  \"End session\": \"Завершить сессию\",\n  \"No applications\": \"Тут пусто!\",\n  \"Description font size\": \"Размер шрифта описания\",\n  \"Other\": \"Другое\",\n  \"More volume settings\": \"Доп. настройки звука\",\n  \"Split buttons\": \"Разделённые кнопки\",\n  \"Open\": \"Открыть\",\n  \"Output device\": \"Устр. вывода\",\n  \"No new notifications\": \"Нет новых уведомлений\",\n  \"Pin to taskbar\": \"Закрепить на панели\",\n  \"Pin to Start\": \"Закрепить в пуске\",\n  \"Not secured\": \"Не защищено\",\n  \"Font family name (e.g., Google Sans Flex)\": \"Шрифт (напр, Google Sans Flex)\",\n  \"Digital\": \"Цифровой\",\n  \"Notes\": \"Заметки\",\n  \"File Explorer\": \"Проводник\",\n  \"Stopping...\": \"Остановка...\",\n  \"Used for code and terminal\": \"Для кода и терминала\",\n  \"Video Recording Path\": \"Путь для видеозаписей\",\n  \"Enjoy your empty sidebar...\": \"Наслаждайтесь пустой панелью...\",\n  \"Cookie\": \"Cookie MD3\",\n  \"Save paths\": \"Сохранить пути\",\n  \"Productivity\": \"Продуктивность\",\n  \"Not connected\": \"Не подключено\",\n  \"Battery: %1%2\": \"Батарея: %1%2\",\n  \"Command\": \"Команда\",\n  \"Tooltips\": \"Подсказки\",\n  \"Show hidden icons\": \"Показать скрытые значки\",\n  \"Used for headings and titles\": \"Для заголовков\",\n  \"Top-down\": \"Сверху вниз\",\n  \"of %1\": \"из %1\",\n  \"System updates (Arch only)\": \"Апдейты системы (только Arch)\",\n  \"Apps\": \"Приложения\",\n  \"Main font\": \"Основной шрифт\",\n  \"Parallax\": \"Параллакс\",\n  \"Move left\": \"Переместить влево\",\n  \"Anti-flashbang\": \"Анти-вспышка\",\n  \"Desktop %1\": \"Рабочий Стол %1\",\n  \"%1\\nInternet access\": \"%1\\nДоступ в интернет\",\n  \"(Plugged in)\": \"(Подключено)\",\n  \"Off\": \"Выкл\",\n  \"Overlay: General\": \"Игровой оверлей\",\n  \"Enable opening zoom animation\": \"Анимация зума при открытии\",\n  \"More Internet settings\": \"Доп. сетевые настройки\",\n  \"Nerd font icons\": \"Иконки Nerd Font\",\n  \"Display modifiers and keys in multiple keycap (e.g., \\\"Ctrl + A\\\" instead of \\\"Ctrl A\\\" or \\\"󰘴 + A\\\" instead of \\\"󰘴 A\\\")\": \"Показывать модификаторы с разделителем (напр, «Ctrl + A» вместо «Ctrl A»)\",\n  \"Change password\": \"Изменить пароль\",\n  \"Check interval (mins)\": \"Интервал пров. (мин)\",\n  \"On\": \"Вкл\",\n  \"Task View\": \"Просм. задач\",\n  \"Secured\": \"Защищено\",\n  \"Click to show\": \"Зажать для раскрытия\",\n  \"Center icons\": \"Иконки по центру\",\n  \"Rectangle\": \"Прямоугол.\",\n  \"Creativity\": \"Творчество\",\n  \"Sound output\": \"Звуковой микшер\",\n  \"Copy region (LMB) or annotate (RMB)\": \"Копировать область (ЛКМ) или разметить (ПКМ)\",\n  \"e.g. 󰘴  for Ctrl, 󰘵  for Alt, 󰘶  for Shift, etc\": \"напр. 󰘴 для Ctrl, 󰘵 для Alt, 󰘶 для Shift и т.д.\",\n  \"Unpin from taskbar\": \"Открепить от панели\",\n  \"Snipping area\": \"Область скриншота\",\n  \"Font roundness\": \"Скруглённость\",\n  \"Numbers font\": \"Шрифт чисел\",\n  \"Move right\": \"Переместить вправо\",\n  \"Unknown Application\": \"Неизв. приложение\",\n  \"Used for decorative/expressive text\": \"Для декоративного / выразительного текста\",\n  \"Used for reading large blocks of text\": \"Для чтения больших блоков\",\n  \"Type /key to get started with online models\\nCtrl+O to expand sidebar\\nCtrl+P to pin sidebar\\nCtrl+D to detach sidebar\": \"Введите /key для онлайн-моделей\\nCtrl+O - расширить панель\\nCtrl+P - закрепить панель\\nCtrl+D - открепить панель\",\n  \"Health:\": \"Здоровье:\",\n  \"Widgets\": \"Виджеты\",\n  \"Saved\": \"Сохранено\",\n  \"Emoji\": \"Эмодзи\",\n  \"Font family name (e.g., JetBrains Mono NF)\": \"Шрифт (напр, JetBrains Mono NF)\",\n  \"Snip\": \"Скриншот\",\n  \"Font weight\": \"Насыщенность \",\n  \"More Bluetooth settings\": \"Доп. настройки Bluetooth\",\n  \"Recognize text\": \"Распознать текст\",\n  \"Pinned\": \"Закреплено\",\n  \"Unpin from Start\": \"Открепить из пуска\",\n  \"Adjust the color temperature\": \"Цветовая температура\",\n  \"Han chars\": \"Иероглифы\",\n  \"Show only when locked\": \"Отображать только при блокировке\",\n  \"Widget: Weather\": \"Виджет погоды\",\n  \"Right to left\": \"Справа налево\",\n  \"New desktop\": \"Новый рабочий стол\",\n  \"Local account\": \"Локальная уч. запись\",\n  \"Super key symbol\": \"Символ клавиши Super\",\n  \"Used for displaying numbers\": \"Для отображения чисел\",\n  \"Fonts\": \"Шрифты\",\n  \"Left to right\": \"Слева направо\",\n  \"Set FPS limit\": \"Лимит FPS\",\n  \"Draggable\": \"Перетаскиваемый\",\n  \"Turn on from sunset to sunrise\": \"От заката до рассвета\",\n  \"Do you want to allow this app to make changes to your device?\": \"Разрешить приложению изменять устройство?\",\n  \"Balance brightness based on content\": \"Баланс яркости по содержимому\",\n  \"Font width and roundness settings are only available for some fonts like Google Sans Flex\": \"Ширина и скруглённость - только для некоторых шрифтов (напр, Google Sans Flex)\",\n  \"Record region\": \"Запись обл.\",\n  \"You can also manually edit cheatsheet.superKey\": \"Также вы можете вручную отредактировать cheatsheet.superKey\",\n  \"Sign out\": \"Выйти\",\n  \"Overlay: Crosshair\": \"Оверлей: прицел\",\n  \"Shut down\": \"Выключить\",\n  \"Show this window on all desktops\": \"На всех рабочих столах\",\n  \"Quick markup (Ctrl+E)\": \"Быстрая разметка (Ctrl+E)\",\n  \"Sound input\": \"Звуковой вход\",\n  \"Manage accounts\": \"Упр. учётными записями\",\n  \"+%1 notifications\": \"+%1 уведомлений\",\n  \"Font family\": \"Семейство шрифта\",\n  \"RAM\": \"ОЗУ\",\n  \"Commands\": \"Команды\",\n  \"Title font\": \"Шрифт заголовка\",\n  \"Most busy\": \"Более загруж.\",\n  \"Press Super+G to open the overlay and pin the crosshair\": \"Super+G - открыть оверлей и закрепите прицел\",\n  \"Search for apps\": \"Поиск приложений\",\n  \"See fewer\": \"Показать меньше\",\n  \"Utilities & Tools\": \"Утилиты и инструменты\",\n  \"Speakers (%1): %2\": \"Динамики (%1): %2\",\n  \"Manage my account\": \"Упр. аккаунтом\",\n  \"Use symbols for function keys\": \"Спец. символы для особых клавиш\",\n  \"Aligns the date and quote to left, center or right depending on its position on the screen.\": \"Выравнивание по позиции на экране.\",\n  \"Darken screen\": \"Затемнить экран\",\n  \"Move to front\": \"На перед. план\",\n  \"Overlay: Floating Image\": \"Оверлей: изображение\",\n  \"OutlineVpn\": \"OutlineVpn\",\n  \"AmneziaVPN\": \"AmneziaVPN\",\n  \"Screenshot Path (leave empty to just copy)\": \"Путь снимков (пусто = только копировать)\",\n  \"Open recordings folder\": \"Открыть папку записей\",\n  \"Write something here...\\nUse '-' to create copyable bullet points, like this:\\n\\nSheep fricker\\n- 4x Slab\\n- 1x Boat\\n- 4x Redstone Dust\\n- 1x Sticky Piston\\n- 1x End Rod\\n- 4x Redstone Repeater\\n- 1x Redstone Torch\\n- 1x Sheep\": \"Пишите здесь...\\nИспользуйте '-' для пунктов:\\n\\nПример\\n- 4x Плиты\\n- 1x Лодка\\n- 4x Редстоуна\\n- 1x Липкий поршень\\n- 1x Стержень энда\\n- 4x Повторитель\\n- 1x Факел\\n- 1x Овца\",\n  \"Keybind font size\": \"Размер шрифта кейбиндов\",\n  \"All\": \"Все\",\n  \"Used for general UI text\": \"Для общего текста UI\",\n  \"Media\": \"Медиа\",\n  \"Enable update checks\": \"Вкл. проверку обновлений\",\n  \"Widget: Clock\": \"Виджет: часы\",\n  \"Clock style (locked)\": \"Стиль часов (на заблок. экране)\",\n  \"Replace 󱕐   for \\\"Scroll ↓\\\", 󱕑   \\\"Scroll ↑\\\", L󰍽   \\\"LMB\\\", R󰍽   \\\"RMB\\\", 󱕒   \\\"Scroll ↑/↓\\\" and ⇞/⇟ for \\\"Page_↑/↓\\\"\": \"Заменить 󱕐 на «Scroll ↓», 󱕑 на «Scroll ↑», L󰍽 на «LMB», R󰍽 на «RMB», 󱕒 на «Scroll ↑/↓» и ⇞/⇟ на «Page_↑/↓»\",\n  \"Monospace font\": \"Моноширинный шрифт\",\n  \"Saving...\": \"Сохранение...\",\n  \"Best match\": \"Лучшее совпадение\",\n  \"Use macOS-like symbols for mods keys\": \"Символы в стиле macOS\",\n  \"There might be a download in progress. Check your Downloads folder.\": \"Возможно, идёт загрузка. Проверьте «Загрузки».\",\n  \"Web\": \"Веб\",\n  \"Reading font\": \"Шрифт для чтения\",\n  \"Enter a valid number\": \"Введите корректное число\",\n  \"Image search\": \"Поиск изображений\",\n  \"Use symbols for mouse\": \"Символы для мыши\",\n  \"Font family name (e.g., Readex Pro)\": \"Шрифт (напр, Readex Pro)\",\n  \"Font family name (e.g., Space Grotesk)\": \"Шрифт (напр, Space Grotesk)\",\n  \"Wi-Fi\": \"Wi-Fi\",\n  \"Clear all\": \"Очистить всё\",\n  \"More comfortable viewing at night\": \"Комфортный просмотр ночью\",\n  \"Close all windows\": \"Закрыть все окна\",\n  \"Bottom-up\": \"Снизу вверх\",\n  \"%1 mins\": \"%1 мин\",\n  \"Font width\": \"Ширина шрифта\",\n  \"Get the latest features and security improvements with\\nthe newest feature update.\\n\\n%1 packages\": \"Новые функции и улучшения безоп.\\n\\n%1 пакетов\",\n  \"Use adaptive alignment\": \"Адаптивное выравн.\",\n  \"e.g. 󱊫 for F1, 󱊶  for F12\": \"напр. 󱊫 для F1, 󱊶 для F12\",\n  \"Focusing\": \"Фокусировка\",\n  \"Search with Google Lens\": \"Поиск через Google Lens\",\n  \"Expressive font\": \"Выразительный шрифт\",\n  \"VPN\": \"VPN\",\n  \"Text extractor\": \"Извлечение текста\",\n  \"Least busy\": \"Менее загруж.\",\n  \"Command-line-invoked Action\": \"Действие из ком. строки\",\n  \"Show date\": \"Показывать дату\",\n  \"Window\": \"Окно\",\n  \"Network\": \"Сеть\",\n  \"Input device\": \"Устр. ввода\",\n  \"Polkit\": \"Polkit\",\n  \"Font size\": \"Размер шрифта\",\n  \"Swap\": \"Своп\",\n  \"Starting...\": \"Запуск...\",\n  \"Close window\": \"Закрыть окно\",\n  \"Sound effects\": \"Звуковые эффекты\",\n  \"Last refresh: %1\": \"Обновлено: %1\",\n  \"We\": \"Ср/*keep*/\",\n  \"Mo\": \"Пн/*keep*/\",\n  \"Su\": \"Вс/*keep*/\",\n  \"Th\": \"Чт/*keep*/\",\n  \"Tu\": \"Вт/*keep*/\",\n  \"Sa\": \"Сб/*keep*/\",\n  \"Fr\": \"Пт/*keep*/\",\n  \"Font family name\": \"Назв. семейства\",\n  \"Pin\": \"Закреп\",\n  \"Unpin\": \"Откреп\"\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tools/README.md",
    "content": "# Translation Management Tool Suite\n\nThis suite is used to manage project translation files, automatically extract translatable texts, compare differences between language files, and provide maintenance functions.\n\n## Tool Components\n\n### 1. `translation-manager.py` - Main Translation Manager\n- Extract translatable texts\n- Compare and update translation files\n- Interactive addition/removal of translation keys\n\n### 2. `translation-cleaner.py` - Translation File Maintenance Tool\n- Clean unused translation keys\n- Synchronize key structure across different language files\n\n### 3. `manage-translations.sh` - Convenient Wrapper Script\n- Provides a unified command-line interface\n- Displays translation status\n- Simplifies common operations\n\n## Quick Start\n\n### Using the Wrapper Script (Recommended)\n\n```bash\n# Enter the tools directory\ncd .config/quickshell/translations/tools\n\n# Show help\n./manage-translations.sh --help\n\n# Show current translation status\n./manage-translations.sh status\n\n# Extract translatable texts\n./manage-translations.sh extract\n\n# Update all translation files\n./manage-translations.sh update\n\n# Update a specific language\n./manage-translations.sh update -l zh_CN\n\n# Clean unused keys\n./manage-translations.sh clean\n\n# Synchronize keys across all language files\n./manage-translations.sh sync\n```\n\nOr run from the project root:\n```bash\n# Run from the project root\n.config/quickshell/translations/tools/manage-translations.sh status\n.config/quickshell/translations/tools/manage-translations.sh update\n```\n\n## Detailed Usage\n\n### Translation Manager (`translation-manager.py`)\n\nBasic usage:\n```bash\n# Process all languages\n./translation-manager.py\n\n# Specify a particular language\n./translation-manager.py --language zh_CN\n\n# Extract translatable texts only\n./translation-manager.py --extract-only\n\n# Show extracted texts\n./translation-manager.py --extract-only --show-temp\n```\n\nParameter description:\n- `--translations-dir`, `-t`: Translation files directory (default: `.config/quickshell/translations`)\n- `--source-dir`, `-s`: Source code directory (default: `.config/quickshell`)\n- `--language`, `-l`: Specify the language code to process\n- `--extract-only`, `-e`: Only extract translatable texts\n- `--show-temp`: Show the content of the temporary extraction file\n\n### Translation Cleaner (`translation-cleaner.py`)\n\n```bash\n# Clean unused translation keys\n./translation-cleaner.py --clean\n\n# Synchronize translation keys (using en_US as the base)\n./translation-cleaner.py --sync\n\n# Specify a different source language for syncing\n./translation-cleaner.py --sync --source-lang zh_CN\n\n# Clean without creating backups\n./translation-cleaner.py --clean --no-backup\n```\n\n## Workflow\n\n### Regular Translation Update Workflow\n\n1. **Check status**:\n   ```bash\n   ./manage-translations.sh status\n   ```\n\n2. **Update translations**:\n   ```bash\n   ./manage-translations.sh update\n   ```\n\n3. **Clean unused keys** (optional):\n   ```bash\n   ./manage-translations.sh clean\n   ```\n\n### Adding a New Language\n\n1. **Create a new language file**:\n   ```bash\n   ./manage-translations.sh update -l new_lang\n   ```\n\n2. **Synchronize key structure**:\n   ```bash\n   ./manage-translations.sh sync\n   ```\n\n### Cleanup After Large Refactoring\n\n1. **Backup translation files**:\n   ```bash\n   cp -r .config/quickshell/translations .config/quickshell/translations.backup\n   ```\n\n2. **Clean unused keys**:\n   ```bash\n   ./manage-translations.sh clean\n   ```\n\n3. **Synchronize all languages**:\n   ```bash\n   ./manage-translations.sh sync\n   ```\n\n## Supported Translatable Text Formats\n\nThe tool recognizes the following formats for translatable texts:\n\n```qml\n// Basic format\nTranslation.tr(\"Hello, world!\")\nTranslation.tr('Hello, world!')\nTranslation.tr(`Hello, world!`)\n\n// With line breaks\nTranslation.tr(\"Line 1\\nLine 2\")\n\n// With escape characters\nTranslation.tr(\"Say \\\"Hello\\\"\")\n\n// With parameter placeholders\nTranslation.tr(\"Hello, %1!\").arg(name)\n```\n\n## Example Output\n\n### Status Display\n```\n$ ./manage-translations.sh status\nAnalyzing translation status...\n=== Current Project Status ===\n166 translatable texts extracted\n\n=== Translation File Status ===\n  en_US: 470 keys\n  zh_CN: 470 keys\n```\n\n### Update Translations\n```\n$ ./manage-translations.sh update -l zh_CN\nUpdating translation files...\n==================================================\nProcessing language: zh_CN\n==================================================\nAnalysis result:\n  Missing keys: 5\n  Extra keys: 20\n\nFound 5 missing translation keys:\n1. \"New feature text\"\n2. \"Another new text\"\n...\n\nAdd these 5 missing keys? (y/n): y\n5 keys added\n\nFound 20 extra translation keys:\n1. \"Removed old text\" -> \"已删除的旧文本\"\n...\n\nDelete these 20 extra keys? (y/n): y\n20 keys deleted\n\nTranslation file saved\n```\n\n### Clean Unused Keys\n```\n$ ./manage-translations.sh clean\nCleaning unused translation keys...\nProcessing language: zh_CN\nFound 50 unused keys:\n  1. \"old_unused_text\"\n  2. \"deprecated_message\"\n  ...\n\nDelete these 50 unused keys? (y/n): y\n50 keys deleted\nOriginal key count: 470, after cleaning: 420\n```\n\n## Advanced Features\n\n### Custom Directory Structure\n\n```bash\n# Use custom directories\n./translation-manager.py \\\n  --translations-dir /path/to/translations \\\n  --source-dir /path/to/source\n```\n\n### Ignore Mark Feature\n\nFor dynamic resources or special texts that should not be automatically cleaned, you can add `/*keep*/` at the end of the translation value. The tool will automatically ignore these keys and will not delete them during cleaning or syncing.\n\nExample:\n```json\n{\n  \"dynamic_key\": \"Some dynamic value /*keep*/\"\n}\n```\n\n## Notes\n\n1. **Backup is important**: The tool automatically creates backups before cleaning, but it is recommended to manually back up important files\n\n2. **Text extraction limitations**:\n   - ~~Only supports static strings, not dynamically constructed strings~~\n   - Dynamic resources (such as variable concatenation or runtime-generated text) cannot be automatically extracted. You need to manually add them to the translation file and use the `/*keep*/` mark for ignore management.\n   - Must use the `Translation.tr()` format\n\n3. **File encoding**: All files must use UTF-8 encoding\n\n4. **Key naming conventions**: It is recommended to use English for key names and avoid special characters\n\n## Troubleshooting\n\n### Common Issues\n\n**Q: Text does not appear after adding Translation.tr?**\nA: You need to import the translation feature in your QML file using `import \"root:/\"`, otherwise the translation text will not be displayed correctly.\n\n**Q: The number of extracted texts does not match expectations?**\nA: Check whether all translatable texts use the `Translation.tr()` format and ensure there are no dynamically constructed strings.\n\n**Q: Some translations are missing after syncing?**\nA: Check whether the source language file contains all necessary keys, and consider using a different source language for syncing.\n\n**Q: The cleaning operation deleted needed keys?**\nA: Restore from the automatically created backup file and check whether `Translation.tr()` is used correctly in the source code.\n\n### Restore Backup\n\n```bash\n# Restore a single file\ncp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json\n\n# Restore all files\ncp .config/quickshell/translations.backup/* .config/quickshell/translations/\n```\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tools/guide/translation-tools-guide-zh_CN.md",
    "content": "# 翻译管理工具套件\n\n这套工具用于管理项目的翻译文件，自动提取可翻译文本，比较不同语言文件之间的差异，并提供维护功能。\n\n## 工具组成\n\n### 1. `translation-manager.py` - 主要翻译管理器\n- 提取可翻译文本\n- 比较和更新翻译文件\n- 交互式添加/删除翻译键\n\n### 2. `translation-cleaner.py` - 翻译文件维护工具\n- 清理不再使用的翻译键\n- 同步不同语言文件的键结构\n\n### 3. `manage-translations.sh` - 便捷包装脚本\n- 提供统一的命令行界面\n- 显示翻译状态\n- 简化常用操作\n\n## 快速开始\n\n### 使用便捷脚本（推荐）\n\n```bash\n# 进入工具目录\ncd .config/quickshell/translations/tools\n\n# 查看帮助\n./manage-translations.sh --help\n\n# 显示当前翻译状态\n./manage-translations.sh status\n\n# 提取可翻译文本\n./manage-translations.sh extract\n\n# 更新所有翻译文件\n./manage-translations.sh update\n\n# 更新特定语言\n./manage-translations.sh update -l zh_CN\n\n# 清理不再使用的键\n./manage-translations.sh clean\n\n# 同步所有语言文件的键\n./manage-translations.sh sync\n```\n\n或者从项目根目录运行：\n```bash\n# 从项目根目录运行\n.config/quickshell/translations/tools/manage-translations.sh status\n.config/quickshell/translations/tools/manage-translations.sh update\n```\n\n## 详细使用说明\n\n### 翻译管理器 (`translation-manager.py`)\n\n基本用法：\n```bash\n# 处理所有语言\n./translation-manager.py\n\n# 指定特定语言\n./translation-manager.py --language zh_CN\n\n# 仅提取可翻译文本\n./translation-manager.py --extract-only\n\n# 显示提取的文本\n./translation-manager.py --extract-only --show-temp\n```\n\n参数说明：\n- `--translations-dir`, `-t`: 翻译文件目录（默认：`.config/quickshell/translations`）\n- `--source-dir`, `-s`: 源代码目录（默认：`.config/quickshell`）\n- `--language`, `-l`: 指定要处理的语言代码\n- `--extract-only`, `-e`: 仅提取可翻译文本\n- `--show-temp`: 显示临时提取文件的内容\n\n### 翻译清理器 (`translation-cleaner.py`)\n\n```bash\n# 清理不再使用的翻译键\n./translation-cleaner.py --clean\n\n# 同步翻译键（以 en_US 为基准）\n./translation-cleaner.py --sync\n\n# 指定不同的源语言进行同步\n./translation-cleaner.py --sync --source-lang zh_CN\n\n# 清理时不创建备份\n./translation-cleaner.py --clean --no-backup\n```\n\n## 工作流程\n\n### 日常翻译更新流程\n\n1. **检查状态**：\n   ```bash\n   ./manage-translations.sh status\n   ```\n\n2. **更新翻译**：\n   ```bash\n   ./manage-translations.sh update\n   ```\n\n3. **清理无用键**（可选）：\n   ```bash\n   ./manage-translations.sh clean\n   ```\n\n### 新增语言流程\n\n1. **创建新语言文件**：\n   ```bash\n   ./manage-translations.sh update -l new_lang\n   ```\n\n2. **同步键结构**：\n   ```bash\n   ./manage-translations.sh sync\n   ```\n\n### 大规模重构后的清理流程\n\n1. **备份翻译文件**：\n   ```bash\n   cp -r .config/quickshell/translations .config/quickshell/translations.backup\n   ```\n\n2. **清理无用键**：\n   ```bash\n   ./manage-translations.sh clean\n   ```\n\n3. **同步所有语言**：\n   ```bash\n   ./manage-translations.sh sync\n   ```\n\n## 支持的翻译文本格式\n\n工具可以识别以下格式的可翻译文本：\n\n```qml\n// 基本格式\nTranslation.tr(\"Hello, world!\")\nTranslation.tr('Hello, world!')\nTranslation.tr(`Hello, world!`)\n\n// 带换行符\nTranslation.tr(\"Line 1\\nLine 2\")\n\n// 带转义字符\nTranslation.tr(\"Say \\\"Hello\\\"\")\n\n// 带参数占位符\nTranslation.tr(\"Hello, %1!\").arg(name)\n```\n\n## 示例输出\n\n### 状态显示\n```\n$ ./manage-translations.sh status\n正在分析翻译状态...\n=== 当前项目状态 ===\n提取到 166 个可翻译文本\n\n=== 翻译文件状态 ===\n  en_US: 470 个键\n  zh_CN: 470 个键\n```\n\n### 更新翻译\n```\n$ ./manage-translations.sh update -l zh_CN\n更新翻译文件...\n==================================================\n处理语言: zh_CN\n==================================================\n分析结果:\n  缺少的键: 5\n  多余的键: 20\n\n发现 5 个缺少的翻译键：\n1. \"New feature text\"\n2. \"Another new text\"\n...\n\n是否添加这 5 个缺少的键？ (y/n): y\n已添加 5 个键\n\n发现 20 个多余的翻译键：\n1. \"Removed old text\" -> \"已删除的旧文本\"\n...\n\n是否删除这 20 个多余的键？ (y/n): y\n已删除 20 个键\n\n已保存翻译文件\n```\n\n### 清理无用键\n```\n$ ./manage-translations.sh clean\n清理不再使用的翻译键...\n处理语言: zh_CN\n发现 50 个不再使用的键:\n  1. \"old_unused_text\"\n  2. \"deprecated_message\"\n  ...\n\n是否删除这 50 个不再使用的键？ (y/n): y\n已删除 50 个键\n原始键数: 470, 清理后: 420\n```\n\n## 高级功能\n\n### 自定义目录结构\n\n```bash\n# 使用自定义目录\n./translation-manager.py \\\n  --translations-dir /path/to/translations \\\n  --source-dir /path/to/source\n```\n\n\n## 注意事项\n\n1. **备份重要**：在执行清理操作前，工具会自动创建备份，但建议手动备份重要文件\n\n2. **文本提取限制**：\n   - ~~只支持静态字符串，不支持动态构建的字符串~~\n   - 动态资源（如变量拼接、运行时生成的文本）无法自动提取，需要在翻译文件中手动添加，并使用 `/*keep*/` 标记进行忽略管理。\n   - 必须使用 `Translation.tr()` 格式\n### 忽略标记功能\n\n对于动态资源或特殊文本，如果不希望被自动清理，可在翻译值末尾添加 `/*keep*/`，工具会自动忽略这些键，不会在清理和同步时删除。\n\n示例：\n```json\n{\n  \"dynamic_key\": \"Some dynamic value /*keep*/\"\n}\n```\n\n3. **文件编码**：所有文件必须使用 UTF-8 编码\n\n4. **键名规范**：建议使用英文作为键名，避免使用特殊字符\n\n## 故障排除\n\n### 常见问题\n\n\n**Q: 添加了 Translation.tr 后文字不显示？**\nA: 需要在 QML 文件中使用 `import \"root:/\"` 导入翻译功能，否则无法正常显示翻译文本。\n\n**Q: 提取的文本数量与预期不符？**\nA: 检查是否所有可翻译文本都使用了 `Translation.tr()` 格式，确保没有动态构建的字符串。\n\n**Q: 同步后某些翻译丢失？**\nA: 检查源语言文件是否包含所有必要的键，考虑使用不同的源语言进行同步。\n\n**Q: 清理操作删除了需要的键？**\nA: 从自动创建的备份文件中恢复，检查源代码中是否正确使用了 `Translation.tr()`。\n\n### 恢复备份\n\n```bash\n# 恢复单个文件\ncp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json\n\n# 恢复所有文件\ncp .config/quickshell/translations.backup/* .config/quickshell/translations/\n```\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tools/guide/translation-tools-guide.md",
    "content": "# Translation Management Tool Suite\n\nThis suite is used to manage project translation files, automatically extract translatable texts, compare differences between language files, and provide maintenance functions.\n\n## Tool Components\n\n### 1. `translation-manager.py` - Main Translation Manager\n- Extract translatable texts\n- Compare and update translation files\n- Interactive addition/removal of translation keys\n\n### 2. `translation-cleaner.py` - Translation File Maintenance Tool\n- Clean unused translation keys\n- Synchronize key structure across different language files\n\n### 3. `manage-translations.sh` - Convenient Wrapper Script\n- Provides a unified command-line interface\n- Displays translation status\n- Simplifies common operations\n\n## Quick Start\n\n### Using the Wrapper Script (Recommended)\n\n```bash\n# Enter the tools directory\ncd .config/quickshell/translations/tools\n\n# Show help\n./manage-translations.sh --help\n\n# Show current translation status\n./manage-translations.sh status\n\n# Extract translatable texts\n./manage-translations.sh extract\n\n# Update all translation files\n./manage-translations.sh update\n\n# Update a specific language\n./manage-translations.sh update -l zh_CN\n\n# Clean unused keys\n./manage-translations.sh clean\n\n# Synchronize keys across all language files\n./manage-translations.sh sync\n```\n\nOr run from the project root:\n```bash\n# Run from the project root\n.config/quickshell/translations/tools/manage-translations.sh status\n.config/quickshell/translations/tools/manage-translations.sh update\n```\n\n## Detailed Usage\n\n### Translation Manager (`translation-manager.py`)\n\nBasic usage:\n```bash\n# Process all languages\n./translation-manager.py\n\n# Specify a particular language\n./translation-manager.py --language zh_CN\n\n# Extract translatable texts only\n./translation-manager.py --extract-only\n\n# Show extracted texts\n./translation-manager.py --extract-only --show-temp\n```\n\nParameter description:\n- `--translations-dir`, `-t`: Translation files directory (default: `.config/quickshell/translations`)\n- `--source-dir`, `-s`: Source code directory (default: `.config/quickshell`)\n- `--language`, `-l`: Specify the language code to process\n- `--extract-only`, `-e`: Only extract translatable texts\n- `--show-temp`: Show the content of the temporary extraction file\n\n### Translation Cleaner (`translation-cleaner.py`)\n\n```bash\n# Clean unused translation keys\n./translation-cleaner.py --clean\n\n# Synchronize translation keys (using en_US as the base)\n./translation-cleaner.py --sync\n\n# Specify a different source language for syncing\n./translation-cleaner.py --sync --source-lang zh_CN\n\n# Clean without creating backups\n./translation-cleaner.py --clean --no-backup\n```\n\n## Workflow\n\n### Regular Translation Update Workflow\n\n1. **Check status**:\n   ```bash\n   ./manage-translations.sh status\n   ```\n\n2. **Update translations**:\n   ```bash\n   ./manage-translations.sh update\n   ```\n\n3. **Clean unused keys** (optional):\n   ```bash\n   ./manage-translations.sh clean\n   ```\n\n### Adding a New Language\n\n1. **Create a new language file**:\n   ```bash\n   ./manage-translations.sh update -l new_lang\n   ```\n\n2. **Synchronize key structure**:\n   ```bash\n   ./manage-translations.sh sync\n   ```\n\n### Cleanup After Large Refactoring\n\n1. **Backup translation files**:\n   ```bash\n   cp -r .config/quickshell/translations .config/quickshell/translations.backup\n   ```\n\n2. **Clean unused keys**:\n   ```bash\n   ./manage-translations.sh clean\n   ```\n\n3. **Synchronize all languages**:\n   ```bash\n   ./manage-translations.sh sync\n   ```\n\n## Supported Translatable Text Formats\n\nThe tool recognizes the following formats for translatable texts:\n\n```qml\n// Basic format\nTranslation.tr(\"Hello, world!\")\nTranslation.tr('Hello, world!')\nTranslation.tr(`Hello, world!`)\n\n// With line breaks\nTranslation.tr(\"Line 1\\nLine 2\")\n\n// With escape characters\nTranslation.tr(\"Say \\\"Hello\\\"\")\n\n// With parameter placeholders\nTranslation.tr(\"Hello, %1!\").arg(name)\n```\n\n## Example Output\n\n### Status Display\n```\n$ ./manage-translations.sh status\nAnalyzing translation status...\n=== Current Project Status ===\n166 translatable texts extracted\n\n=== Translation File Status ===\n  en_US: 470 keys\n  zh_CN: 470 keys\n```\n\n### Update Translations\n```\n$ ./manage-translations.sh update -l zh_CN\nUpdating translation files...\n==================================================\nProcessing language: zh_CN\n==================================================\nAnalysis result:\n  Missing keys: 5\n  Extra keys: 20\n\nFound 5 missing translation keys:\n1. \"New feature text\"\n2. \"Another new text\"\n...\n\nAdd these 5 missing keys? (y/n): y\n5 keys added\n\nFound 20 extra translation keys:\n1. \"Removed old text\" -> \"已删除的旧文本\"\n...\n\nDelete these 20 extra keys? (y/n): y\n20 keys deleted\n\nTranslation file saved\n```\n\n### Clean Unused Keys\n```\n$ ./manage-translations.sh clean\nCleaning unused translation keys...\nProcessing language: zh_CN\nFound 50 unused keys:\n  1. \"old_unused_text\"\n  2. \"deprecated_message\"\n  ...\n\nDelete these 50 unused keys? (y/n): y\n50 keys deleted\nOriginal key count: 470, after cleaning: 420\n```\n\n## Advanced Features\n\n### Custom Directory Structure\n\n```bash\n# Use custom directories\n./translation-manager.py \\\n  --translations-dir /path/to/translations \\\n  --source-dir /path/to/source\n```\n\n### Ignore Mark Feature\n\nFor dynamic resources or special texts that should not be automatically cleaned, you can add `/*keep*/` at the end of the translation value. The tool will automatically ignore these keys and will not delete them during cleaning or syncing.\n\nExample:\n```json\n{\n  \"dynamic_key\": \"Some dynamic value /*keep*/\"\n}\n```\n\n## Notes\n\n1. **Backup is important**: The tool automatically creates backups before cleaning, but it is recommended to manually back up important files\n\n2. **Text extraction limitations**:\n   - ~~Only supports static strings, not dynamically constructed strings~~\n   - Dynamic resources (such as variable concatenation or runtime-generated text) cannot be automatically extracted. You need to manually add them to the translation file and use the `/*keep*/` mark for ignore management.\n   - Must use the `Translation.tr()` format\n\n3. **File encoding**: All files must use UTF-8 encoding\n\n4. **Key naming conventions**: It is recommended to use English for key names and avoid special characters\n\n## Troubleshooting\n\n### Common Issues\n\n**Q: Text does not appear after adding Translation.tr?**\nA: You need to import the translation feature in your QML file using `import \"root:/\"`, otherwise the translation text will not be displayed correctly.\n\n**Q: The number of extracted texts does not match expectations?**\nA: Check whether all translatable texts use the `Translation.tr()` format and ensure there are no dynamically constructed strings.\n\n**Q: Some translations are missing after syncing?**\nA: Check whether the source language file contains all necessary keys, and consider using a different source language for syncing.\n\n**Q: The cleaning operation deleted needed keys?**\nA: Restore from the automatically created backup file and check whether `Translation.tr()` is used correctly in the source code.\n\n### Restore Backup\n\n```bash\n# Restore a single file\ncp .config/quickshell/translations/zh_CN.json.backup .config/quickshell/translations/zh_CN.json\n\n# Restore all files\ncp .config/quickshell/translations.backup/* .config/quickshell/translations/\n```\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tools/manage-translations.sh",
    "content": "#!/bin/bash\n# Translation management script - convenient wrapper\n\nset -e\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nTRANSLATIONS_DIR=\"$(dirname \"$SCRIPT_DIR\")\"\nSOURCE_DIR=\"$(dirname \"$(dirname \"$TRANSLATIONS_DIR\")\")\"\n\nshow_help() {\n    echo \"Translation Management Tool - Convenient Wrapper\"\n    echo \"\"\n    echo \"Usage: $0 [options] <command>\"\n    echo \"\"\n    echo \"Commands:\"\n    echo \"  extract      Extract translatable texts to temporary file\"\n    echo \"  update       Update translation files (add missing/remove extra keys)\"\n    echo \"  clean        Clean unused translation keys\"\n    echo \"  sync         Sync keys across all language files\"\n    echo \"  status       Show translation status\"\n    echo \"\"\n    echo \"Options:\"\n    echo \"  -l, --lang LANG     Specify language (e.g.: zh_CN)\"\n    echo \"  -t, --trans-dir DIR Translation files directory (default: $TRANSLATIONS_DIR)\"\n    echo \"  -s, --source-dir DIR Source code directory (default: $SOURCE_DIR)\"\n    echo \"  -y, --yes           Skip all confirmation prompts (auto-confirm)\"\n    echo \"  -h, --help          Show this help message\"\n    echo \"\"\n    echo \"Examples:\"\n    echo \"  $0 extract                    # Extract translatable texts\"\n    echo \"  $0 update -l zh_CN           # Update Chinese translations\"\n    echo \"  $0 update                    # Update all translations\"\n    echo \"  $0 clean                     # Clean unused keys\"\n    echo \"  $0 sync                      # Sync keys across all languages\"\n    echo \"  $0 status                    # Show translation status\"\n}\n\nshow_status() {\n    echo \"Analyzing translation status...\"\n    \n    # Extract current text count\n    echo \"=== Current Project Status ===\"\n    python3 \"$SCRIPT_DIR/translation-manager.py\" \\\n        --translations-dir \"$TRANSLATIONS_DIR\" \\\n        --source-dir \"$SOURCE_DIR\" \\\n        --extract-only | grep \"Extracted\"\n    \n    echo \"\"\n    echo \"=== Translation File Status ===\"\n    \n    if [ -d \"$TRANSLATIONS_DIR\" ]; then\n        for file in \"$TRANSLATIONS_DIR\"/*.json; do\n            if [ -f \"$file\" ]; then\n                lang=$(basename \"$file\" .json)\n                count=$(jq 'length' \"$file\" 2>/dev/null || echo \"error\")\n                echo \"  $lang: $count keys\"\n            fi\n        done\n    else\n        echo \"  Translation directory does not exist: $TRANSLATIONS_DIR\"\n    fi\n}\n\n# Parse command line arguments\nLANG_CODE=\"\"\nCOMMAND=\"\"\nYES_FLAG=\"\"\n\nwhile [[ $# -gt 0 ]]; do\n    case $1 in\n        -l|--lang)\n            LANG_CODE=\"$2\"\n            shift 2\n            ;;\n        -t|--trans-dir)\n            TRANSLATIONS_DIR=\"$2\"\n            shift 2\n            ;;\n        -s|--source-dir)\n            SOURCE_DIR=\"$2\"\n            shift 2\n            ;;\n        -y|--yes)\n            YES_FLAG=\"-y\"\n            shift\n            ;;\n        -h|--help)\n            show_help\n            exit 0\n            ;;\n        extract|update|clean|sync|status)\n            if [ -n \"$COMMAND\" ]; then\n                echo \"Error: Only one command can be specified\"\n                exit 1\n            fi\n            COMMAND=\"$1\"\n            shift\n            ;;\n        *)\n            echo \"Unknown option: $1\"\n            show_help\n            exit 1\n            ;;\n    esac\ndone\n\nif [ -z \"$COMMAND\" ]; then\n    echo \"Error: A command must be specified\"\n    show_help\n    exit 1\nfi\n\n# Check dependencies\nif ! command -v python3 >/dev/null 2>&1; then\n    echo \"Error: python3 is required\"\n    exit 1\nfi\n\nif [ \"$COMMAND\" = \"status\" ] && ! command -v jq >/dev/null 2>&1; then\n    echo \"Warning: jq is not installed, status display may be incomplete\"\nfi\n\n# Build base arguments\nBASE_ARGS=\"--translations-dir $TRANSLATIONS_DIR --source-dir $SOURCE_DIR\"\n\ncase $COMMAND in\n    extract)\n        echo \"Extracting translatable texts...\"\n        python3 \"$SCRIPT_DIR/translation-manager.py\" $BASE_ARGS $YES_FLAG --extract-only --show-temp\n        ;;\n    update)\n        echo \"Updating translation files...\"\n        if [ -n \"$LANG_CODE\" ]; then\n            python3 \"$SCRIPT_DIR/translation-manager.py\" $BASE_ARGS $YES_FLAG --language \"$LANG_CODE\"\n        else\n            python3 \"$SCRIPT_DIR/translation-manager.py\" $BASE_ARGS $YES_FLAG\n        fi\n        ;;\n    clean)\n        echo \"Cleaning unused translation keys...\"\n        python3 \"$SCRIPT_DIR/translation-cleaner.py\" $BASE_ARGS $YES_FLAG --clean\n        ;;\n    sync)\n        echo \"Syncing translation keys...\"\n        python3 \"$SCRIPT_DIR/translation-cleaner.py\" $BASE_ARGS $YES_FLAG --sync\n        ;;\n    status)\n        show_status\n        ;;\n    *)\n        echo \"Unknown command: $COMMAND\"\n        show_help\n        exit 1\n        ;;\nesac\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tools/translation-cleaner.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nTranslation File Maintenance Helper\nUsed to clean and organize translation files, removing unused keys\n\"\"\"\n\nimport os\nimport sys\nimport json\nimport argparse\nimport importlib.util\nfrom pathlib import Path\nfrom typing import Dict, Set, List\n\n# Import from the same directory using importlib\ncurrent_dir = os.path.dirname(os.path.abspath(__file__))\nmanager_path = os.path.join(current_dir, 'translation-manager.py')\nspec = importlib.util.spec_from_file_location(\"translation_manager\", manager_path)\ntranslation_manager = importlib.util.module_from_spec(spec)\nspec.loader.exec_module(translation_manager)\nTranslationManager = translation_manager.TranslationManager\n\ndef clean_translation_files(translations_dir: str, source_dir: str, backup: bool = True, yes_mode: bool = False):\n    \"\"\"Clean translation files by removing unused keys\"\"\"\n    print(\"Starting translation file cleanup...\")\n    \n    # Create manager\n    manager = TranslationManager(translations_dir, source_dir)\n    \n    # Extract currently used texts\n    print(\"Extracting currently used translatable texts...\")\n    current_texts = manager.extract_translatable_texts()\n    print(f\"Extracted {len(current_texts)} currently used texts\")\n    \n    # Get all language files\n    languages = manager.get_available_languages()\n    if not languages:\n        print(\"No translation files found\")\n        return\n    \n    print(f\"Found language files: {', '.join(languages)}\")\n    \n    total_removed = 0\n    \n    for lang in languages:\n        print(f\"\\nProcessing language: {lang}\")\n        \n        # Load translation file\n        translations = manager.load_translation_file(lang)\n        original_count = len(translations)\n        \n        # Find unused keys, skip those whose value ends with /*keep*/\n        unused_keys = set()\n        for k in translations.keys():\n            v = translations[k]\n            if k not in current_texts:\n                if isinstance(v, str) and v.strip().endswith('/*keep*/'):\n                    continue\n                unused_keys.add(k)\n\n        if unused_keys:\n            print(f\"Found {len(unused_keys)} unused keys:\")\n            for i, key in enumerate(sorted(unused_keys)[:10], 1):  # Only show first 10\n                print(f\"  {i}. \\\"{key[:50]}{'...' if len(key) > 50 else ''}\\\"\")\n            if len(unused_keys) > 10:\n                print(f\"  ... and {len(unused_keys) - 10} more keys\")\n\n            if yes_mode:\n                response = 'y'\n                print(f\"Delete these {len(unused_keys)} unused keys? (auto-confirmed by --yes)\")\n            else:\n                response = input(f\"Delete these {len(unused_keys)} unused keys? (y/n): \")\n            if response.lower().strip() in ['y', 'yes']:\n                if backup:\n                    # Create backup only when user confirms deletion\n                    backup_file = Path(translations_dir) / f\"{lang}.json.bak\"\n                    with open(backup_file, 'w', encoding='utf-8') as f:\n                        json.dump(translations, f, ensure_ascii=False, indent=2)\n                    print(f\"Created backup: {backup_file}\")\n                # Delete unused keys\n                for key in unused_keys:\n                    del translations[key]\n\n                # Save cleaned file\n                manager.save_translation_file(lang, translations)\n                removed_count = len(unused_keys)\n                total_removed += removed_count\n                print(f\"Deleted {removed_count} keys\")\n            else:\n                print(\"Skipped deletion\")\n        else:\n            print(\"No unused keys found\")\n\n        new_count = len(translations)\n        print(f\"Original key count: {original_count}, after cleanup: {new_count}\")\n    \n    print(f\"\\nCleanup completed! Total deleted {total_removed} unused keys.\")\n\ndef sync_translations(translations_dir: str, source_lang: str = \"en_US\", target_langs: List[str] = None, yes_mode: bool = False):\n    \"\"\"Sync translation keys to ensure all language files have the same keys\"\"\"\n    print(f\"Starting translation key sync using {source_lang} as reference...\")\n    \n    translations_path = Path(translations_dir)\n    \n    # Load source language file\n    source_file = translations_path / f\"{source_lang}.json\"\n    if not source_file.exists():\n        print(f\"Error: Source language file does not exist: {source_file}\")\n        return\n    \n    with open(source_file, 'r', encoding='utf-8') as f:\n        source_translations = json.load(f)\n    \n    source_keys = set(source_translations.keys())\n    print(f\"Source language {source_lang} has {len(source_keys)} keys\")\n    \n    # Get target language list\n    if target_langs is None:\n        target_langs = []\n        for file_path in translations_path.glob(\"*.json\"):\n            lang_code = file_path.stem\n            if lang_code != source_lang:\n                target_langs.append(lang_code)\n    \n    if not target_langs:\n        print(\"No target language files found\")\n        return\n    \n    print(f\"Target languages: {', '.join(target_langs)}\")\n    \n    for target_lang in target_langs:\n        print(f\"\\nSyncing language: {target_lang}\")\n        \n        target_file = translations_path / f\"{target_lang}.json\"\n        if target_file.exists():\n            with open(target_file, 'r', encoding='utf-8') as f:\n                target_translations = json.load(f)\n        else:\n            target_translations = {}\n        \n        target_keys = set(target_translations.keys())\n        \n        # Find missing and extra keys\n        missing_keys = source_keys - target_keys\n        extra_keys = target_keys - source_keys\n        \n        print(f\"  Missing keys: {len(missing_keys)}\")\n        print(f\"  Extra keys: {len(extra_keys)}\")\n        \n        # Add missing keys\n        if missing_keys:\n            for key in missing_keys:\n                # Use source language value as placeholder by default\n                target_translations[key] = source_translations[key]\n            print(f\"  Added {len(missing_keys)} missing keys\")\n        \n        # Ask whether to delete extra keys\n        if extra_keys:\n            if yes_mode:\n                response = 'y'\n                print(f\"  Delete {len(extra_keys)} extra keys? (auto-confirmed by --yes)\")\n            else:\n                response = input(f\"  Delete {len(extra_keys)} extra keys? (y/n): \")\n            if response.lower().strip() in ['y', 'yes']:\n                for key in extra_keys:\n                    del target_translations[key]\n                print(f\"  Deleted {len(extra_keys)} extra keys\")\n        \n        # Save file (ensure UTF-8, fix for special chars)\n        with open(target_file, 'w', encoding='utf-8', newline='') as f:\n            json.dump(target_translations, f, ensure_ascii=False, indent=2)\n        print(f\"  Saved: {target_file}\")\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Translation File Maintenance Helper\")\n    parser.add_argument(\"--translations-dir\", \"-t\", \n                       default=\".config/quickshell/translations\",\n                       help=\"Translation files directory\")\n    parser.add_argument(\"--source-dir\", \"-s\", \n                       default=\".config/quickshell\",\n                       help=\"Source code directory\")\n    parser.add_argument(\"--clean\", \"-c\", action=\"store_true\",\n                       help=\"Clean unused translation keys\")\n    parser.add_argument(\"--sync\", action=\"store_true\",\n                       help=\"Sync translation keys\")\n    parser.add_argument(\"--source-lang\", default=\"en_US\",\n                       help=\"Source language for syncing (default: en_US)\")\n    parser.add_argument(\"--no-backup\", action=\"store_true\",\n                       help=\"Do not create backup files when cleaning\")\n    parser.add_argument(\"-y\", \"--yes\", action=\"store_true\",\n                       help=\"Skip all confirmation prompts (auto-confirm)\")\n    \n    args = parser.parse_args()\n    \n    # Convert to absolute paths\n    translations_dir = os.path.abspath(args.translations_dir)\n    source_dir = os.path.abspath(args.source_dir)\n    \n    if args.clean:\n        clean_translation_files(translations_dir, source_dir, backup=not args.no_backup, yes_mode=args.yes)\n    elif args.sync:\n        sync_translations(translations_dir, args.source_lang, yes_mode=args.yes)\n    else:\n        print(\"Please specify an operation:\")\n        print(\"  --clean: Clean unused translation keys\")\n        print(\"  --sync: Sync translation keys\")\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tools/translation-manager.py",
    "content": "#!/usr/bin/env python3\n# -*- coding: utf-8 -*-\n\"\"\"\nTranslation File Management Script\nUsed to update and extract translatable texts, manage JSON translation file key comparison\n\"\"\"\n\nimport os\nimport json\nimport re\nimport sys\nimport argparse\nfrom pathlib import Path\nfrom typing import Dict, Set, List, Tuple\nimport tempfile\nimport subprocess\n\nclass TranslationManager:\n    def __init__(self, translations_dir: str, source_dir: str, yes_mode: bool = False):\n        self.translations_dir = Path(translations_dir)\n        self.source_dir = Path(source_dir)\n        self.temp_extracted_file = None\n        self.yes_mode = yes_mode\n        \n        # Ensure translation directory exists\n        self.translations_dir.mkdir(parents=True, exist_ok=True)\n        \n    def extract_translatable_texts(self) -> Set[str]:\n        \"\"\"Extract translatable texts from source code\"\"\"\n        translatable_texts = set()\n        \n        # Search patterns: Translation.tr(\"text\") or Translation.tr('text')\n        # Improved regex that handles nested quotes correctly\n        patterns = [\n            r'Translation\\.tr\\s*\\(\\s*([\"\\'])(((?!\\1)[^\\\\]|\\\\.)*)(\\1)\\s*\\)',  # Double or single quotes with escape support\n            r'Translation\\.tr\\s*\\(\\s*`([^`]*(?:\\\\.[^`]*)*?)`\\s*\\)',          # Backticks (template strings)\n        ]\n        \n        # Search all .qml and .js files\n        file_extensions = ['*.qml', '*.js']\n        \n        for ext in file_extensions:\n            for file_path in self.source_dir.rglob(ext):\n                try:\n                    with open(file_path, 'r', encoding='utf-8') as f:\n                        content = f.read()\n                        \n                    for pattern in patterns:\n                        matches = re.findall(pattern, content, re.MULTILINE | re.DOTALL)\n                        for match in matches:\n                            # Handle different match group structures\n                            if isinstance(match, tuple):\n                                # For improved regex, text is in the second group\n                                if len(match) >= 3:\n                                    text = match[1]  # Second group is the text content\n                                else:\n                                    text = match[0] if match else \"\"\n                            else:\n                                text = match\n\n                            try:\n                                if '\\\\u' in text or '\\\\x' in text:\n                                    clean_text = bytes(text, \"utf-8\").decode(\"unicode_escape\")\n                                else:\n                                    clean_text = (\n                                        text.replace('\\\\n', '\\n')\n                                            .replace('\\\\t', '\\t')\n                                            .replace('\\\\r', '\\r')\n                                            .replace('\\\\\"', '\"')\n                                            .replace('\\\\\\'', \"'\")\n                                            .replace('\\\\f', '\\f')\n                                            .replace('\\\\b', '\\b')\n                                            .replace('\\\\\\\\', '\\\\')\n                                    )\n                            except Exception:\n                                clean_text = text\n                            \n                            # Clean text (remove extra whitespace)\n                            clean_text = clean_text.strip()\n                            if clean_text:\n                                translatable_texts.add(clean_text)\n                                \n                except (UnicodeDecodeError, IOError) as e:\n                    print(f\"Warning: Cannot read file {file_path}: {e}\")\n                    \n        return translatable_texts\n    \n    def create_temp_translation_file(self, texts: Set[str]) -> str:\n        \"\"\"Create temporary JSON file containing extracted texts\"\"\"\n        temp_data = {}\n        for text in sorted(texts):\n            temp_data[text] = text  # Key and value are the same, indicating untranslated\n            \n        # Create temporary file\n        with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:\n            json.dump(temp_data, f, ensure_ascii=False, indent=2)\n            self.temp_extracted_file = f.name\n            \n        return self.temp_extracted_file\n    \n    def load_translation_file(self, lang_code: str) -> Dict[str, str]:\n        \"\"\"Load translation file for specified language\"\"\"\n        file_path = self.translations_dir / f\"{lang_code}.json\"\n        if file_path.exists():\n            try:\n                with open(file_path, 'r', encoding='utf-8') as f:\n                    return json.load(f)\n            except (json.JSONDecodeError, IOError) as e:\n                print(f\"Warning: Cannot load translation file {file_path}: {e}\")\n                return {}\n        return {}\n    \n    def save_translation_file(self, lang_code: str, translations: Dict[str, str]):\n        \"\"\"Save translation file\"\"\"\n        file_path = self.translations_dir / f\"{lang_code}.json\"\n        try:\n            with open(file_path, 'w', encoding='utf-8') as f:\n                json.dump(translations, f, ensure_ascii=False, indent=2)\n            print(f\"Translation file saved: {file_path}\")\n        except IOError as e:\n            print(f\"Error: Cannot save translation file {file_path}: {e}\")\n    \n    def get_available_languages(self) -> List[str]:\n        \"\"\"Get list of available languages\"\"\"\n        languages = []\n        for file_path in self.translations_dir.glob(\"*.json\"):\n            lang_code = file_path.stem\n            languages.append(lang_code)\n        return sorted(languages)\n    \n    def compare_translations(self, extracted_texts: Set[str], target_lang: str) -> Tuple[Set[str], Set[str]]:\n        \"\"\"Compare extracted texts with existing translation file\"\"\"\n        existing_translations = self.load_translation_file(target_lang)\n        existing_keys = set(existing_translations.keys())\n        \n        missing_keys = extracted_texts - existing_keys  # Missing keys\n        extra_keys = existing_keys - extracted_texts    # Extra keys\n        \n        return missing_keys, extra_keys\n    \n    def interactive_update(self, lang_code: str, missing_keys: Set[str], extra_keys: Set[str]):\n        \"\"\"Interactively update translation file, create backup only if updating\"\"\"\n        translations = self.load_translation_file(lang_code)\n        modified = False\n        backup_created = False\n\n        # Handle missing keys\n        if missing_keys:\n            print(f\"\\nFound {len(missing_keys)} missing translation keys:\")\n            for i, key in enumerate(sorted(missing_keys), 1):\n                print(f\"{i}. \\\"{key}\\\"\")\n\n            if self.ask_yes_no(f\"\\nAdd these {len(missing_keys)} missing keys?\"):\n                if not backup_created:\n                    backup_file = self.translations_dir / f\"{lang_code}.json.bak\"\n                    with open(backup_file, 'w', encoding='utf-8') as f:\n                        json.dump(translations, f, ensure_ascii=False, indent=2)\n                    print(f\"Created backup: {backup_file}\")\n                    backup_created = True\n                for key in missing_keys:\n                    translations[key] = key  # Default value is the key itself\n                    modified = True\n                print(f\"Added {len(missing_keys)} keys\")\n\n        # Handle extra keys\n        if extra_keys:\n            # Only show extra keys that are not marked with /*keep*/\n            filtered_extra_keys = [key for key in extra_keys if not (isinstance(translations.get(key, \"\"), str) and translations.get(key, \"\").strip().endswith('/*keep*/'))]\n            if filtered_extra_keys:\n                print(f\"\\nFound {len(filtered_extra_keys)} extra translation keys:\")\n                for i, key in enumerate(sorted(filtered_extra_keys), 1):\n                    print(f\"{i}. \\\"{key}\\\" -> \\\"{translations.get(key, '')}\\\"\")\n                if self.ask_yes_no(f\"\\nDelete these {len(filtered_extra_keys)} extra keys?\"):\n                    if not backup_created:\n                        backup_file = self.translations_dir / f\"{lang_code}.json.bak\"\n                        with open(backup_file, 'w', encoding='utf-8') as f:\n                            json.dump(translations, f, ensure_ascii=False, indent=2)\n                        print(f\"Created backup: {backup_file}\")\n                        backup_created = True\n                    deleted_count = 0\n                    for key in filtered_extra_keys:\n                        if key in translations:\n                            del translations[key]\n                            modified = True\n                            deleted_count += 1\n                    print(f\"Deleted {deleted_count} keys\")\n\n        # Save changes\n        if modified:\n            self.save_translation_file(lang_code, translations)\n        else:\n            print(\"No changes made\")\n    \n    def ask_yes_no(self, question: str) -> bool:\n        \"\"\"Ask user for confirmation, auto-confirm if yes_mode is True\"\"\"\n        if getattr(self, \"yes_mode\", False):\n            print(f\"{question} (auto-confirmed by --yes)\")\n            return True\n        while True:\n            response = input(f\"{question} (y/n): \").lower().strip()\n            if response in ['y', 'yes']:\n                return True\n            elif response in ['n', 'no']:\n                return False\n            else:\n                print(\"Please enter y/yes or n/no\")\n    \n    def cleanup(self):\n        \"\"\"Clean up temporary files\"\"\"\n        if self.temp_extracted_file and os.path.exists(self.temp_extracted_file):\n            os.unlink(self.temp_extracted_file)\n\ndef main():\n    parser = argparse.ArgumentParser(description=\"Translation file management tool\")\n    parser.add_argument(\"--translations-dir\", \"-t\", \n                       default=\".config/quickshell/translations\",\n                       help=\"Translation files directory (default: .config/quickshell/translations)\")\n    parser.add_argument(\"--source-dir\", \"-s\", \n                       default=\".config/quickshell/ii\",\n                       help=\"Source code directory (default: .config/quickshell/ii)\")\n    parser.add_argument(\"--language\", \"-l\", \n                       help=\"Specify language code to process (e.g., zh_CN)\")\n    parser.add_argument(\"--extract-only\", \"-e\", action=\"store_true\",\n                       help=\"Only extract translatable texts to temporary file\")\n    parser.add_argument(\"--show-temp\", action=\"store_true\",\n                       help=\"Show temporary extracted file content\")\n    parser.add_argument(\"-y\", \"--yes\", action=\"store_true\",\n                       help=\"Skip all confirmation prompts (auto-confirm)\")\n    \n    args = parser.parse_args()\n    \n    # Convert to absolute paths\n    translations_dir = os.path.abspath(args.translations_dir)\n    source_dir = os.path.abspath(args.source_dir)\n    \n    print(f\"Translation directory: {translations_dir}\")\n    print(f\"Source code directory: {source_dir}\")\n    \n    # Check if directories exist\n    if not os.path.exists(source_dir):\n        print(f\"Error: Source code directory does not exist: {source_dir}\")\n        sys.exit(1)\n    \n    # Create manager\n    manager = TranslationManager(translations_dir, source_dir, yes_mode=args.yes)\n    \n    try:\n        # Extract translatable texts\n        print(\"\\nExtracting translatable texts...\")\n        extracted_texts = manager.extract_translatable_texts()\n        print(f\"Extracted {len(extracted_texts)} translatable texts\")\n        \n        # Create temporary file\n        temp_file = manager.create_temp_translation_file(extracted_texts)\n        print(f\"Created temporary file: {temp_file}\")\n        \n        if args.show_temp:\n            print(\"\\nTemporary file contents:\")\n            with open(temp_file, 'r', encoding='utf-8') as f:\n                print(f.read())\n        \n        if args.extract_only:\n            print(\"Extract-only mode, program finished\")\n            return\n        \n        # Get available languages\n        available_languages = manager.get_available_languages()\n        \n        if args.language:\n            target_languages = [args.language]\n        else:\n            print(f\"\\nAvailable languages: {', '.join(available_languages) if available_languages else 'None'}\")\n            if not available_languages:\n                if manager.yes_mode:\n                    print(\"No existing translation files found, auto-skipping language creation due to --yes\")\n                    return\n                lang_input = input(\"Enter language code to create (e.g.: zh_CN): \").strip()\n                if lang_input:\n                    target_languages = [lang_input]\n                else:\n                    print(\"No language specified, program finished\")\n                    return\n            else:\n                print(\"Choose language to process:\")\n                for i, lang in enumerate(available_languages, 1):\n                    print(f\"{i}. {lang}\")\n                print(\"a. Process all languages\")\n                if manager.yes_mode:\n                    choice = 'a'\n                    print(\"Auto-selecting all languages due to --yes\")\n                else:\n                    choice = input(\"Please choose (enter number, language code, or 'a'): \").strip()\n                \n                if choice.lower() == 'a':\n                    target_languages = available_languages\n                elif choice.isdigit() and 1 <= int(choice) <= len(available_languages):\n                    target_languages = [available_languages[int(choice) - 1]]\n                elif choice in available_languages:\n                    target_languages = [choice]\n                else:\n                    print(\"Invalid choice, program finished\")\n                    return\n        \n        # Process each language\n        for lang in target_languages:\n            print(f\"\\n{'='*50}\")\n            print(f\"Processing language: {lang}\")\n            print('='*50)\n            \n            missing_keys, extra_keys = manager.compare_translations(extracted_texts, lang)\n            \n            if not missing_keys and not extra_keys:\n                print(f\"Translation file for language {lang} is already up to date\")\n                continue\n            \n            print(f\"Analysis results:\")\n            print(f\"  Missing keys: {len(missing_keys)}\")\n            # Load translation file for current lang to get values\n            current_translations = manager.load_translation_file(lang)\n            filtered_extra_keys = [key for key in extra_keys if not (isinstance(current_translations.get(key, \"\"), str) and current_translations.get(key, \"\").strip().endswith('/*keep*/'))]\n            ignored_extra_keys = [key for key in extra_keys if (isinstance(current_translations.get(key, \"\"), str) and current_translations.get(key, \"\").strip().endswith('/*keep*/'))]\n            print(f\"  Extra keys: {len(filtered_extra_keys)}\")\n            if ignored_extra_keys:\n                print(f\"  Ignored keys: {len(ignored_extra_keys)} (marked with /*keep*/)\")\n            \n            if missing_keys or extra_keys:\n                manager.interactive_update(lang, missing_keys, extra_keys)\n        \n    finally:\n        # Clean up temporary files\n        manager.cleanup()\n\nif __name__ == \"__main__\":\n    main()\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/tr_TR.json",
    "content": "{\n  \"Material cookie\": \"Material cookie\",\n  \"Style: Blurred\": \"Stil: Bulanık\",\n  \"Unknown device\": \"Bilinmeyen cihaz\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"İstediğiniz zaman /dark, /light, /wallpaper komutlarıyla değiştirebilirsiniz\\nShell renkleri değişmiyorsa:\\n    1. Super+N ile sağ kenar çubuğunu açın\\n    2. Sağ üst köşedeki \\\"Hyprland ve Quickshell'i Yeniden Yükle\\\" düğmesine tıklayın\",\n  \"No pending tasks\": \"Bekleyen görev yok\",\n  \"Positioning\": \"Konumlandırma\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Modelin sıcaklığını (rastgelelik) ayarlayın. Gemini için 0 ile 2 arası, diğer modeller için 0 ile 1 arası değerler. Varsayılan 0.5.\",\n  \"Critical warning\": \"Kritik uyarı\",\n  \"Unknown Artist\": \"Bilinmeyen Sanatçı\",\n  \"Web search\": \"Web araması\",\n  \"Load prompt from %1\": \"%1'den komut yükle\",\n  \"Attach a file. Only works with Gemini.\": \"Dosya ekle. Sadece Gemini ile çalışır.\",\n  \"Reboot\": \"Yeniden Başlat\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"API anahtarı:\\n\\n```txt\\n%1\\n```\",\n  \"Pinned on startup\": \"Başlangıçta sabitle\",\n  \"Right\": \"Sağ\",\n  \"Reboot to firmware settings\": \"Firmware ayarlarına yeniden başlat\",\n  \"Automatically hide\": \"Otomatik gizle\",\n  \"Waiting for response...\": \"Yanıt bekleniyor...\",\n  \"To Do\": \"Yapılacaklar\",\n  \"Full\": \"Tam\",\n  \"Select Language\": \"Dil Seçin\",\n  \"Password\": \"Şifre\",\n  \"Bluetooth devices\": \"Bluetooth cihazları\",\n  \"Enable\": \"Etkinleştir\",\n  \"Elements\": \"Öğeler\",\n  \"Start\": \"Başlat\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Konachan'dan rastgele SFW Anime duvar kağıdı\\nGörsel ~/Resimler/Wallpapers dizinine kaydedilir\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Popüler olan | En iyi miktar, ama kalite değişebilir\",\n  \"System uptime:\": \"Sistem çalışma süresi:\",\n  \"illogical-impulse Welcome\": \"illogical-impulse'a Hoş Geldiniz\",\n  \"Code saved to file\": \"Kod dosyaya kaydedildi\",\n  \"Info\": \"Bilgi\",\n  \"Preferred wallpaper zoom (%)\": \"Tercih edilen duvar kağıdı yakınlaştırması (%)\",\n  \"Time\": \"Zaman\",\n  \"Help & Support\": \"Yardım ve Destek\",\n  \"Bubble\": \"Baloncuk\",\n  \"Large images | God tier quality, no NSFW.\": \"Büyük görseller | Mükemmel kalite, NSFW yok.\",\n  \"Dark\": \"Koyu\",\n  \"Center clock\": \"Saati ortala\",\n  \"Search, calculate or run\": \"Ara, hesapla veya çalıştır\",\n  \"Region height\": \"Bölge yüksekliği\",\n  \"Load chat\": \"Sohbet yükle\",\n  \"Gives the model search capabilities (immediately)\": \"Modele arama yetenekleri kazandırır (hemen)\",\n  \"Depends on workspace\": \"Çalışma alanına bağlı\",\n  \"Blurred style\": \"Bulanık stil\",\n  \"Screenshot tool\": \"Ekran görüntüsü aracı\",\n  \"Enter password\": \"Şifre girin\",\n  \"Search the web\": \"Web'de ara\",\n  \"Local only\": \"Sadece yerel\",\n  \"at\": \"saat\",\n  \"Math\": \"Matematik\",\n  \"Consider plugging in your device\": \"Cihazınızı prize takın\",\n  \"Workspaces shown\": \"Gösterilen çalışma alanları\",\n  \"Place the corners to trigger at the bottom\": \"Tetikleyici köşeleri alta yerleştir\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"API anahtarı yok\\n/key API_ANAHTARINIZ ile ayarlayın\",\n  \"Auto (System)\": \"Otomatik (Sistem)\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Ok tuşlarıyla gezin, Enter ile seçin\\nEsc veya herhangi bir yere tıklayarak iptal edin\",\n  \"Critically low battery\": \"Kritik düşük pil\",\n  \"Open editor\": \"Düzenleyici aç\",\n  \"%1 notifications\": \"%1 bildirim\",\n  \"Region width\": \"Bölge genişliği\",\n  \"Max allowed increase\": \"İzin verilen maksimum artış\",\n  \"Enable translator\": \"Çevirmeni etkinleştir\",\n  \"Constantly rotate\": \"Sürekli döndür\",\n  \"Automatically suspends the system when battery is low\": \"Pil düşük olduğunda sistemi otomatik olarak askıya alır\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"GPS hizmeti bulunamadı. Yerine yedek yöntem kullanılıyor.\",\n  \"Qt apps\": \"Qt uygulamaları\",\n  \"Color picker\": \"Renk seçici\",\n  \"Interface\": \"Arayüz\",\n  \"Tint app icons\": \"Uygulama simgelerini renklendir\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"Kullanıcı arayüzü dilini seçin.\\n\\\"Otomatik\\\" sisteminizin yerel ayarını kullanır.\",\n  \"Show quote\": \"Alıntı göster\",\n  \"Local Ollama model | %1\": \"Yerel Ollama modeli | %1\",\n  \"Show clock\": \"Saat göster\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"Kullanım: <tt>%1superpaste GIRDI_SAYISI[i]</tt>\\nGörseller için <tt>i</tt> ekleyin\\nÖrnekler:\\n<tt>%1superpaste 4i</tt> son 4 görsel için\\n<tt>%1superpaste 7</tt> son 7 girdi için\",\n  \"Audio\": \"Ses\",\n  \"Corner style\": \"Köşe stili\",\n  \"No media\": \"Medya yok\",\n  \"Unknown function call: %1\": \"Bilinmeyen fonksiyon çağrısı: %1\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Çevrimiçi | %1'in modeli | Hızlı, duyarlı ve iyi biçimlendirilmiş yanıtlar verir. Dezavantajlar: işleri yapmaya pek istekli değil; bilinmeyen fonksiyon çağrıları yapabilir\",\n  \"Volume\": \"Ses\",\n  \"Medium\": \"Orta\",\n  \"Copy code\": \"Kodu kopyala\",\n  \"Exceeded max allowed\": \"İzin verilen maksimum aşıldı\",\n  \"Keep right sidebar loaded\": \"Sağ kenar çubuğunu yüklü tut\",\n  \"Left\": \"Sol\",\n  \"High\": \"Yüksek\",\n  \"Rect\": \"Dikdörtgen\",\n  \"Lap\": \"Tur\",\n  \"Clear\": \"Temizle\",\n  \"Screen snip\": \"Ekran kırpma\",\n  \"Reset\": \"Sıfırla\",\n  \"Back\": \"Geri\",\n  \"Dark/Light toggle\": \"Koyu/Açık geçişi\",\n  \"12h am/pm\": \"12s öö/ös\",\n  \"Download complete\": \"İndirme tamamlandı\",\n  \"Enable blur\": \"Bulanıklığı etkinleştir\",\n  \"Second hand\": \"Saniye ibresi\",\n  \"Bar & screen\": \"Çubuk ve ekran\",\n  \"Discharging:\": \"Deşarj:\",\n  \"Up %1\": \"Çalışma süresi %1\",\n  \"Low\": \"Düşük\",\n  \"Hour hand\": \"Saat ibresi\",\n  \"Clear chat history\": \"Sohbet geçmişini temizle\",\n  \"Fruit Salad\": \"Meyve Salatası\",\n  \"%1 Safe Storage\": \"%1 Güvenli Depolama\",\n  \"Hibernate\": \"Hazırda Beklet\",\n  \"Delete\": \"Sil\",\n  \"OK\": \"Tamam\",\n  \"Settings\": \"Ayarlar\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"Bu genellikle güvenlidir ve tarayıcınız ve AI kenar çubuğu için zaten gereklidir\\nÇoğunlukla başlangıçta kilitleme kullanıp bunu yapan bir görüntü yöneticisi (GDM, SDDM, vb.) kullanmayanlar için faydalıdır\",\n  \"Use Hyprlock (instead of Quickshell)\": \"Hyprlock kullan (Quickshell yerine)\",\n  \"Crosshair code (in Valorant's format)\": \"Nişangah kodu (Valorant formatında)\",\n  \"Silent\": \"Sessiz\",\n  \"Useless buttons\": \"İşe yaramaz düğmeler\",\n  \"Hover to reveal\": \"Görmek için üzerine gelin\",\n  \"Wallpaper & Colors\": \"Duvar Kağıdı ve Renkler\",\n  \"Auto\": \"Otomatik\",\n  \"Visibility\": \"Görünürlük\",\n  \"Shell & utilities\": \"Shell ve yardımcı programlar\",\n  \"Hollow\": \"İçi boş\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"Bunun yerine sistem dosya seçiciyi kullan\\nBunu varsayılan davranış yapmak için sağ tıklayın\",\n  \"On-screen display\": \"Ekran üstü gösterim\",\n  \"Dotfiles\": \"Nokta dosyaları\",\n  \"Search wallpapers\": \"Duvar kağıtlarında ara\",\n  \"Mic toggle\": \"Mikrofon geçişi\",\n  \"Input\": \"Giriş\",\n  \"Also unlock keyring\": \"Anahtar halkasının da kilidini aç\",\n  \"Configuration\": \"Yapılandırma\",\n  \"Keep system awake\": \"Sistemi uyanık tut\",\n  \"Unknown command:\": \"Bilinmeyen komut:\",\n  \"Anime boorus\": \"Anime booru'ları\",\n  \"To Do:\": \"Yapılacaklar:\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"Gemini kullanarak duvar kağıdını kategorize eder ve ona göre bir ön ayar seçer.\\nÖnce sol kenar çubuğunda Gemini API anahtarını ayarlamanız gerekir.\\nGörseller performans için küçültülür, ancak güvenli olmak için\\nhasas bilgi içeren duvar kağıtlarını seçmeyin.\",\n  \"Bottom\": \"Alt\",\n  \"Clear the current list of images\": \"Mevcut görsel listesini temizle\",\n  \"Sunrise\": \"Gün doğumu\",\n  \"Show app icons\": \"Uygulama simgelerini göster\",\n  \"Format\": \"Format\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"Oynatıcınızın MPRIS desteği olduğundan emin olun\\nveya yinelenen oynatıcı filtrelemeyi kapatmayı deneyin\",\n  \"Pause\": \"Duraklat\",\n  \"Desktop\": \"Masaüstü\",\n  \"Conflicts with the shell's system tray implementation\": \"Kabuğun sistem tepsisi uygulamasıyla çakışıyor\",\n  \"Your package manager is running\": \"Paket yöneticiniz çalışıyor\",\n  \"Conflicts with the shell's notification implementation\": \"Kabuğun bildirim uygulamasıyla çakışıyor\",\n  \"Unknown Album\": \"Bilinmeyen Albüm\",\n  \"Pick wallpaper image on your system\": \"Sisteminizdeki duvar kağıdı görselini seçin\",\n  \"Used:\": \"Kullanılan:\",\n  \"Cheat sheet\": \"Kopya kağıdı\",\n  \"Clock style\": \"Saat stili\",\n  \"No audio source\": \"Ses kaynağı yok\",\n  \"Paired\": \"Eşleştirildi\",\n  \"Documentation\": \"Belgeler\",\n  \"No\": \"Hayır\",\n  \"Pills\": \"Haplar\",\n  \"Thought\": \"Düşünce\",\n  \"When this is off you'll have to click\": \"Bu kapalı olduğunda tıklamanız gerekecek\",\n  \"Select output device\": \"Çıkış cihazını seç\",\n  \"Logout\": \"Çıkış Yap\",\n  \"Tip: Close a window with Super+Q\": \"İpucu: Super+Q ile bir pencereyi kapatın\",\n  \"Finished tasks will go here\": \"Tamamlanan görevler buraya gelecek\",\n  \"Terminal: Harmony (%)\": \"Terminal: Uyum (%)\",\n  \"Corner open\": \"Köşe açık\",\n  \"Shell conflicts killer\": \"Shell çakışma giderici\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Temiz içerik | Mükemmel kalite, NSFW yok\",\n  \"Scroll to change volume\": \"Ses değiştirmek için kaydırın\",\n  \"Wind\": \"Rüzgar\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API anahtarı ayarlandı\\n/key API_ANAHTARINIZ ile değiştirin\",\n  \"Neutral\": \"Nötr\",\n  \"12h AM/PM\": \"12s ÖÖ/ÖS\",\n  \"Number show delay when pressing Super (ms)\": \"Super'e basarken numara gösterme gecikmesi (ms)\",\n  \"Fill\": \"Doldur\",\n  \"Always show numbers\": \"Numaraları her zaman göster\",\n  \"Dot\": \"Nokta\",\n  \"Provider set to\": \"Sağlayıcı şu şekilde ayarlandı\",\n  \"Unknown Title\": \"Bilinmeyen Başlık\",\n  \"Anime\": \"Anime\",\n  \"Refreshing (manually triggered)\": \"Yenileniyor (manuel tetiklendi)\",\n  \"Dock\": \"Dock\",\n  \"Require password to power off/restart\": \"Kapatma/yeniden başlatma için şifre iste\",\n  \"Line\": \"Çizgi\",\n  \"Weather\": \"Hava Durumu\",\n  \"All-rounder | Good quality, decent quantity\": \"Çok yönlü | İyi kalite, makul miktar\",\n  \"Scale (%)\": \"Ölçek (%)\",\n  \"Copy\": \"Kopyala\",\n  \"Usage\": \"Kullanım\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Çevrimiçi modellerle başlamak için /key yazın\\nKenar çubuğunu genişletmek için Ctrl+O\\nKenar çubuğunu pencereye ayırmak için Ctrl+P\",\n  \"Set the tool to use for the model.\": \"Model için kullanılacak aracı ayarlayın.\",\n  \"Disable tools\": \"Araçları devre dışı bırak\",\n  \"Connect\": \"Bağlan\",\n  \"Allow NSFW\": \"NSFW'ye izin ver\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Kayıt başarısız. Lütfen <tt>warp-cli</tt> komutuyla manuel olarak kontrol edin\",\n  \"Time to full:\": \"Dolmasına kalan süre:\",\n  \"Session\": \"Oturum\",\n  \"Services\": \"Hizmetler\",\n  \"Nothing here!\": \"Burada hiçbir şey yok!\",\n  \"Overview\": \"Genel Bakış\",\n  \"Random: osu! seasonal\": \"Rastgele: osu! mevsimlik\",\n  \"If you want to somehow use fingerprint unlock...\": \"Parmak izi kilidi açmayı kullanmak istiyorsanız...\",\n  \"Minute hand\": \"Dakika ibresi\",\n  \"Notifications\": \"Bildirimler\",\n  \"Enable if you want clocks to show seconds accurately\": \"Saatlerin saniyeleri doğru göstermesini istiyorsanız etkinleştirin\",\n  \"Timer\": \"Zamanlayıcı\",\n  \"Quote settings\": \"Alıntı ayarları\",\n  \"System prompt\": \"Sistem komutu\",\n  \"Classic\": \"Klasik\",\n  \"Close\": \"Kapat\",\n  \"Disconnect\": \"Bağlantıyı kes\",\n  \"Go to source (%1)\": \"Kaynağa git (%1)\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Yapılandırmak için sağ tıklayın\",\n  \"Forget\": \"Unut\",\n  \"Output\": \"Çıkış\",\n  \"Date style\": \"Tarih stili\",\n  \"System\": \"Sistem\",\n  \"Usage: %1tool TOOL_NAME\": \"Kullanım: %1tool ARAÇ_ADI\",\n  \"Workspaces\": \"Çalışma alanları\",\n  \"Calendar\": \"Takvim\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Talimatlar**: Mistral hesabına giriş yapın, kenar çubuğundaki Keys'e gidin, Create new key'e tıklayın\",\n  \"Volume limit\": \"Ses sınırı\",\n  \"Sunset\": \"Gün batımı\",\n  \"Dial style\": \"Kadran stili\",\n  \"Hi there! First things first...\": \"Merhaba! İlk önce...\",\n  \"Save chat to %1\": \"Sohbeti %1'e kaydet\",\n  \"Security\": \"Güvenlik\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Toplam token sayısı\\nGiriş: %1\\nÇıkış: %2\",\n  \"Cancel wallpaper selection\": \"Duvar kağıdı seçimini iptal et\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Lütfen şarj edin!\\nOtomatik askıya alma %1'de tetikleniyor\",\n  \"Terminal: Harmonize threshold\": \"Terminal: Uyumlaştırma eşiği\",\n  \"Be patient...\": \"Sabırlı olun...\",\n  \"Utility buttons\": \"Yardımcı düğmeler\",\n  \"Tonal Spot\": \"Tonal Nokta\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Ani artışları önler ve ses sınırını kısıtlar\",\n  \"Set the current API provider\": \"Mevcut API sağlayıcısını ayarlayın\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Bağlantı başarısız. Lütfen <tt>warp-cli</tt> komutuyla manuel olarak kontrol edin\",\n  \"Networking\": \"Ağ\",\n  \"Tint icons\": \"Simgeleri renklendir\",\n  \"Low battery\": \"Düşük pil\",\n  \"Make icons pinned by default\": \"Simgeleri varsayılan olarak sabitle\",\n  \"Get the next page of results\": \"Sonraki sayfa sonuçları al\",\n  \"Invalid API provider. Supported: \\n-\": \"Geçersiz API sağlayıcısı. Desteklenenler: \\n-\",\n  \"Show \\\"Locked\\\" text\": \"\\\"Kilitli\\\" metnini göster\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Fiyatlandırma**: ücretsiz. Veri kullanım politikası OpenRouter hesap ayarlarınıza göre değişir.\\n\\n**Talimatlar**: OpenRouter hesabına giriş yapın, sağ üst menüdeki Keys'e gidin, Create API Key'e tıklayın\",\n  \"Not visible to model\": \"Model için görünmez\",\n  \"Lock screen\": \"Ekranı kilitle\",\n  \"Save to Downloads\": \"İndirilenler'e kaydet\",\n  \"Expressive\": \"İfadeli\",\n  \"Suspend at\": \"Askıya alma zamanı\",\n  \"Jump to current month\": \"Mevcut aya git\",\n  \"Bold\": \"Kalın\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Sadece waifu'lar | Mükemmel kalite, sınırlı miktar\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"Açık/koyu modu değiştirmek için tıklayın\\n(duvar kağıdı seçildiğinde uygulanır)\",\n  \"Visualize region\": \"Bölgeyi görselleştir\",\n  \"Quote\": \"Alıntı\",\n  \"Sleep\": \"Uyku\",\n  \"Hit \\\"/\\\" to search\": \"Aramak için \\\"/\\\" tuşuna basın\",\n  \"Hug\": \"Sarıl\",\n  \"Report a Bug\": \"Hata Bildir\",\n  \"Precipitation\": \"Yağış\",\n  \"Crosshair\": \"Nişangah\",\n  \"Model set to %1\": \"Model %1 olarak ayarlandı\",\n  \"Rows\": \"Satırlar\",\n  \"Top\": \"Üst\",\n  \"Long break\": \"Uzun mola\",\n  \"Superpaste\": \"Süper yapıştır\",\n  \"Screen round corner\": \"Ekran köşe yuvarlama\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Çevrimiçi | Google'ın modeli\\nÖncekinden daha yavaş ama daha kaliteli yanıtlar vermesi gereken yeni model\",\n  \"Rainbow\": \"Gökkuşağı\",\n  \"Weeb\": \"Weeb\",\n  \"Large language models\": \"Büyük dil modelleri\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Çevrimiçi modellere izin verilmiyor\\n\\n`policies.ai` yapılandırma seçeneği ile kontrol edilir\",\n  \"Policies\": \"Politikalar\",\n  \"Temperature must be between 0 and 2\": \"Sıcaklık 0 ile 2 arasında olmalıdır\",\n  \"Automatic suspend\": \"Otomatik askıya alma\",\n  \"Extra wallpaper zoom (%)\": \"Ekstra duvar kağıdı yakınlaştırması (%)\",\n  \"GitHub\": \"GitHub\",\n  \"%1 | Right-click to configure\": \"%1 | Yapılandırmak için sağ tıklayın\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**Fiyatlandırma**: Sınırlı kullanımlı ücretsiz katman mevcut. Bakınız https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Talimatlar**: Models izniyle bir GitHub kişisel erişim jetonu oluşturun, ardından buraya API anahtarı olarak ayarlayın\\n\\n**Not**: Bunu kullanmak için sıcaklık parametresini 1'e ayarlamanız gerekecek\",\n  \"Edit directory\": \"Dizini düzenle\",\n  \"Action\": \"Eylem\",\n  \"Search\": \"Ara\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"İpucu: bir gruba sağ tıklamak\\naynı zamanda genişletir\",\n  \"Bar\": \"Çubuk\",\n  \"Show regions of potential interest\": \"Potansiyel ilgi alanlarını göster\",\n  \"Clipboard\": \"Pano\",\n  \"Stopwatch\": \"Kronometre\",\n  \"Enter text to translate...\": \"Çevrilecek metni girin...\",\n  \"App\": \"Uygulama\",\n  \"Sides\": \"Yanlar\",\n  \"No active player\": \"Aktif oynatıcı yok\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"Bu uygulamada tüm seçenekler mevcut değil. Sol üst köşedeki \\\"Yapılandırma dosyası\\\" düğmesine basarak veya %1'i manuel olarak açarak yapılandırma dosyasını da kontrol etmelisiniz.\",\n  \"There might be a download in progress\": \"Devam eden bir indirme olabilir\",\n  \"Math result\": \"Matematik sonucu\",\n  \"Fidelity\": \"Sadakat\",\n  \"Prefixes\": \"Önekler\",\n  \"Terminal\": \"Terminal\",\n  \"Incorrect password\": \"Yanlış şifre\",\n  \"Line-separated\": \"Satır ayrılmış\",\n  \"Always\": \"Her zaman\",\n  \"☕ Break: %1 minutes\": \"☕ Mola: %1 dakika\",\n  \"Depends on sidebars\": \"Kenar çubuklarına bağlı\",\n  \"Tool set to: %1\": \"Araç şu şekilde ayarlandı: %1\",\n  \"Save chat\": \"Sohbeti kaydet\",\n  \"Crosshair overlay\": \"Nişangah bindirmesi\",\n  \"Keybinds\": \"Tuş atamaları\",\n  \"Launch\": \"Başlat\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Çok fazla yazım hatası yaparsanız daha iyi olabilir,\\nancak sonuçlar garip olabilir ve kısaltmalarla çalışmayabilir\\n(örn. \\\"GIMP\\\" size boyama programını vermeyebilir)\",\n  \"Choose model\": \"Model seç\",\n  \"Base URL\": \"Temel URL\",\n  \"Float\": \"Yüzen\",\n  \"Wallpaper parallax\": \"Duvar kağıdı paralaks\",\n  \"Invalid arguments. Must provide `command`.\": \"Geçersiz argümanlar. `command` sağlanmalıdır.\",\n  \"Fully charged\": \"Tamamen şarj oldu\",\n  \"Earbang protection\": \"Kulak patlama koruması\",\n  \"Low warning\": \"Düşük uyarısı\",\n  \"Advanced\": \"Gelişmiş\",\n  \"Scroll to change brightness\": \"Parlaklığı değiştirmek için kaydırın\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Aşağıdaki sistem komutu yüklendi\\n\\n---\\n\\n%1\",\n  \"Show next time\": \"Bir dahaki sefere göster\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Mevcut araç: %1\\n%2tool ARAÇ ile ayarlayın\",\n  \"Unread indicator: show count\": \"Okunmamış göstergesi: sayıyı göster\",\n  \"Press Super+G to toggle appearance\": \"Görünümü değiştirmek için Super+G'ye basın\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Bu işe yaramadı. İpuçları:\\n- Etiketlerinizi ve NSFW ayarlarınızı kontrol edin\\n- Aklınızda bir etiket yoksa, bir sayfa numarası yazın\",\n  \"Dots\": \"Noktalar\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Volume mixer\": \"Ses karıştırıcı\",\n  \"Config file\": \"config dosyası\",\n  \"API key set for %1\": \"%1 için API anahtarı ayarlandı\",\n  \"Online via %1 | %2's model\": \"%1 üzerinden çevrimiçi | %2'nin modeli\",\n  \"Shell command\": \"Shell komutu\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Bu tür bölgeler görseller veya ekranın içerme özelliği olan bölümleri olabilir.\\nHer zaman doğru olmayabilir.\\nBu, yerel olarak çalıştırılan bir görüntü işleme algoritmasıyla yapılır ve AI kullanılmaz.\",\n  \"Reload Hyprland & Quickshell\": \"Hyprland ve Quickshell'i Yeniden Yükle\",\n  \"Resources\": \"Kaynaklar\",\n  \"Brightness\": \"Parlaklık\",\n  \"Unknown\": \"Bilinmeyen\",\n  \"Polling interval (ms)\": \"Yoklama aralığı (ms)\",\n  \"Lock\": \"Kilitle\",\n  \"Thinking\": \"Düşünüyor\",\n  \"Approve\": \"Onayla\",\n  \"Unfinished\": \"Tamamlanmamış\",\n  \"Random: Konachan\": \"Rastgele: Konachan\",\n  \"Connected\": \"Bağlandı\",\n  \"Wallpaper safety enforced\": \"Duvar kağıdı güvenliği uygulandı\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Geçersiz argümanlar. `key` ve `value` sağlanmalıdır.\",\n  \"24h\": \"24s\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"Çubuk konumundan bağımsız olarak ekran köşelerine tıklayarak veya üzerine gelerek kenar çubuklarını açmanıza olanak tanır\",\n  \"Bar style\": \"Çubuk stili\",\n  \"Load:\": \"Yük:\",\n  \"Open file link\": \"Dosya bağlantısını aç\",\n  \"Ignored if terminal theming is not enabled\": \"Terminal temalaması etkinleştirilmemişse yok sayılır\",\n  \"Shutdown\": \"Kapat\",\n  \"Hour marks\": \"Saat işaretleri\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"Rastgele osu! mevsimlik arka plan\\nGörsel ~/Resimler/Wallpapers dizinine kaydedilir\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Çevrimiçi | Google'ın modeli\\nHızlı, güncel bilgi için arama yapabilir\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Mevcut model: %1\\n%2model MODEL ile ayarlayın\",\n  \"Select input device\": \"Giriş cihazını seç\",\n  \"Connect to Wi-Fi\": \"Wi-Fi'ye bağlan\",\n  \"... and %1 more\": \"... ve %1 daha fazla\",\n  \"Cookie clock settings\": \"Kurabiye saat ayarları\",\n  \"Brightness and volume\": \"Parlaklık ve ses\",\n  \"Choose file\": \"Dosya seç\",\n  \"Invalid model. Supported: \\n```\": \"Geçersiz model. Desteklenenler: \\n```\",\n  \"Task Manager\": \"Görev Yöneticisi\",\n  \"Charging:\": \"Şarj oluyor:\",\n  \"Illegal increment\": \"Geçersiz artış\",\n  \"Total:\": \"Toplam:\",\n  \"or\": \"veya\",\n  \"Battery\": \"Pil\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"Zaman aşımı süresi (bildirim tarafından tanımlanmamışsa) (ms)\",\n  \"Cancel\": \"İptal\",\n  \"Locked\": \"Kilitli\",\n  \"Temperature: %1\": \"Sıcaklık: %1\",\n  \"Hover to trigger\": \"Tetiklemek için üzerine gelin\",\n  \"Command rejected by user\": \"Komut kullanıcı tarafından reddedildi\",\n  \"User agent (for services that require it)\": \"Kullanıcı aracısı (gerektiren hizmetler için)\",\n  \"Saved to %1\": \"%1'e kaydedildi\",\n  \"Emojis\": \"Emoji'ler\",\n  \"Color generation\": \"Renk oluşturma\",\n  \"Welcome app\": \"Hoş geldiniz uygulaması\",\n  \"Humidity\": \"Nem\",\n  \"Page %1\": \"Sayfa %1\",\n  \"Feels like %1\": \"Hissedilen %1\",\n  \"Distro\": \"Dağıtım\",\n  \"Transparency\": \"Şeffaflık\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 görev\",\n  \"Markdown test\": \"Markdown testi\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Geçersiz araç. Desteklenen araçlar:\\n- %1\",\n  \"No notifications\": \"Bildirim yok\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Hentai olan | Çok miktarda, çok NSFW, kalite çok değişkendir\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Resume\": \"Devam Et\",\n  \"Work safety\": \"İş güvenliği\",\n  \"Temperature\\nChange with /temp VALUE\": \"Sıcaklık\\n/temp DEĞER ile değiştirin\",\n  \"Terminal: Foreground boost (%)\": \"Terminal: Ön plan artışı (%)\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Gece Işığı | Otomatik modu değiştirmek için sağ tıklayın\",\n  \"Closet\": \"Dolap\",\n  \"Yes\": \"Evet\",\n  \"Columns\": \"Sütunlar\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"API anahtarı ayarlamak için %4 komutuyla birlikte iletin\\n\\nAnahtarı görüntülemek için komutla birlikte \\\"get\\\" iletin<br/>\\n\\n### %1 için:\\n\\n**Bağlantı**: %2\\n\\n%3\",\n  \"Kill conflicting programs?\": \"Çakışan programlar sonlandırılsın mı?\",\n  \"For storing API keys and other sensitive information\": \"API anahtarlarını ve diğer hassas bilgileri saklamak için\",\n  \"Reject\": \"Reddet\",\n  \"Set API key\": \"API anahtarını ayarla\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Zerochan için notlar:\\n- Bir renk girmelisiniz\\n- `sidebar.booru.zerochan.username` yapılandırma seçeneğinde zerochan kullanıcı adınızı ayarlayın. [Bunu yapmazsanız yasaklanabilirsiniz](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Content\": \"İçerik\",\n  \"Pomodoro\": \"Pomodoro\",\n  \"Vertical\": \"Dikey\",\n  \"Pick a wallpaper\": \"Bir duvar kağıdı seçin\",\n  \"Load chat from %1\": \"%1'den sohbet yükle\",\n  \"Launch on startup\": \"Başlangıçta başlat\",\n  \"Add\": \"Ekle\",\n  \"Style: general\": \"Stil: genel\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Bulanık yerine Levenshtein mesafe tabanlı algoritma kullan\",\n  \"Shell & utilities theming must also be enabled\": \"Shell ve yardımcı program temalaması da etkinleştirilmelidir\",\n  \"Workspace\": \"Çalışma alanı\",\n  \"Translator\": \"Çevirmen\",\n  \"Free:\": \"Boş:\",\n  \"🌿 Long break: %1 minutes\": \"🌿 Uzun mola: %1 dakika\",\n  \"Value scroll\": \"Değer kaydırma\",\n  \"Bar position\": \"Çubuk konumu\",\n  \"Language\": \"Dil\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Mevcut API uç noktası: %1\\n%2mode SAĞLAYICI ile ayarlayın\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"Çoğu cihazda güç düğmesini basılı tutarak zorla kapatma yapılabileceğini unutmayın\\nBu sadece kazaların olmasını biraz daha zorlaştırır\",\n  \"AI\": \"AI\",\n  \"Task description\": \"Görev açıklaması\",\n  \"Add task\": \"Görev ekle\",\n  \"Donate\": \"Bağış Yap\",\n  \"Disable NSFW content\": \"NSFW içeriği devre dışı bırak\",\n  \"Set the system prompt for the model.\": \"Model için sistem komutunu ayarlayın.\",\n  \"Done\": \"Tamamlandı\",\n  \"Focus\": \"Odaklan\",\n  \"Open the shell config file.\\nIf the button doesn't work or doesn't open in your favorite editor,\\nyou can manually open ~/.config/illogical-impulse/config.json\": \"Shell yapılandırma dosyasını açın.\\nDüğme çalışmıyorsa veya favori düzenleyicinizde açılmıyorsa,\\n~/.config/illogical-impulse/config.json dosyasını manuel olarak açabilirsiniz\",\n  \"View Markdown source\": \"Markdown kaynağını görüntüle\",\n  \"Border\": \"Kenarlık\",\n  \"Temperature set to %1\": \"Sıcaklık %1 olarak ayarlandı\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"Çevrimiçi | Google'ın modeli\\nGoogle'ın kodlama ve karmaşık akıl yürütme görevlerinde mükemmel olan son teknoloji çok amaçlı modeli.\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Modele mesaj gönderin... komutlar için \\\"%1\\\"\",\n  \"Translation goes here...\": \"Çeviri buraya gelir...\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"Etkinleştirildiğinde açarken gecikmeyi azaltmak için sağ kenar çubuğunun içeriğini yüklü tutar,\\nyaklaşık 15MB tutarlı RAM kullanımı pahasına. Gecikme önemi sisteminizin performansına bağlıdır.\\nlinux-cachyos gibi özel bir çekirdek kullanmak yardımcı olabilir\",\n  \"For desktop wallpapers | Good quality\": \"Masaüstü duvar kağıtları için | İyi kalite\",\n  \"🔴 Focus: %1 minutes\": \"🔴 Odaklan: %1 dakika\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Mevcut sistem komutu\\n\\n---\\n\\n%1\",\n  \"About\": \"Hakkında\",\n  \"Quick\": \"Hızlı\",\n  \"General\": \"Genel\",\n  \"UV Index\": \"UV İndeksi\",\n  \"Force dark mode in terminal\": \"Terminalde koyu modu zorla\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Bir bölgeyi sürükleyin veya tıklayın • Sol Tık: Kopyala • Sağ Tık: Düzenle\",\n  \"%1 characters\": \"%1 karakter\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Fiyatlandırma**: ücretsiz. Veriler eğitim için kullanılır.\\n\\n**Talimatlar**: Google hesabına giriş yapın, AI Studio'nun Google Cloud projesi oluşturmasına veya her ne istiyorsa izin verin, geri dönün ve Get API key'e tıklayın\",\n  \"Monochrome\": \"Monokrom\",\n  \"Details\": \"Detaylar\",\n  \"Issues\": \"Sorunlar\",\n  \"Keyboard toggle\": \"Klavye geçişi\",\n  \"Might look ass. Unsupported.\": \"Kötü görünebilir. Desteklenmiyor.\",\n  \"Download\": \"İndir\",\n  \"%1 does not require an API key\": \"%1 API anahtarı gerektirmiyor\",\n  \"Style & wallpaper\": \"Stil ve duvar kağıdı\",\n  \"Second precision\": \"Saniye hassasiyeti\",\n  \"Group style\": \"Grup stili\",\n  \"Break\": \"Mola\",\n  \"Run\": \"Çalıştır\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"Keyfinize bakın! Hoş geldiniz uygulamasını istediğiniz zaman <tt>Super+Shift+Alt+/</tt> ile yeniden açabilirsiniz. Ayarlar uygulamasını açmak için <tt>Super+I</tt> tuşuna basın\",\n  \"Interface Language\": \"Arayüz Dili\",\n  \"Game mode\": \"Oyun modu\",\n  \"Usage: %1save CHAT_NAME\": \"Kullanım: %1save SOHBET_ADI\",\n  \"Thin\": \"İnce\",\n  \"Light\": \"Açık\",\n  \"When not fullscreen\": \"Tam ekran değilken\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Komutlar, yapılandırmaları düzenle, ara.\\nGerekirse arama moduna geçmek için ekstra bir tur alır\",\n  \"Privacy Policy\": \"Gizlilik Politikası\",\n  \"Timeout (ms)\": \"Zaman aşımı (ms)\",\n  \"Allow NSFW content\": \"NSFW içeriğine izin ver\",\n  \"Edit\": \"Düzenle\",\n  \"Digits in the middle\": \"Ortada rakamlar\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Çevrimiçi | Google'ın modeli\\nMaliyet verimliliği ve yüksek verim için optimize edilmiş bir Gemini 2.5 Flash modeli.\",\n  \"Weather Service\": \"Hava Durumu Servisi\",\n  \"Background\": \"Arka plan\",\n  \"Pick random from this folder\": \"Bu klasörden rastgele seç\",\n  \"Pressure\": \"Basınç\",\n  \"Save\": \"Kaydet\",\n  \"Run command\": \"Komutu çalıştır\",\n  \"Time to empty:\": \"Boşalmasına kalan süre:\",\n  \"Place at bottom\": \"Alta yerleştir\",\n  \"Switched to search mode. Continue with the user's request.\": \"Arama moduna geçildi. Kullanıcının isteğiyle devam edin.\",\n  \"Performance Profile toggle\": \"Performans Profili geçişi\",\n  \"Sidebars\": \"Kenar çubukları\",\n  \"Usage: %1load CHAT_NAME\": \"Kullanım: %1load SOHBET_ADI\",\n  \"Auto styling with Gemini\": \"Gemini ile otomatik stil\",\n  \"Simple digital\": \"Basit dijital\",\n  \"No API key set for %1\": \"%1 için API anahtarı ayarlanmadı\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Etiketleri girin veya komutlar için \\\"%1\\\"\",\n  \"%1 queries pending\": \"%1 sorgu bekliyor\",\n  \"Discussions\": \"Tartışmalar\",\n  \"Tray\": \"Tepsi\",\n  \"Numbers\": \"Numaralar\",\n  \"Intelligence\": \"Zeka\",\n  \"Open network portal\": \"Ağ portalını aç\",\n  \"<i>No further instruction provided</i>\": \"<i>Başka talimat verilmedi</i>\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"Dil listede yok veya çeviriler eksik mi?\\nGemini ile çeviriler oluşturmayı seçebilirsiniz.\\n1. Super+A ile sol kenar çubuğunu açın, modeli Gemini olarak ayarlayın (değilse)\\n2. /key yazın, Enter'a basın ve talimatları izleyin\\n3. /key API_ANAHTARINIZ yazın\\n4. Dilinizin yerel ayarını aşağıya yazın ve Oluştur'a basın\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"Yerel kod, örn. tr_TR, fr_FR, de_DE, zh_CN...\",\n  \"Select language\": \"Dil seçin\",\n  \"Generate translation with Gemini\": \"Gemini ile çeviri oluştur\",\n  \"Generating...\\nDon't close this window!\": \"Oluşturuluyor...\\nBu pencereyi kapatmayın!\",\n  \"Generate\\nTypically takes 2 minutes\": \"Oluştur\\nGenellikle 2 dakika sürer\",\n  \"Use system file picker\": \"Sistem dosya seçiciyi kullan\",\n  \"Wallpaper selector\": \"Duvar kağıdı seçici\",\n  \"but force at absolute corner\": \"ancak mutlak köşede zorla\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"Önceki seçenek kapalı ve bu açık olduğunda,\\nkenar çubuğunu açmak için köşenin ucunun üzerine gelebilirsiniz,\\nve kalan alan ses/parlaklık kaydırması için kullanılabilir\",\n  \"Copy path\": \"Yolu kopyala\",\n  \"Windows\": \"Pencereler\",\n  \"Regenerate\": \"Yeniden oluştur\",\n  \"Microphone\": \"Mikrofon\",\n  \"Unmuted\": \"Sessiz değil\",\n  \"System sound\": \"Sistem sesi\",\n  \"Enable now\": \"Şimdi etkinleştir\",\n  \"Night Light\": \"Gece Işığı\",\n  \"Show aim lines\": \"Nişan çizgilerini göster\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"Bu neden havalı:\\n0 olmayan değerler için, yatay kenar boyunca\\nekran köşesine ulaştığınızda tetiklenmez, ancak\\ndikey kenar boyunca ulaştığınızda tetiklenir\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"Lütfen şarj edin!\\nOtomatik askıya alma %1%'de tetikleniyor\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"Örnek kullanım durumu: bir çalışma alanında eroge, diğerinde koyu Discord penceresi\",\n  \"Couldn't recognize music\": \"Müzik tanınamadı\",\n  \"Automatic\": \"Otomatik\",\n  \"Hint target regions\": \"Hedef bölgeleri işaretle\",\n  \"Devices\": \"Cihazlar\",\n  \"Eye protection\": \"Göz koruması\",\n  \"Japanese\": \"Japonca\",\n  \"Layers\": \"Katmanlar\",\n  \"Listening...\": \"Dinleniyor...\",\n  \"LMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"Etkinleştirmek/devre dışı bırakmak için Sol Tık\\nBoyutu değiştirmek için Sağ Tık\\nKonumu değiştirmek için kaydırın\",\n  \"Identify Music\": \"Müziği Tanı\",\n  \"Quick toggles\": \"Hızlı geçişler\",\n  \"Hide sussy/anime wallpapers\": \"Şüpheli/anime duvar kağıtlarını gizle\",\n  \"Android\": \"Android\",\n  \"Show\": \"Göster\",\n  \"Muted\": \"Sessiz\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"Ses girişi | Ses karıştırıcı ve cihaz seçici için sağ tıklayın\",\n  \"Region selector (screen snipping/Google Lens)\": \"Bölge seçici (ekran kırpma/Google Lens)\",\n  \"Total duration timeout (s)\": \"Toplam süre zaman aşımı (s)\",\n  \"Music Recognition\": \"Müzik Tanıma\",\n  \"Night Light | Right-click to configure\": \"Gece Işığı | Yapılandırmak için sağ tıklayın\",\n  \"Anti-flashbang (experimental)\": \"Parlama önleyici (deneysel)\",\n  \"Digital clock settings\": \"Dijital saat ayarları\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Görseller veya ekranın içerme özelliği olan bölümleri olabilir.\\nHer zaman doğru olmayabilir.\\nBu, yerel olarak çalıştırılan bir görüntü işleme algoritmasıyla yapılır ve AI kullanılmaz.\",\n  \"Polling interval (m)\": \"Yoklama aralığı (d)\",\n  \"Inactive\": \"İnaktif\",\n  \"Authentication\": \"Kimlik Doğrulama\",\n  \"Full warning\": \"Tam uyarı\",\n  \"Power Profile\": \"Güç Profili\",\n  \"Content region\": \"İçerik bölgesi\",\n  \"Internet\": \"İnternet\",\n  \"Record\": \"Kaydet\",\n  \"Circle selection\": \"Daire seçimi\",\n  \"Edit quick toggles\": \"Hızlı geçişleri düzenle\",\n  \"Virtual Keyboard\": \"Sanal Klavye\",\n  \"Music Recognized\": \"Müzik Tanındı\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Make sure you have songrec installed\": \"songrec'in kurulu olduğundan emin olun\",\n  \"Dark Mode\": \"Koyu Mod\",\n  \"No device\": \"Cihaz yok\",\n  \"Animate time change\": \"Zaman değişimini canlandır\",\n  \"It may take a few seconds to update\": \"Güncellenme birkaç saniye sürebilir\",\n  \"Polling interval (s)\": \"Yoklama aralığı (s)\",\n  \"Perhaps what you're listening to is too niche\": \"Belki de dinlediğiniz şey çok niş\",\n  \"Fahrenheit unit\": \"Fahrenheit birimi\",\n  \"Sliders\": \"Kaydırıcılar\",\n  \"Roman\": \"Romen\",\n  \"Number style\": \"Numara stili\",\n  \"Intensity\": \"Yoğunluk\",\n  \"Google Lens\": \"Google Lens\",\n  \"Circle\": \"Daire\",\n  \"Hide clipboard images copied from sussy sources\": \"Şüpheli kaynaklardan kopyalanan pano görsellerini gizle\",\n  \"Scroll to Bottom\": \"Alta Kaydır\",\n  \"Enabled\": \"Etkin\",\n  \"Nothing\": \"Hiçbir şey\",\n  \"Audio input\": \"Ses girişi\",\n  \"with vertical offset\": \"dikey ofseti ile\",\n  \"Padding\": \"Dolgu\",\n  \"Please unplug the charger\": \"Lütfen şarj cihazını çıkarın\",\n  \"Show notifications\": \"Bildirimleri göster\",\n  \"Path copied\": \"Yol kopyalandı\",\n  \"On-screen keyboard\": \"Ekran klavyesi\",\n  \"City name\": \"Şehir adı\",\n  \"Click to cycle through power profiles\": \"Güç profilleri arasında geçiş yapmak için tıklayın\",\n  \"Recognize music | Right-click to toggle source\": \"Müziği tanı | Kaynağı değiştirmek için sağ tıklayın\",\n  \"Use old sine wave cookie implementation\": \"Eski sinüs dalgası kurabiye uygulamasını kullan\",\n  \"Rectangular selection\": \"Dikdörtgen seçim\",\n  \"Audio output\": \"Ses çıkışı\",\n  \"Applications\": \"Uygulamalar\",\n  \"Circle to Search\": \"Arama İçin Daire\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"Ses çıkışı | Ses karıştırıcı ve cihaz seçici için sağ tıklayın\",\n  \"Enable GPS based location\": \"GPS tabanlı konumu etkinleştir\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"Önce Gemini API anahtarınızı girmeniz gerekecek.\\nTalimatlar için kenar çubuğunda /key yazın.\",\n  \"Sounds\": \"Sesler\",\n  \"Active\": \"Aktif\",\n  \"Keep awake\": \"Uyanık tut\",\n  \"Auto,\": \"Otomatik,\",\n  \"Normal\": \"Normal\",\n  \"Force hover open at absolute corner\": \"Mutlak köşede üzerine gelinerek açmayı zorla\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"Shell yapılandırma dosyasını açın\\nAlternatif olarak yolu kopyalamak için sağ tıklayın\",\n  \"Recognize music\": \"Müziği tanı\",\n  \"Stroke width\": \"Çizgi genişliği\",\n  \"Use varying shapes for password characters\": \"Şifre karakterleri için değişen şekiller kullan\",\n  \"Battery full\": \"Pil dolu\"\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/uk_UA.json",
    "content": "{\n  \"Mo\": \"Пн/*keep*/\",\n  \"Tu\": \"Вт/*keep*/\",\n  \"We\": \"Ср/*keep*/\",\n  \"Th\": \"Чт/*keep*/\",\n  \"Fr\": \"Пт/*keep*/\",\n  \"Sa\": \"Сб/*keep*/\",\n  \"Su\": \"Нд/*keep*/\",\n  \"%1 characters\": \"%1 символів\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Вартість**: безкоштовно. Політика використання даних залежить від параметрів облікового запису OpenRouter.\\n\\n**Інструкції**: Увійдіть в обліковий запис OpenRouter, перейдіть до розділу «Keys» у верхньому правому меню, натисніть «Create API Key»\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Вартість**: безкоштовно. Дані використовуються для тренування.\\n\\n**Інструкції**: Увійдіть в обліковий запис Google, дозвольте AI Studio створити проект Google Cloud або те, що вона попросить, поверніться назад і натисніть Get API key\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Примітки для Zerochan:\\n- Ви повинні ввести колір\\n- Встановіть ваше ім'я користувача zerochan у параметрі конфігурації `sidebar.booru.zerochan.username`. Якщо ви цього не зробите, вас [може бути заблоковано](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"<i>No further instruction provided</i>\": \"<i>Подальших інструкцій не надано</i>\",\n  \"Action\": \"Дія\",\n  \"Add\": \"Додати\",\n  \"Add task\": \"Додати завдання\",\n  \"All-rounder | Good quality, decent quantity\": \"Універсальний | Хороша якість, пристойна кількість\",\n  \"Allow NSFW\": \"Дозволити NSFW\",\n  \"Allow NSFW content\": \"Дозволити NSFW вміст\",\n  \"Anime\": \"Аніме\",\n  \"Anime boorus\": \"Аніме боору\",\n  \"App\": \"Програма\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Стрілки для навігації, Enter для вибору\\nНатисніть Esc або будь де щоб скасувати\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Brightness\": \"Яскравість\",\n  \"Cancel\": \"Скасувати\",\n  \"Cheat sheet\": \"Шпаргалка\",\n  \"Choose model\": \"Виберіть модель\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Чистий вміст | Відмінна якість, без NSFW\",\n  \"Clear\": \"Очистити\",\n  \"Clear chat history\": \"Очистити історію чату\",\n  \"Clear the current list of images\": \"Очистити поточний список картинок\",\n  \"Close\": \"Закрити\",\n  \"Copy\": \"Копіювати\",\n  \"Copy code\": \"Копіювати код\",\n  \"Delete\": \"Видалити\",\n  \"Desktop\": \"Стільниця\",\n  \"Disable NSFW content\": \"Вимкнути NSFW вміст\",\n  \"Done\": \"Готово\",\n  \"Download\": \"Завантажити\",\n  \"Edit\": \"Редагувати\",\n  \"Enter text to translate...\": \"Введіть текст щоб перекласти...\",\n  \"Finished tasks will go here\": \"Завершені завдання зберігаються тут\",\n  \"For desktop wallpapers | Good quality\": \"На шпалери | Хороша якість\",\n  \"For storing API keys and other sensitive information\": \"Для зберігання API ключів та іншої конфіденційної інформація\",\n  \"Game mode\": \"Ігровий режим\",\n  \"Get the next page of results\": \"Наступна сторінка результатів\",\n  \"Hibernate\": \"Сплячий режим\",\n  \"Input\": \"Ввід\",\n  \"Intelligence\": \"Інтелект\",\n  \"Interface\": \"Інтерфейс\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Неправильні аргументи. Повинні бути вказані `key` та `value`.\",\n  \"Jump to current month\": \"Перейти до поточного місяця\",\n  \"Keep system awake\": \"Тримати систему в режимі очікування\",\n  \"Large images | God tier quality, no NSFW.\": \"Великі зображення | Божественна якість, без NSFW.\",\n  \"Large language models\": \"Великі мовні моделі\",\n  \"Launch\": \"Пуск\",\n  \"Lock\": \"Блокувати\",\n  \"Logout\": \"Вийти\",\n  \"Markdown test\": \"Тест Markdown\",\n  \"Math result\": \"Результат обчислень\",\n  \"No audio source\": \"Немає джерела аудіо\",\n  \"No media\": \"Немає медіа\",\n  \"No notifications\": \"Немає сповіщень\",\n  \"Not visible to model\": \"Не видно для моделі\",\n  \"Nothing here!\": \"Тут нічого!\",\n  \"Notifications\": \"Сповіщення\",\n  \"OK\": \"Гаразд\",\n  \"Open file link\": \"Відкрити лінк до файлу\",\n  \"Output\": \"Вивід\",\n  \"Reboot\": \"Перезапуск\",\n  \"Reboot to firmware settings\": \"Перезапуск в параметри UEFI/BIOS\",\n  \"Reload Hyprland & Quickshell\": \"Перезавантажити Hyprland та Quickshell\",\n  \"Run\": \"Виконати\",\n  \"Run command\": \"Виконати команду\",\n  \"Save\": \"Зберегти\",\n  \"Save to Downloads\": \"Зберегти в Завантаження\",\n  \"Search\": \"Пошук\",\n  \"Search the web\": \"Шукати в інтернеті\",\n  \"Search, calculate or run\": \"Шукати, обчислювати або запускати\",\n  \"Select Language\": \"Вибрати мову\",\n  \"Session\": \"Сесія\",\n  \"Set API key\": \"Вказати ключ API\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Задати температуру (випадковість) моделі. Значення в діапазоні від 0 до 2 для Gemini, від 0 до 1 для інших моделей. Значення за замовчуванням 0.5.\",\n  \"Set the current API provider\": \"Встановити поточного провайдера API\",\n  \"Shutdown\": \"Вимкнути\",\n  \"Silent\": \"Тиша\",\n  \"Sleep\": \"Сон\",\n  \"System\": \"Система\",\n  \"Task Manager\": \"Менеджер завдань\",\n  \"Task description\": \"Опис завдання\",\n  \"Temperature must be between 0 and 2\": \"Температура має бути між 0 та 2\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Хентай | Велика кількість, багато NSFW, якість сильно варіюється\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Популярний | Найкраща кількість, але якість може сильно варіюватись\",\n  \"Thinking\": \"Замислився\",\n  \"Translation goes here...\": \"Переклад буде тут...\",\n  \"Translator\": \"Перекладач\",\n  \"Unfinished\": \"Незавершений\",\n  \"Unknown\": \"Невідмий\",\n  \"Unknown Album\": \"Невідомий Альбои\",\n  \"Unknown Artist\": \"Невідомий Виконавець\",\n  \"Unknown Title\": \"Невідома назва\",\n  \"View Markdown source\": \"Дивитися джерело Markdown\",\n  \"Volume\": \"Гучність\",\n  \"Volume mixer\": \"Мікшер гучності\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Лише вайфу | Відмінна якість, обмежена кількість\",\n  \"Waiting for response...\": \"Чекаємо відповідь...\",\n  \"Workspace\": \"Простір\",\n  \"Invalid API provider. Supported: \\n-\": \"Неправильний провайдер API. Підтримується: \\n-\",\n  \"Unknown command:\": \"Невідома команда:\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Введіть /key, щоб почати роботу з онлайн моделями\\nCtrl+O, щоб розгорнути бічну панель\\nCtrl+P, щоб прибрати бічну панель у вікно\",\n  \"Provider set to\": \"Провайдер виставлений на\",\n  \"Invalid model. Supported: \\n```\": \"Неправельна модель. Підтримується: \\n```\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Це не спрацювало. Поради:\\n- Перевірте свої теги та параметри NSFW\\n- Якщо ви не знаєте тега, введіть номер сторінки\",\n  \"Switched to search mode. Continue with the user's request.\": \"Перейшли в режим пошуку. Продовження пошуку за запитом користувача.\",\n  \"Settings\": \"Параметри\",\n  \"Save chat\": \"Зберегти чат\",\n  \"Load chat\": \"Завантажити чат\",\n  \"or\": \"або\",\n  \"Set the system prompt for the model.\": \"Встановіть системний запит для моделі.\",\n  \"To Do\": \"Зробити\",\n  \"Calendar\": \"Календар\",\n  \"Advanced\": \"Розширені\",\n  \"About\": \"Про\",\n  \"Services\": \"Сервіси\",\n  \"Style\": \"Стиль\",\n  \"Edit config\": \"Редагувати конфігурацію\",\n  \"Colors & Wallpaper\": \"Кольори та Шпалери\",\n  \"Light\": \"Світла\",\n  \"Dark\": \"Темна\",\n  \"Material palette\": \"Палітра кольорів\",\n  \"Fidelity\": \"Вірність\",\n  \"Fruit Salad\": \"Фруктовий салат\",\n  \"Alternatively use /dark, /light, /img in the launcher\": \"Або використовуйте /dark, /light, /img у лаунчері\",\n  \"Fake screen rounding\": \"Фальшиві заокруглення екрану\",\n  \"When not fullscreen\": \"Коли не на весь екран\",\n  \"Choose file\": \"Вибрати файл\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Випадкові SFW аніме шпалери від Konachan\\nЗображення збережено до ~/Pictures/Wallpapers\",\n  \"Be patient...\": \"Потерпіть...\",\n  \"Decorations & Effects\": \"Декорації та ефекти\",\n  \"Tonal Spot\": \"Тональна пляма\",\n  \"Shell windows\": \"Вікна оболонки\",\n  \"Auto\": \"Авто\",\n  \"Wallpaper\": \"Шпалери\",\n  \"Content\": \"Вміст\",\n  \"Title bar\": \"Заголовок\",\n  \"Transparency\": \"Прозорість\",\n  \"Expressive\": \"Виразний\",\n  \"Yes\": \"Так\",\n  \"Enable\": \"Увімкнути\",\n  \"Rainbow\": \"Веселка\",\n  \"Might look ass. Unsupported.\": \"Виглядатиме дурновато. Не підтримується.\",\n  \"Monochrome\": \"Монохромний\",\n  \"Random: Konachan\": \"Випадково: Konachan\",\n  \"Center title\": \"Назва по центру\",\n  \"Neutral\": \"Нейтральний\",\n  \"Pick wallpaper image on your system\": \"Виберіть зображення шпалер на вашому комп'ютері\",\n  \"No\": \"Ні\",\n  \"AI\": \"ШІ\",\n  \"Local only\": \"Лише локально\",\n  \"Policies\": \"Політика\",\n  \"Weeb\": \"Віабу\",\n  \"Closet\": \"Шафа\",\n  \"Bar style\": \"Стиль заголовку\",\n  \"Show next time\": \"Показати пізніше\",\n  \"Usage\": \"Використання\",\n  \"Plain rectangle\": \"Звичайний квадрат\",\n  \"Useless buttons\": \"Безкорисні кнопки\",\n  \"GitHub\": \"GitHub\",\n  \"Style & wallpaper\": \"Стиль та шпалери\",\n  \"Configuration\": \"Конфігурація\",\n  \"Change any time later with /dark, /light, /img in the launcher\": \"Змініть будь-коли пізніше за допомогою /dark, /light, /img у лаунчері\",\n  \"Keybinds\": \"Комбінації клавіш\",\n  \"Float\": \"Плаваюче\",\n  \"Hug\": \"Обійми\",\n  \"Yooooo hi there\": \"Йоооо, привіт\",\n  \"illogical-impulse Welcome\": \"illogical-impulse Вітаємо\",\n  \"Info\": \"Інфо\",\n  \"Volume limit\": \"Обмеження гучності\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Запобігає різкому збільшенню та обмежує ліміт гучності\",\n  \"Resources\": \"Ресурси\",\n  \"12h am/pm\": \"12г AM/PM\",\n  \"Base URL\": \"Базовий лінк\",\n  \"Audio\": \"Аудіо\",\n  \"Networking\": \"Мережування\",\n  \"Format\": \"Формат\",\n  \"Time\": \"Час\",\n  \"Battery\": \"Батарея\",\n  \"Prefixes\": \"Префікси\",\n  \"Emojis\": \"Емодзі\",\n  \"Earbang protection\": \"Захист навушників\",\n  \"Automatically suspends the system when battery is low\": \"Автоматично призупиняє роботу системи, коли батарея розряджається\",\n  \"Automatic suspend\": \"Автоматична зупинка\",\n  \"Suspend at\": \"Зупиняти на\",\n  \"Max allowed increase\": \"Максимально допустимий приріст\",\n  \"Web search\": \"Пошук в інтернеті\",\n  \"Polling interval (ms)\": \"Інтервал між опитуваннями (мс)\",\n  \"Clipboard\": \"Буфер обміну\",\n  \"Low warning\": \"Незначні попередження\",\n  \"24h\": \"24г\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Використовуйте алгоритм на основі відстані Левенштейна замість нечіткого\",\n  \"System prompt\": \"Системний запит\",\n  \"12h AM/PM\": \"12г AM/PM\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Було б краще, якби ви зробили купу помилок,\\nале результати можуть бути дивними і не працювати з абревіатурами\\n(наприклад, «GIMP» може не вивести програму для малювання).\",\n  \"Critical warning\": \"Критичні попередження\",\n  \"User agent (for services that require it)\": \"User agent (для сервісів де це потрібно)\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Такими областями можуть бути зображення або частини екрана, які мають певні обмеження.\\nМоже бути не завжди точним.\\nЦе робиться за допомогою алгоритму обробки зображень, запущеного локально, і не використовується ШІ.\",\n  \"Note: turning off can hurt readability\": \"Примітка: вимкнення може погіршити читабельність\",\n  \"Workspaces shown\": \"Показані простори\",\n  \"Dark/Light toggle\": \"Перемикач Світлої/Темної\",\n  \"Dock\": \"Лоток\",\n  \"Weather\": \"Погода\",\n  \"Pinned on startup\": \"Прикріплено до запуску\",\n  \"Tip: Hide icons and always show numbers for\\nthe classic illogical-impulse experience\": \"Порада: приховуйте іконки і завжди показуйте цифри для класичного досвіду illogical-impulse\",\n  \"Appearance\": \"Вигляд\",\n  \"Always show numbers\": \"Завжди показувати номери\",\n  \"Buttons\": \"Кнопки\",\n  \"Keyboard toggle\": \"Перемикання клавіатури\",\n  \"Scale (%)\": \"Розмір (%)\",\n  \"Overview\": \"Огляд\",\n  \"Rows\": \"Рядки\",\n  \"Borderless\": \"Без меж\",\n  \"Screenshot tool\": \"Інструмент створення скріншотів\",\n  \"Number show delay when pressing Super (ms)\": \"Затримка відображення номера при натисканні клавіші Super (мс)\",\n  \"Timeout (ms)\": \"Тайм-аут (ms)\",\n  \"Show app icons\": \"Показувати іконки програм\",\n  \"Workspaces\": \"Простори\",\n  \"Columns\": \"Стовбці\",\n  \"On-screen display\": \"Екранний дисплей\",\n  \"Screen snip\": \"Скріншот\",\n  \"Mic toggle\": \"Перемикач мікрофону\",\n  \"Hover to reveal\": \"Наведіть курсор, щоб відкрити\",\n  \"Bar\": \"Панель\",\n  \"Show background\": \"Показувати задній фон\",\n  \"Show regions of potential interest\": \"Показати регіони потенційного інтересу\",\n  \"Color picker\": \"Вибір кольору\",\n  \"Help & Support\": \"Допомога та підтримка\",\n  \"Discussions\": \"Дискусії\",\n  \"Color generation\": \"Генератор кольорів\",\n  \"Dotfiles\": \"Дотфайли\",\n  \"Distro\": \"Дистрибютив\",\n  \"Privacy Policy\": \"Політика Конфіденційності\",\n  \"Documentation\": \"Документація\",\n  \"Shell & utilities theming must also be enabled\": \"Тему оболонки та утиліт також слід увімкнути\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Donate\": \"Донат\",\n  \"Terminal\": \"Термінал\",\n  \"Shell & utilities\": \"Оболонка та утиліти\",\n  \"Qt apps\": \"Програми Qt\",\n  \"Report a Bug\": \"Повідомити про помилку\",\n  \"Issues\": \"Проблеми\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Перетягніть або клацніть регіон - LMB: Копіювати - RMB: Редагувати\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Поточна модель: %1\\nЗадати її за допомогою %2model МОДЕЛЬ\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Написати моделі... «%1» для команд\",\n  \"No API key set for %1\": \"Немає вказаного API ключач для %1\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Завантажився наступний системний запит\\n\\n---\\n\\n%1\",\n  \"%1 | Right-click to configure\": \"%1 | ПКМ для налаштування\",\n  \"API key set for %1\": \"ключ API вказано для %1\",\n  \"Online via %1 | %2's model\": \"Онлайн через %1 | %2's модель\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Поточна кінцева точка API: %1\\nВстановіть її за допомогою %2mode ПРОВАЙТЕР\",\n  \"Go to source (%1)\": \"До джерела (%1)\",\n  \"Temperature set to %1\": \"Температура виставлена на %1\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Напишіть тег або \\\"%1\\\" для команд\",\n  \"%1 queries pending\": \"%1 запитів на розгляді\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"ключ API:\\n\\n```txt\\n%1\\n```\",\n  \"Uptime: %1\": \"Активно: %1\",\n  \"%1 Safe Storage\": \"%1 Надійне зберігання\",\n  \"%1 does not require an API key\": \"%1 не потребує API ключа\",\n  \"Temperature: %1\": \"температура: %1\",\n  \"Model set to %1\": \"Модель встановлена на %1\",\n  \"Page %1\": \"Сторінка %1\",\n  \"Local Ollama model | %1\": \"Локальна модель Оллама | %1\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Поточний запит системи\\n\\n---\\n\\n%1\",\n  \"Unknown function call: %1\": \"Невідомий виклик функції: %1\",\n  \"%1 notifications\": \"%1 сповіщень\",\n  \"Load chat from %1\": \"Завантажено чат з %1\",\n  \"Load prompt from %1\": \"Завантажено запит з %1\",\n  \"Save chat to %1\": \"Зберегти чат до %1\",\n  \"Weather Service\": \"Сервіс погоди\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Не вдається знайти службу GPS. Замість цього використовується резервний метод.\",\n  \"Critically low battery\": \"Критичний рівень заряду батареї\",\n  \"Select output device\": \"Виберіть вихідний пристрій\",\n  \"Code saved to file\": \"Код збережений у файл\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Онлайн-моделі заборонено\\n\\nКерується параметром конфігурації `policies.ai`\",\n  \"Scroll to change volume\": \"Прокрутіть, щоб змінити гучність\",\n  \"Elements\": \"Елементи\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 завдань\",\n  \"Download complete\": \"Завантаження завершено\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Будь ласка, зарядіть! \\nАвтоматичне призупинення спрацьовування при %1\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Scroll to change brightness\": \"Прокрутіть щоб змінити яскравість\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Помилка зєднання. Перевірте самостійно за допомогою команди <tt>warp-cli</tt>\",\n  \"Select input device\": \"Виберіть вхідний пристрій\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Помилка реєстрації. Перевірте самостійно за допомогою команди <tt>warp-cli</tt>\",\n  \"Consider plugging in your device\": \"Подумайте про те, щоб підключити свій пристрій\",\n  \"Low battery\": \"Низький заряд батареї\",\n  \"Saved to %1\": \"Збережено до %1\",\n  \"Sunset\": \"Захід сонця\",\n  \"UV Index\": \"Індекс UV\",\n  \"Humidity\": \"Вологість\",\n  \"Wind\": \"Вітер\",\n  \"Sunrise\": \"Світанок\",\n  \"Pressure\": \"Тиск\",\n  \"Visibility\": \"Видимість\",\n  \"Precipitation\": \"Опади\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Немає API ключа\\nВстановіть його можна командою /key YOUR_API_KEY\",\n  \"Your package manager is running\": \"Ваш пакетний менеджер запущено\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Нічне світло | ПКМ щоб увімкнути автоматичний режим\",\n  \"Gives the model search capabilities (immediately)\": \"Надає можливість пошуку моделі (негайно)\",\n  \"Depends on workspace\": \"Залежно від простору\",\n  \"Invalid arguments. Must provide `command`.\": \"Неправельні параметри. Потрібно вказати `command`.\",\n  \"Temperature\\nChange with /temp VALUE\": \"Температура\\nЗмінити можна командою /temp ЗНАЧЕННЯ\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"Онлайн | Модель Google\\nСучасна багатоцільова модель Google, яка чудово справляється з кодуванням і складними завданнями на міркування.\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | ПКМ щоб налаштувати\",\n  \"Thought\": \"Думка\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Онлайн | Модель Google \\nМодель Gemini 2.5 Flash оптимізована для економічної ефективності та високої пропускної здатності.\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API ключ встановлено\\nЗмінити можна командою /key YOUR_API_KEY\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Поточний інструмент: %1\\nВстановіть його командою %2tool TOOL\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**Інструкції**: Увійдіть в обліковий запис Mistral, перейдіть до розділу «Keys» на бічній панелі, натисніть «Create new key»\",\n  \"Usage: %1tool TOOL_NAME\": \"Використання: %1tool TOOL_NAME\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Онлайн | модель Google \\nШвидкий, може здійснювати пошук актуальної інформації\",\n  \"Approve\": \"Схвалити\",\n  \"Preferred wallpaper zoom (%)\": \"Бажаний маштаб шпалер (%)\",\n  \"Performance Profile toggle\": \"Перемикач профілю продуктивності\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Загальна кількість токенів\\nВхідні: %1\\nВихідні: %2\",\n  \"Wallpaper parallax\": \"Обємні шпалери\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Неправельний інструмент. Підтримуються:\\n- %1\",\n  \"Usage: %1load CHAT_NAME\": \"Виокристання: %1load CHAT_NAME\",\n  \"Reject\": \"Відхилити\",\n  \"Usage: %1save CHAT_NAME\": \"Використання: %1save CHAT_NAME\",\n  \"Set the tool to use for the model.\": \"Вкажіть інструмент для роботи з моделью\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"Онлайн | %1's модель | Надає швидкі, чуйні та добре відформатовані відповіді. Недоліки: не дуже охоче виконує завдання; може вигадувати виклики невідомих функцій\",\n  \"Depends on sidebars\": \"Залежно від бокових панелей\",\n  \"Command rejected by user\": \"Команда відхилена користувачем\",\n  \"There might be a download in progress\": \"Можливо, триває завантаження\",\n  \"Disable tools\": \"Вимкнути інструмент\",\n  \"Tool set to: %1\": \"Інструмет вказано: %1\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"Команди, редагування конфігурацій, пошук.\\nВиконує додаткову дію для переходу в режим пошуку, якщо це потрібно\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Щоб задати ключ API, передайте його командою %4\\n\\nЩоб переглянути ключ, передайте \\\"get\\\" командою<br/>\\n\\n### Для %1:\\n\\n**Лінк**: %2\\n\\n%3\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Онлайн | Модель Google\\nНовіша модель, яка повільніша за свою попередницю, але має надавати якісніші відповіді\"\n}\n"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/vi_VN.json",
    "content": "{\n  \"Unknown function call: %1\": \"Hàm không xác định: %1\",\n  \"Show next time\": \"Hiển thị lần sau\",\n  \"Fidelity\": \"Khá giống gốc\",\n  \"Open file link\": \"Mở liên kết tệp\",\n  \"Interrupts possibility of overview being toggled on release.\": \"Ngăn mở overview khi nhả nút.\",\n  \"No audio source\": \"Không có nguồn âm thanh\",\n  \"Might look ass. Unsupported.\": \"Có thể rất tệ. Không được hỗ trợ.\",\n  \"Jump to current month\": \"Nhảy đến tháng hiện tại\",\n  \"Delete\": \"Xóa\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**Giá**: miễn phí. Dữ liệu được sử dụng cho mục đích huấn luyện. **Hướng dẫn**: Đăng nhập vào tài khoản Google, cho phép AI Studio tạo dự án Google Cloud gì đó, quay lại rồi ấn Get API key\",\n  \"Rainbow\": \"Cầu vồng\",\n  \"%1 does not require an API key\": \"%1 không cần API key\",\n  \"Choose model\": \"Chọn model\",\n  \"Prevents abrupt increments and restricts volume limit\": \"Chặn thay đổi đột ngột và giới hạn âm lượng\",\n  \"%1 characters\": \"%1 ký tự\",\n  \"Change any time later with /dark, /light, /img in the launcher\": \"Thay đổi bất cứ lúc nào sau này với /dark, /light, /img trong launcher\",\n  \"Tonal Spot\": \"Tonal Spot\",\n  \"Neutral\": \"Trung tính\",\n  \"To Do\": \"Cần làm\",\n  \"Auto\": \"Tự động\",\n  \"Polling interval (ms)\": \"Thời gian lặp lại (ms)\",\n  \"Center title\": \"Căn giữa tiêu đề\",\n  \"Lock\": \"Khóa màn hình\",\n  \"Screen snip\": \"Chụp màn hình (chọn vùng)\",\n  \"User agent (for services that require it)\": \"User agent (nếu cần)\",\n  \"Report a Bug\": \"Báo lỗi\",\n  \"Shutdown\": \"Tắt máy\",\n  \"Keyboard toggle\": \"Mở/đóng bàn phím ảo\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"Cái nhiều hentai nhất | Số lượng rất tốt, rất nhiều NSFW, chất lượng có thể khác nhau nhiều\",\n  \"Download\": \"Tải xuống\",\n  \"Note: turning off can hurt readability\": \"Ghi chú: nếu tắt có thể khó đọc\",\n  \"Local Ollama model | %1\": \"Model Ollama trên máy | %1\",\n  \"Silent\": \"Im lặng\",\n  \"Columns\": \"Số cột\",\n  \"Set with /mode PROVIDER\": \"Set with /mode PROVIDER\",\n  \"Issues\": \"Các vấn đề\",\n  \"Policies\": \"Chính sách\",\n  \"Load chat from %1\": \"Tải trò chuyện từ %1\",\n  \"Unknown Album\": \"Album không xác định\",\n  \"Yes\": \"Có\",\n  \"Battery\": \"Pin\",\n  \"Material palette\": \"Kiểu material\",\n  \"Chain of Thought\": \"Dòng suy nghĩ\",\n  \"This is necessary because GlobalShortcut.onReleased in quickshell triggers whether or not you press something else while holding the key.\": \"Cần cái này vì GlobalShortcut.onReleased cho một phím của Quickshell được kích hoạt kể cả khi ban ân phím khác trước khi thả phím đó.\",\n  \"Low warning\": \"Cảnh báo thấp\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \". Với Zerochan:\\n- Hãy nhập tên một màu (bằng tiếng Anh)\\n- Đặt username Zerochan trong tùy chọn `sidebar.booru.zerochan.username`. Bạn [có thể bị ban nếu không tuân thủ](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\",\n  \"Brightness\": \"Độ sáng\",\n  \"Yooooo hi there\": \"Yooooo chào bạn\",\n  \"Colors & Wallpaper\": \"Màu sắc & Hình nền\",\n  \"No media\": \"Không có media\",\n  \"Critical warning\": \"Cảnh báo rất thấp\",\n  \"Mic toggle\": \"Bật/tắt mic\",\n  \"12h AM/PM\": \"12h AM/PM\",\n  \"Large language models\": \"Mô hình ngôn ngữ lớn\",\n  \"Markdown test\": \"Test markdown\",\n  \"Temperature: %1\": \"Nhiệt độ: %1\",\n  \"Edit\": \"Sửa\",\n  \"Waifus only | Excellent quality, limited quantity\": \"Chỉ waifus | Chất lượng xuất sắc, số lượng hạn chế\",\n  \"Cheat sheet\": \"Bảng tra cứu\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"Model đang chọn: %1\\nChọn với lệnh %2model MODEL\",\n  \"Provider set to\": \"Đã đặt nhà cung cấp thành\",\n  \"Clear\": \"Xóa hết\",\n  \"GitHub\": \"GitHub\",\n  \"App\": \"Ứng dụng\",\n  \"Title bar\": \"Thanh tiêu đề\",\n  \"Web search\": \"Tìm kiếm web\",\n  \"Invalid model. Supported: \\n```\": \"Model không hợp lệ. Các lựa chọn: \\n```\",\n  \"Calendar\": \"Lịch\",\n  \"Done\": \"Đã xong\",\n  \"Monochrome\": \"Đen trắng\",\n  \"Show regions of potential interest\": \"Hiển thị vùng thông minh\",\n  \"Dark/Light toggle\": \"Chuyển chế độ sáng/tối\",\n  \"Unknown command:\": \"Lệnh không xác định:\",\n  \"Allow NSFW content\": \"Cho phép nội dung NSFW\",\n  \"Closes cheatsheet on press\": \"Đóng bảng tra cứu khi ấn\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"Chỉnh giá trị nhiệt độ (sự ngẫu nhiên) của model. Giá trị 0-2 với Gemini, 0-1 với các model khác. Mặc định là 0.5.\",\n  \"Invalid API provider. Supported: \\n-\": \"Nhà cung cấp API không hợp lệ. Các lựa chọn: \\n-\",\n  \"Shell windows\": \"Cửa sổ của shell\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"Đã tải chỉ dẫn hệ thống sau đây\\n\\n---\\n\\n%1\",\n  \"Clipboard\": \"Clipboard\",\n  \"For storing API keys and other sensitive information\": \"Để lưu trữ API key và các thông tin nhạy cảm khác\",\n  \"Wallpaper\": \"Hình nền\",\n  \"Decorations & Effects\": \"Trang trí & Hiệu ứng\",\n  \"AI\": \"AI\",\n  \"Large images | God tier quality, no NSFW.\": \"Ảnh kích thước lớn | Chất lượng cực tốt, không có NSFW.\",\n  \"When not fullscreen\": \"Khi không toàn màn hình\",\n  \"Resources\": \"Tài nguyên\",\n  \"Light\": \"Sáng\",\n  \"Weeb\": \"Wibu\",\n  \"Disable NSFW content\": \"Tắt nội dung NSFW\",\n  \"OK\": \"OK\",\n  \"Screenshot tool\": \"Công cụ chụp màn hình\",\n  \"Enable\": \"Bật\",\n  \"Select Language\": \"Chọn ngôn ngữ\",\n  \"System\": \"Hệ thống\",\n  \"Emojis\": \"Emoji\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"Chỉ dẫn hệ thống hiện tại như sau\\n\\n---\\n\\n%1\",\n  \"Translator\": \"Dịch\",\n  \"Sleep\": \"Ngủ\",\n  \"Action\": \"Hành động\",\n  \"Audio\": \"Âm thanh\",\n  \"Show background\": \"Hiện nền\",\n  \"All-rounder | Good quality, decent quantity\": \"Tốt đều | Chất lượng tốt, số lượng ổn\",\n  \"Documentation\": \"Tài liệu\",\n  \"Terminal\": \"Terminal\",\n  \"Distro\": \"Distro\",\n  \"Clear chat history\": \"Xóa lịch sử trò chuyện\",\n  \"Float\": \"Nổi\",\n  \"<i>No further instruction provided</i>\": \"<i>No further instruction provided</i>\",\n  \"Choose file\": \"Chọn tệp\",\n  \"Set the system prompt for the model.\": \"Đặt chỉ dẫn hệ thống cho model.\",\n  \"Unknown Title\": \"Bài hát không rõ tên\",\n  \"Math result\": \"Kết quả phép tính\",\n  \"Logout\": \"Đăng xuất\",\n  \"Privacy Policy\": \"Chính sách quyền riêng tư\",\n  \"Style\": \"Phong cách\",\n  \"Borderless\": \"Không viền\",\n  \"Set API key\": \"Đặt API key\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"Sạch sẽ | Chất lượng xuất sắc, không có NSFW\",\n  \"Experimental | Online | Google's model\\nCan do a little more but doesn't search quickly\": \"Thử nghiệm | Trực tuyến | Model của Google\\nCó thể làm nhiều hơn một chút nhưng không tìm kiếm nhanh chóng\",\n  \"Toggles cheatsheet on press\": \"Mở/đóng bảng tra cứu khi ấn\",\n  \"Thinking\": \"Đang nghĩ\",\n  \"Earbang protection\": \"Bảo vệ tai\",\n  \"Advanced\": \"Nâng cao\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"Có thể tốt hơn nếu bạn gõ lệch phím nhiều,\\nnhưng kết quả có thể hơi lạ và không hoạt động tốt với từ viết tắt\\n(ví dụ tìm \\\"GIMP\\\" có thể không ra cái chương trình vẽ)\",\n  \"Shell & utilities theming must also be enabled\": \"Cần Shell & công cụ cũng bật\",\n  \"Desktop\": \"Màn hình chính\",\n  \"Anime\": \"Anime\",\n  \"Qt apps\": \"Các ứng dụng Qt\",\n  \"Style & wallpaper\": \"Phong cách & hình nền\",\n  \"Finished tasks will go here\": \"Việc đã xong sẽ hiện ở đây\",\n  \"Weather\": \"Thời tiết\",\n  \"Settings\": \"Cài đặt\",\n  \"Shell & utilities\": \"Shell & tiện ích\",\n  \"Unfinished\": \"Chưa xong\",\n  \"Random: Konachan\": \"Ngẫu nhiên: Konachan\",\n  \"Pick wallpaper image on your system\": \"Chọn hình nền trên máy\",\n  \"Volume\": \"Âm lượng\",\n  \"Add\": \"Thêm\",\n  \"Hibernate\": \"Ngủ đông\",\n  \"Run\": \"Chạy\",\n  \"Keep system awake\": \"Giữ hệ thống bật\",\n  \"To make sure this works consistently, use binditn = MODKEYS, catchall in an automatically triggered submap that includes everything.\": \"Để đảm bảo luôn hoạt động, dùng binditn = MODKEYS, catchall trong một submap luôn được kích hoạt bao trùm mọi thứ.\",\n  \"Plain rectangle\": \"Hình chữ nhật\",\n  \"%1 queries pending\": \"%1 lệnh gọi đang chờ\",\n  \"Temperature set to %1\": \"Nhiệt độ đã được đặt thành %1\",\n  \"Notifications\": \"Thông báo\",\n  \"System prompt\": \"Chỉ dẫn hệ thống\",\n  \"Hover to reveal\": \"Đặt chuột vào để hiện\",\n  \"No\": \"Không\",\n  \"Bar\": \"Bar\",\n  \"Search the web\": \"Tìm kiếm web\",\n  \"Page %1\": \"Trang %1\",\n  \"Reboot\": \"Khởi động lại\",\n  \"Such regions could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"Các vùng có thể là hình ảnh hoặc phần của màn hình cớ vẻ được bao chứa.\\nKhông luôn chính xác.\\nSử dụng một thuật toán xử lý ảnh chạy trên máy, không dùng AI.\",\n  \"Show app icons\": \"Hiện biểu tượng ứng dụng\",\n  \"Closet\": \"Nghiện mà ngại\",\n  \"Set the current API provider\": \"Đặt nguồn cung cấp API\",\n  \"Cancel\": \"Hủy\",\n  \"Networking\": \"Mạng\",\n  \"Overview\": \"Overview\",\n  \"Search, calculate or run\": \"Tìm, tính hoặc chạy\",\n  \"Useless buttons\": \"Mấy nút vô dụng\",\n  \"Transparency\": \"Sự trong suốt\",\n  \"Temperature must be between 0 and 2\": \"Nhiệt độ phải trong khoảng từ 0 đến 2\",\n  \"Automatically suspends the system when battery is low\": \"Tự động ngủ khi pin thấp\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"Endpoint API hiện tại: %1\\nĐặt với lệnh %2mode PROVIDER\",\n  \"Services\": \"Các dịch vụ\",\n  \"Reload Hyprland & Quickshell\": \"Tải lại Hyprland & Quickshell\",\n  \"Automatic suspend\": \"Tự động ngủ\",\n  \"illogical-impulse Welcome\": \"illogical-impulse - Xin chào\",\n  \"Interface\": \"Giao diện\",\n  \"Load chat\": \"Tải cuộc trò chuyện\",\n  \"Number show delay when pressing Super (ms)\": \"Thời gian chờ hiện số khi nhấn Super (ms)\",\n  \"Clear the current list of images\": \"Xóa danh sách hình ảnh hiện tại\",\n  \"Fake screen rounding\": \"Giả bo tròn màn hình\",\n  \"Tip: Hide icons and always show numbers for\\nthe classic illogical-impulse experience\": \"Mẹo: Ẩn biểu tượng và luôn hiển thị số nếu\\nmuốn giống trải nghiệm illogical-impulse gốc\",\n  \"Launch\": \"Chạy\",\n  \"%1 notifications\": \"%1 thông báo\",\n  \"%1 | Right-click to configure\": \"%1 | Ấn chuột phải để chỉnh\",\n  \"Unknown Artist\": \"Nghệ sĩ không xác định\",\n  \"Appearance\": \"Giao diện\",\n  \"Task Manager\": \"Quản lí ứng dụng đang chạy\",\n  \"To set an API key, pass it with the command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"Để đặt API key, viết nó sau lệnh\\n\\nĐể xem lại, viết \\\"get\\\" sau lệnh<br/>\\n\\n### Với %1:\\n\\n**Link**: %2\\n\\n%3\",\n  \"Opens cheatsheet on press\": \"Mở bảng tra cứu khi ấn\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"Biến không hợp lệ. cần cả `key` và `value`.\",\n  \"About\": \"Giới thiệu\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Help & Support\": \"Trợ giúp\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"Nhập tag hoặc \\\"%1\\\" để xem các lệnh\",\n  \"Format\": \"Định dạng\",\n  \"Content\": \"Giống gốc\",\n  \"Edit config\": \"Sửa config\",\n  \"Bluetooth\": \"Bluetooth\",\n  \"Be patient...\": \"Bình tĩnh...\",\n  \"Discussions\": \"Thảo luận\",\n  \"Anime boorus\": \"Các booru anime\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"Quả này không được. Mẹo:\\n- Kiểm tra tag và cài đặt NSFW\\n- Nếu không nghĩ ra tag nào có thể nhập số trang\",\n  \"Task description\": \"Mô tả công việc\",\n  \"Max allowed increase\": \"Thay đổi tối đa\",\n  \"Rows\": \"Số hàng\",\n  \"Switched to search mode. Continue with the user's request.\": \"Đã chuyển sang chế độ tìm kiếm. Tiếp tục với yêu cầu của người dùng.\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"Sử dụng thuật toán dùng khoảng cách Levenshtein thay vì fuzzy\",\n  \"Copy\": \"Sao chép\",\n  \"12h am/pm\": \"12h am/pm\",\n  \"Unknown\": \"Không xác định\",\n  \"Waiting for response...\": \"Đang chờ phản hồi...\",\n  \"Workspace\": \"Workspace\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"Hình nền Anime SFW ngẫu nhiên từ Konachan\\nẢnh được lưu vào ~/Pictures/Wallpapers\",\n  \"Online via %1 | %2's model\": \"Trực tuyến qua %1 | Model của %2\",\n  \"Always show numbers\": \"Luôn hiện số\",\n  \"or\": \"hoặc\",\n  \"Drag or click a region • LMB: Copy • RMB: Edit\": \"Kéo thả hoặc chọn vùng • Chuột trái: Sao chép • Chuột phải: Chỉnh sửa\",\n  \"Local only\": \"Chỉ trên máy\",\n  \"Donate\": \"Ủng hộ\",\n  \"Online | Google's model\\nGives up-to-date information with search.\": \"Trực tuyến | Model của Google\\nCó thể tìm kiếm để cung cấp thông tin cập nhật.\",\n  \"Run command\": \"Chạy lệnh\",\n  \"Dotfiles\": \"Dotfiles\",\n  \"Volume limit\": \"Giới hạn âm lượng\",\n  \"On-screen display\": \"Âm lượng/độ sáng\",\n  \"Reboot to firmware settings\": \"Khởi động lại vào cài đặt firmware\",\n  \"Workspaces shown\": \"Số workspace hiển thị\",\n  \"Save\": \"Lưu\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"Phổ biến | Số lượng tốt nhất, nhưng chất lượng không biết đâu vào đâu\",\n  \"Save chat\": \"Lưu cuộc trò chuyện\",\n  \"Intelligence\": \"Trí tuệ\",\n  \"Translation goes here...\": \"Bản dịch sẽ hiện ở đây...\",\n  \"Toggle clipboard query on overview widget\": \"Mở/đóng tìm kiếm clipboard trên overview\",\n  \"Search\": \"Tìm kiếm\",\n  \"Timeout (ms)\": \"Thời gian chờ (ms)\",\n  \"24h\": \"24h\",\n  \"Color picker\": \"Chọn màu\",\n  \"Save to Downloads\": \"Lưu vào Downloads\",\n  \"No notifications\": \"Không có thông báo\",\n  \"Game mode\": \"Chế độ game\",\n  \"Alternatively use /dark, /light, /img in the launcher\": \"Có thể dùng /dark, /light, /img trong launcher\",\n  \"Info\": \"Thông tin\",\n  \"Dock\": \"Dock\",\n  \"Pinned on startup\": \"Ghim khi khởi động\",\n  \"Suspend at\": \"Tạm dừng ở\",\n  \"Fruit Salad\": \"Salad hoa quả\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"API key:\\n\\n```txt\\n%1\\n```\",\n  \"API key set for %1\": \"API key đã đặt cho %1\",\n  \"Not visible to model\": \"Không hiển thị cho model\",\n  \"Expressive\": \"Biểu cảm\",\n  \"Enter text to translate...\": \"Nhập văn bản để dịch...\",\n  \"Usage\": \"Cách dùng\",\n  \"Message the model... \\\"%1\\\" for commands\": \"Hỏi model... \\\"%1\\\" để xem lệnh\",\n  \"Keybinds\": \"Phím tắt\",\n  \"Model set to %1\": \"Đã đặt model thành %1\",\n  \"Scale (%)\": \"Tỉ lệ (%)\",\n  \"Type /key to get started with online models\\nCtrl+O to expand the sidebar\\nCtrl+P to detach sidebar into a window\": \"Gõ /key để bắt đầu dùng các model trực tuyến\\nCtrl+O để mở rộng sidebar\\nCtrl+P để nhấc sidebar thành cửa sổ\",\n  \"Output\": \"Đầu ra\",\n  \"Uptime: %1\": \"Máy bật được %1\",\n  \"For desktop wallpapers | Good quality\": \"Cho hình nền máy tính | Chất lượng tốt\",\n  \"Nothing here!\": \"Không có gì ở đây!\",\n  \"Close\": \"Đóng\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"Phím mũi tên để chọn, Enter để xác nhận\\nEsc hoặc ấn bất kỳ đâu để thoát\",\n  \"Copy code\": \"Sao chép code\",\n  \"Load prompt from %1\": \"Tải chỉ dẫn từ %1\",\n  \"Time\": \"Thời gian\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**Giá**: miễn phí. Chính sách sử dụng dữ liệu tùy thuộc vào cài đặt tài khoản OpenRouter của bạn.\\n\\n**Hướng dẫn**: Đăng nhập vào tài khoản OpenRouter, mở Keys ở menu góc trên bên phải, ấn Create API Key\",\n  \"Bar style\": \"Phong cách bar\",\n  \"Configuration\": \"Cài đặt\",\n  \"Prefixes\": \"Kí tự đầu\",\n  \"No API key set for %1\": \"Không có API key cho %1\",\n  \"Add task\": \"Thêm công việc\",\n  \"Volume mixer\": \"Trộn âm lượng\",\n  \"Go to source (%1)\": \"Đi đến nguồn (%1)\",\n  \"The current API used. Endpoint:\": \"API đang sử dụng. Endpoint:\",\n  \"View Markdown source\": \"Xem nguồn Markdown\",\n  \"Input\": \"Đầu vào\",\n  \"Allow NSFW\": \"Cho phép NSFW\",\n  \"Session\": \"Session\",\n  \"Detach left sidebar into a window/Attach it back\": \"Nhấc sidebar trái thành cửa sổ/Đặt nó lại\",\n  \"Night Light\": \"Lọc ánh sáng xanh\",\n  \"Workspaces\": \"Các workspace\",\n  \"Dark\": \"Tối\",\n  \"Base URL\": \"Base URL\",\n  \"Hug\": \"Ôm\",\n  \"Buttons\": \"Các nút\",\n  \"Get the next page of results\": \"Lấy trang kết quả tiếp theo\",\n  \"%1 Safe Storage\": \"Lưu trữ an toàn %1\",\n  \"Color generation\": \"Chỉnh màu\",\n  \"Select output device\": \"Chọn đầu ra\",\n  \"Select input device\": \"Chọn đầu vào\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 việc cần làm\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"Model trực tuyến không được cho phép\\n\\nCài đặt bởi lựa chọn `policies.ai`\",\n  \"Download complete\": \"Đã tải xong\",\n  \"Code saved to file\": \"Code đã lưu vào file\",\n  \"Critically low battery\": \"Pin rất thấp\",\n  \"Scroll to change brightness\": \"Cuộn để thay đổi độ sáng\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Toggles bar on press\": \"Mở/đóng bar khi ấn\",\n  \"Saved to %1\": \"Đã lưu vào %1\",\n  \"Elements\": \"Nguyên tố\",\n  \"Save chat to %1\": \"Lưu chat vào %1\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Kết nối không thành công. Hãy xem lại với lệnh <tt>warp-cli</tt>\",\n  \"Weather Service\": \"Thời tiết\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"Đăng ký không thành công. Hãy xem lại với lệnh <tt>warp-cli</tt>\",\n  \"Consider plugging in your device\": \"Hãy cắm nguồn thiết bị của bạn\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"Không tìm thấy dịch vụ GPS. Đang sử dụng phương pháp dự phòng.\",\n  \"Opens bar on press\": \"Mở bar khi ấn\",\n  \"Low battery\": \"Pin yếu\",\n  \"Scroll to change volume\": \"Cuộn để thay đổi âm lượng\",\n  \"Please charge!\\nAutomatic suspend triggers at %1\": \"Hãy sạc pin!\\nHệ thống sẽ tự động ngủ khi pin xuống %1\",\n  \"Closes bar on press\": \"Đóng bar khi ấn\",\n  \"Mo\": \"T2/*keep*/\",\n  \"Tu\": \"T3/*keep*/\",\n  \"We\": \"T4/*keep*/\",\n  \"Th\": \"T5/*keep*/\",\n  \"Fr\": \"T6/*keep*/\",\n  \"Sa\": \"T7/*keep*/\",\n  \"Su\": \"CN/*keep*/\",\n  \"Approve\": \"Chấp nhận\",\n  \"Set the tool to use for the model.\": \"Chọn công cụ để sử dụng với model.\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"Không có API key\\nĐặt bằng /key API_KEY\",\n  \"API Key\": \"API Key\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | Ấn chuột phải để chỉnh cài đặt\",\n  \"API key is set\": \"API key đã đặt\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"Công cụ không hợp lệ. Các lựa chọn:\\n- %1\",\n  \"Thought\": \"Suy nghĩ\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"Công cụ: %1\\nĐặt bằng %2tool CÔNG_CỤ\",\n  \"Edit shell config file\": \"Chỉnh file config của shell\",\n  \"A download might be in progress\": \"Có thể có tệp đang tải\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API key đã đặt\\nThay đổi bằng /key API_KEY\",\n  \"Temperature\\nChange with /temp VALUE\": \"Nhiệt độ (độ ngẫu nhiên)\\nThay đổi bằng /temp GIÁ_TRỊ\",\n  \"Your package manager is running\": \"Package manager đang chạy\",\n  \"Usage: %1load CHAT_NAME\": \"Hướng dẫn: %1load TÊN_ĐOẠN_CHAT\",\n  \"Cannot switch to search mode from %1\": \"Không thể chuyển sang chế độ tìm kiếm từ %1\",\n  \"UV Index\": \"Chỉ số UV\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"Trực tuyến | Model của Google\\nNhanh, có thể tìm kiếm Google để lấy thông tin cập nhật\",\n  \"Token count\": \"Số lượng token\",\n  \"Experimental | Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Thử nghiệm | Trực tuyến | Model của Google\\nModel Gemini 2.5 Flash tối ưu hóa cho hiệu quả chi phí và băng thông.\",\n  \"Wallpaper parallax\": \"Hiệu ứng parallax (hình nền)\",\n  \"Usage: %1tool TOOL_NAME\": \"Hướng dẫn: %1tool TÊN_CÔNG_CỤ\",\n  \"Humidity\": \"Độ ẩm\",\n  \"Invalid tool. Supported tools: %1\": \"Công cụ không hợp lệ. Các lựa chọn: %1\",\n  \"Sunset\": \"Hoàng hôn\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"Số lượng token\\nInput: %1\\nOutput: %2\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"Trực tuyến | Model của Google\\nModel Gemini 2.5 Flash tối ưu hóa cho hiệu quả chi phí và băng thông.\",\n  \"Visibility\": \"Tầm nhìn\",\n  \"Pressure\": \"Áp suất\",\n  \"Depends on workspace\": \"Phụ thuộc vào workspace\",\n  \"Reject\": \"Từ chối\",\n  \"Precipitation\": \"Lượng mưa\",\n  \"Wind\": \"Gió\",\n  \"Usage: %1save CHAT_NAME\": \"Hướng dẫn: %1save TÊN_ĐOẠN_CHAT\",\n  \"Enable EasyEffects\": \"Bật EasyEffects\",\n  \"Night Light | Click to toggle, right-click to toggle automatic mode\": \"Lọc ánh sáng xanh | ấn để bật/tắt, ấn chuột phải để bật/tắt chế độ tự động\",\n  \"Night Light | Right-click to toggle Auto mode\": \"Lọc ánh sáng xanh | Ấn chuột phải để bật/tắt chế độ tự động\",\n  \"No command provided\": \"Không có lệnh nào được cung cấp\",\n  \"No API key\": \"Không có API key\",\n  \"Performance Profile toggle\": \"Nút Performance Profile\",\n  \"Sunrise\": \"Bình minh\",\n  \"Online | Google's model\\nNewer one that's slower\": \"Trực tuyến | Model của Google\\nMới hơn nhưng chậm hơn\",\n  \"Command rejected by user\": \"Lệnh bị từ chối bởi người dùng\",\n  \"Experimental | Online | Google's model\\nCan do a little more but takes an extra turn to perform search\": \"Thử nghiệm | Trực tuyến | Model của Google\\nCó thể làm nhiều hơn một chút nhưng mất thêm một lượt để thực hiện tìm kiếm\",\n  \"Depends on sidebars\": \"Phụ thuộc vào sidebar\",\n  \"Temperature\": \"Nhiệt độ\",\n  \"There might be a download in progress\": \"Có thể có tệp đang tải\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Token count | Input: %1 | Output: %2\": \"Số lượng token | Input: %1 | Output: %2\",\n  \"Tool set to %1\": \"Công cụ được đặt thành %1\",\n  \"Invalid arguments. Must provide `command`.\": \"Tham số không hợp lệ. Phải cung cấp `command`.\",\n  \"A download is in progress\": \"Có một tệp đang tải\",\n  \"illogical-impulse Settings\": \"Cài đặt illogical-impulse\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"Trực tuyến | Model của Google\\nMới hơn nhưng chậm hơn so với phiên bản trước nhưng nên cung cấp câu trả lời chất lượng cao hơn\",\n  \"Preferred wallpaper zoom (%)\": \"Tỷ lệ thu phóng hình nền (%)\",\n  \"Function Response\": \"Phản hồi function\"\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/translations/zh_CN.json",
    "content": "{\n  \"Mo\": \"一/*keep*/\",\n  \"Tu\": \"二/*keep*/\",\n  \"We\": \"三/*keep*/\",\n  \"Th\": \"四/*keep*/\",\n  \"Fr\": \"五/*keep*/\",\n  \"Sa\": \"六/*keep*/\",\n  \"Su\": \"日/*keep*/\",\n  \"%1 characters\": \"%1 个字符\",\n  \"**Pricing**: free. Data used for training.\\n\\n**Instructions**: Log into Google account, allow AI Studio to create Google Cloud project or whatever it asks, go back and click Get API key\": \"**价格**：免费。数据用于训练。\\n\\n**说明**：登录 Google 账户，允许 AI Studio 创建 Google Cloud 项目或其他要求，然后返回并点击获取 API 密钥\",\n  \"**Pricing**: free. Data use policy varies depending on your OpenRouter account settings.\\n\\n**Instructions**: Log into OpenRouter account, go to Keys on the topright menu, click Create API Key\": \"**价格**：免费。数据使用政策取决于你的 OpenRouter 账户设置。\\n\\n**说明**：登录 OpenRouter 账户，在右上角菜单中选择 Keys，点击创建 API 密钥\",\n  \". Notes for Zerochan:\\n- You must enter a color\\n- Set your zerochan username in `sidebar.booru.zerochan.username` config option. You [might be banned for not doing so](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)!\": \"。Zerochan 注意事项：\\n- 你需要指定一个颜色\\n- 请在 `sidebar.booru.zerochan.username` 配置项内填写你的 Zerochan 用户名。如果不这样做[将可能会被封禁](https://www.zerochan.net/api#:~:text=The%20request%20may%20still%20be%20completed%20successfully%20without%20this%20custom%20header%2C%20but%20your%20project%20may%20be%20banned%20for%20being%20anonymous.)！\",\n  \"<i>No further instruction provided</i>\": \"<i>未提供进一步说明</i>\",\n  \"API key set for %1\": \"已为 %1 设置 API 密钥\",\n  \"API key:\\n\\n```txt\\n%1\\n```\": \"API 密钥：\\n\\n```txt\\n%1\\n```\",\n  \"Action\": \"操作\",\n  \"Add\": \"添加\",\n  \"Add task\": \"添加任务\",\n  \"All-rounder | Good quality, decent quantity\": \"全能型 | 质量好，数量适中\",\n  \"Allow NSFW\": \"允许 NSFW\",\n  \"Allow NSFW content\": \"允许 NSFW 内容\",\n  \"Anime\": \"动漫\",\n  \"Anime boorus\": \"动漫图库\",\n  \"App\": \"应用\",\n  \"Arrow keys to navigate, Enter to select\\nEsc or click anywhere to cancel\": \"方向键导航，回车选择\\nEsc 或点击任意地方取消\",\n  \"Bluetooth\": \"蓝牙\",\n  \"Brightness\": \"亮度\",\n  \"Cancel\": \"取消\",\n  \"Cheat sheet\": \"快捷键指南\",\n  \"Choose model\": \"选择模型\",\n  \"Clean stuff | Excellent quality, no NSFW\": \"清洁内容 | 优秀质量，无 NSFW\",\n  \"Clear chat history\": \"清除聊天记录\",\n  \"Clear the current list of images\": \"清除当前图片列表\",\n  \"Close\": \"关闭\",\n  \"Copy\": \"复制\",\n  \"Copy code\": \"复制代码\",\n  \"Current API endpoint: %1\\nSet it with %2mode PROVIDER\": \"当前 API 端点：%1\\n使用 %2mode PROVIDER 设置\",\n  \"Delete\": \"删除\",\n  \"Desktop\": \"桌面\",\n  \"Disable NSFW content\": \"禁用 NSFW 内容\",\n  \"Done\": \"完成\",\n  \"Download\": \"下载\",\n  \"Edit\": \"编辑\",\n  \"Enter text to translate...\": \"输入要翻译的文本...\",\n  \"Finished tasks will go here\": \"已完成的任务将显示在这里\",\n  \"For desktop wallpapers | Good quality\": \"桌面壁纸专用 | 质量好\",\n  \"For storing API keys and other sensitive information\": \"用于存储 API 密钥和其他敏感信息\",\n  \"Game mode\": \"游戏模式\",\n  \"Get the next page of results\": \"获取下一页结果\",\n  \"Go to source (%1)\": \"转到源 (%1)\",\n  \"Hibernate\": \"休眠\",\n  \"Input\": \"输入\",\n  \"Intelligence\": \"智能\",\n  \"Interface\": \"界面\",\n  \"Invalid arguments. Must provide `key` and `value`.\": \"参数无效。必须提供 `key` 和 `value`。\",\n  \"Jump to current month\": \"跳转到当前月份\",\n  \"Keep system awake\": \"保持系统唤醒\",\n  \"Large images | God tier quality, no NSFW.\": \"大尺寸图片 | 顶级质量，无 NSFW\",\n  \"Large language models\": \"大语言模型\",\n  \"Local Ollama model | %1\": \"本地 Ollama 模型 | %1\",\n  \"Lock\": \"锁定\",\n  \"Logout\": \"注销\",\n  \"Markdown test\": \"Markdown 测试\",\n  \"Math result\": \"数学结果\",\n  \"No API key set for %1\": \"未为 %1 设置 API 密钥\",\n  \"No media\": \"无媒体\",\n  \"Not visible to model\": \"对模型不可见\",\n  \"Nothing here!\": \"这里什么都没有！\",\n  \"Notifications\": \"通知\",\n  \"OK\": \"确定\",\n  \"Open file link\": \"打开文件链接\",\n  \"Output\": \"输出\",\n  \"Page %1\": \"第 %1 页\",\n  \"Reboot\": \"重启\",\n  \"Reboot to firmware settings\": \"重启到固件设置\",\n  \"Reload Hyprland & Quickshell\": \"重新加载 Hyprland 与 Quickshell\",\n  \"Run\": \"运行\",\n  \"Save\": \"保存\",\n  \"Save to Downloads\": \"保存到下载文件夹\",\n  \"Search\": \"搜索\",\n  \"Search, calculate or run\": \"搜索、计算或运行\",\n  \"Select Language\": \"选择语言\",\n  \"Session\": \"会话\",\n  \"Set API key\": \"设置 API 密钥\",\n  \"Set temperature (randomness) of the model. Values range between 0 to 2 for Gemini, 0 to 1 for other models. Default is 0.5.\": \"设置模型的温度（随机性）。Gemini 模型范围为 0 到 2，其他模型为 0 到 1。默认值为 0.5。\",\n  \"Set the current API provider\": \"设置当前 API 提供商\",\n  \"Shutdown\": \"关机\",\n  \"Silent\": \"静音\",\n  \"Sleep\": \"睡眠\",\n  \"System\": \"系统\",\n  \"Task Manager\": \"任务管理器\",\n  \"Task description\": \"任务描述\",\n  \"Temperature must be between 0 and 2\": \"温度必须在 0 到 2 之间\",\n  \"Temperature set to %1\": \"温度已设置为 %1\",\n  \"Temperature: %1\": \"温度：%1\",\n  \"The hentai one | Great quantity, a lot of NSFW, quality varies wildly\": \"成人向 | 数量巨大，大量 NSFW，质量参差不齐\",\n  \"The popular one | Best quantity, but quality can vary wildly\": \"最受欢迎 | 数量最多，但质量参差不齐\",\n  \"Thinking\": \"思考中\",\n  \"Translation goes here...\": \"翻译结果会显示在这里...\",\n  \"Translator\": \"翻译\",\n  \"Unfinished\": \"未完成\",\n  \"Unknown\": \"未知\",\n  \"Unknown Album\": \"未知专辑\",\n  \"Unknown Artist\": \"未知艺术家\",\n  \"Unknown Title\": \"未知标题\",\n  \"Unknown function call: %1\": \"未知函数调用：%1\",\n  \"View Markdown source\": \"查看 Markdown 源码\",\n  \"Volume\": \"音量\",\n  \"Volume mixer\": \"音量混合器\",\n  \"Waifus only | Excellent quality, limited quantity\": \"仅限角色 | 优秀质量，数量有限\",\n  \"Workspace\": \"工作区\",\n  \"%1 Safe Storage\": \"%1 安全存储\",\n  \"%1 does not require an API key\": \"%1 不需要 API 密钥\",\n  \"%1 | Right-click to configure\": \"%1 | 右键以配置\",\n  \"Invalid API provider. Supported: \\n- \": \"无效的 API 提供商。支持的有：\\n- \",\n  \"Unknown command: \": \"未知命令：\",\n  \"Provider set to \": \"提供商已设置为 \",\n  \"Invalid model. Supported: \\n```\\n\": \"无效模型。支持的有：\\n```\\n\",\n  \"Switched to search mode. Continue with the user's request.\": \"已切换到搜索模式。继续处理用户请求。\",\n  \"Enter tags, or \\\"%1\\\" for commands\": \"输入标签，或 “%1” 以查看命令\",\n  \"Online via %1 | %2's model\": \"在线 | 通过 %1 | %2 的模型\",\n  \"That didn't work. Tips:\\n- Check your tags and NSFW settings\\n- If you don't have a tag in mind, type a page number\": \"没有找到结果。提示：\\n- 检查你的标签和 NSFW 设置\\n- 如果还没想到标签，可以直接输入页码\",\n  \"Settings\": \"设置\",\n  \"Save chat\": \"保存聊天记录\",\n  \"Load chat\": \"加载聊天记录\",\n  \"or\": \"或\",\n  \"Set the system prompt for the model.\": \"为模型设置系统提示词。\",\n  \"To Do\": \"待办\",\n  \"Calendar\": \"日历\",\n  \"Advanced\": \"高级\",\n  \"About\": \"关于\",\n  \"Services\": \"服务\",\n  \"Light\": \"浅色\",\n  \"Dark\": \"深色\",\n  \"Fidelity\": \"保真\",\n  \"Fruit Salad\": \"水果沙拉\",\n  \"When not fullscreen\": \"非全屏时\",\n  \"Choose file\": \"选择文件\",\n  \"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\": \"随机 Konachan SFW 动漫壁纸\\n图片会保存到 ~/Pictures/Wallpapers\",\n  \"Be patient...\": \"请耐心等待...\",\n  \"Tonal Spot\": \"色调点\",\n  \"Auto\": \"自动\",\n  \"Content\": \"内容\",\n  \"Transparency\": \"透明度\",\n  \"Expressive\": \"表现力\",\n  \"Yes\": \"是\",\n  \"Enable\": \"启用\",\n  \"Rainbow\": \"彩虹\",\n  \"Might look ass. Unsupported.\": \"可能效果很差。不支持。\",\n  \"Monochrome\": \"单色\",\n  \"Random: Konachan\": \"随机：Konachan\",\n  \"Neutral\": \"中性\",\n  \"Pick wallpaper image on your system\": \"在系统中选择壁纸图片\",\n  \"No\": \"否\",\n  \"AI\": \"AI\",\n  \"Local only\": \"仅限本地\",\n  \"Policies\": \"策略\",\n  \"Weeb\": \"二次元\",\n  \"Closet\": \"隐藏\",\n  \"Show next time\": \"下次显示\",\n  \"Usage\": \"用法\",\n  \"Useless buttons\": \"无用按钮\",\n  \"GitHub\": \"GitHub\",\n  \"Style & wallpaper\": \"样式与壁纸\",\n  \"Configuration\": \"配置\",\n  \"Keybinds\": \"快捷键\",\n  \"Float\": \"悬浮\",\n  \"Hug\": \"贴合\",\n  \"illogical-impulse Welcome\": \"illogical-impulse 欢迎页\",\n  \"Info\": \"信息\",\n  \"Volume limit\": \"音量限制\",\n  \"Prevents abrupt increments and restricts volume limit\": \"防止骤增并限制音量\",\n  \"Resources\": \"资源\",\n  \"12h am/pm\": \"12小时制 am/pm\",\n  \"Base URL\": \"基础 URL\",\n  \"Audio\": \"声音\",\n  \"Networking\": \"网络\",\n  \"Format\": \"格式\",\n  \"Time\": \"时间\",\n  \"Battery\": \"电池\",\n  \"Prefixes\": \"前缀\",\n  \"Emojis\": \"表情符号\",\n  \"Earbang protection\": \"防爆音保护\",\n  \"Automatically suspends the system when battery is low\": \"电池电量低时自动挂起系统\",\n  \"Automatic suspend\": \"自动挂起\",\n  \"Max allowed increase\": \"最大允许增幅\",\n  \"Web search\": \"网页搜索\",\n  \"Polling interval (ms)\": \"轮询间隔（毫秒）\",\n  \"Clipboard\": \"剪贴板\",\n  \"Low warning\": \"低电量警告\",\n  \"24h\": \"24小时制\",\n  \"Use Levenshtein distance-based algorithm instead of fuzzy\": \"使用 Levenshtein 距离算法替代模糊匹配\",\n  \"System prompt\": \"系统提示词\",\n  \"12h AM/PM\": \"12小时制 AM/PM\",\n  \"Could be better if you make a ton of typos,\\nbut results can be weird and might not work with acronyms\\n(e.g. \\\"GIMP\\\" might not give you the paint program)\": \"如果你经常打错字可能更好用，但结果可能会奇怪，并且可能无法匹配缩写（如 “GIMP” 可能搜不到绘图程序）\",\n  \"Critical warning\": \"临界警告\",\n  \"User agent (for services that require it)\": \"用户代理（部分服务需要）\",\n  \"Workspaces shown\": \"显示的工作区数\",\n  \"Dark/Light toggle\": \"深浅色切换\",\n  \"Dock\": \"停靠栏\",\n  \"Weather\": \"天气\",\n  \"Pinned on startup\": \"启动时固定\",\n  \"Always show numbers\": \"始终显示数字\",\n  \"Keyboard toggle\": \"键盘切换\",\n  \"Scale (%)\": \"缩放比例（%）\",\n  \"Overview\": \"概览\",\n  \"Rows\": \"行数\",\n  \"Number show delay when pressing Super (ms)\": \"按下 Super 时的数字显示延迟（毫秒）\",\n  \"Timeout (ms)\": \"显示时间（毫秒）\",\n  \"Show app icons\": \"显示应用图标\",\n  \"Workspaces\": \"工作区\",\n  \"Columns\": \"列数\",\n  \"On-screen display\": \"屏幕显示\",\n  \"Screen snip\": \"屏幕截图\",\n  \"Mic toggle\": \"麦克风切换\",\n  \"Hover to reveal\": \"悬停显示\",\n  \"Bar\": \"条栏\",\n  \"Color picker\": \"取色器\",\n  \"Help & Support\": \"帮助与支持\",\n  \"Discussions\": \"讨论区\",\n  \"Color generation\": \"配色生成\",\n  \"Dotfiles\": \"配置文件\",\n  \"Distro\": \"发行版\",\n  \"Privacy Policy\": \"隐私政策\",\n  \"Documentation\": \"文档\",\n  \"Shell & utilities theming must also be enabled\": \"必须同时启用 Shell 与工具主题\",\n  \"Ignored if terminal theming is not enabled\": \"终端主题未启用时不生效\",\n  \"illogical-impulse\": \"illogical-impulse\",\n  \"Donate\": \"捐助\",\n  \"Terminal\": \"终端\",\n  \"Shell & utilities\": \"Shell 与工具\",\n  \"Qt apps\": \"Qt 应用\",\n  \"Force dark mode in terminal\": \"强制终端使用深色模式\",\n  \"Report a Bug\": \"报告问题\",\n  \"Issues\": \"问题追踪\",\n  \"Current model: %1\\nSet it with %2model MODEL\": \"当前模型：%1\\n使用 %2model MODEL 设置\",\n  \"Message the model... \\\"%1\\\" for commands\": \"向模型发送消息... “%1” 以查看命令\",\n  \"The current system prompt is\\n\\n---\\n\\n%1\": \"当前系统提示词为\\n\\n---\\n\\n%1\",\n  \"Model set to %1\": \"模型已设置为 %1\",\n  \"Loaded the following system prompt\\n\\n---\\n\\n%1\": \"已加载以下系统提示词\\n\\n---\\n\\n%1\",\n  \"%1 notifications\": \"%1 条通知\",\n  \"Save chat to %1\": \"保存聊天记录到 %1\",\n  \"Load chat from %1\": \"从 %1 加载聊天记录\",\n  \"Load prompt from %1\": \"从 %1 加载提示词\",\n  \"%1   •   %2 tasks\": \"%1   •   %2 项任务\",\n  \"Online models disallowed\\n\\nControlled by `policies.ai` config option\": \"已禁止在线模型\\n\\n由 `policies.ai` 配置项控制\",\n  \"Low battery\": \"电量低\",\n  \"Registration failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"注册失败。请使用 <tt>warp-cli</tt> 命令手动检查\",\n  \"Code saved to file\": \"代码已保存到文件\",\n  \"Consider plugging in your device\": \"请考虑为你的设备充电\",\n  \"Weather Service\": \"天气服务\",\n  \"Cloudflare WARP (1.1.1.1)\": \"Cloudflare WARP (1.1.1.1)\",\n  \"Cloudflare WARP\": \"Cloudflare WARP\",\n  \"Download complete\": \"下载完成\",\n  \"Critically low battery\": \"电量极低\",\n  \"Scroll to change brightness\": \"滚动以调节亮度\",\n  \"Saved to %1\": \"已保存到 %1\",\n  \"Cannot find a GPS service. Using the fallback method instead.\": \"无法找到 GPS 服务。正在使用备用方法。\",\n  \"Elements\": \"元素\",\n  \"Scroll to change volume\": \"滚动以调节音量\",\n  \"Connection failed. Please inspect manually with the <tt>warp-cli</tt> command\": \"连接失败。请使用 <tt>warp-cli</tt> 命令手动检查\",\n  \"UV Index\": \"紫外线指数\",\n  \"Pressure\": \"气压\",\n  \"Visibility\": \"能见度\",\n  \"Sunrise\": \"日出\",\n  \"Sunset\": \"日落\",\n  \"Humidity\": \"湿度\",\n  \"Wind\": \"风\",\n  \"Precipitation\": \"降水量\",\n  \"Time to full:\": \"距离充满：\",\n  \"Time to empty:\": \"距离耗尽：\",\n  \"Fully charged\": \"已充满电\",\n  \"Charging:\": \"充电功率：\",\n  \"Discharging:\": \"放电功率：\",\n  \"No pending tasks\": \"没有要做的任务\",\n  \"... and %1 more\": \"... 还有 %1 个\",\n  \"Used:\": \"已用：\",\n  \"Free:\": \"可用：\",\n  \"Total:\": \"总计：\",\n  \"Load:\": \"负载：\",\n  \"Medium\": \"适中\",\n  \"Tint icons\": \"图标着色\",\n  \"Performance Profile toggle\": \"性能配置切换\",\n  \"**Instructions**: Log into Mistral account, go to Keys on the sidebar, click Create new key\": \"**说明**：登录 Mistral 账户，在侧边栏中选择 Keys，点击 Create new key\",\n  \"Invalid arguments. Must provide `command`.\": \"参数无效。必须提供 `command`。\",\n  \"Thought\": \"思考\",\n  \"Online | Google's model\\nA Gemini 2.5 Flash model optimized for cost-efficiency and high throughput.\": \"在线 | Google 的模型\\n针对成本效益和高吞吐量优化的 Gemini 2.5 Flash 模型。\",\n  \"Online | Google's model\\nFast, can perform searches for up-to-date information\": \"在线 | Google 的模型\\n速度快，可搜索最新信息\",\n  \"Your package manager is running\": \"你的包管理器正在运行\",\n  \"Gives the model search capabilities (immediately)\": \"为模型提供搜索功能（即时）\",\n  \"Set the tool to use for the model.\": \"设置模型使用的工具。\",\n  \"Night Light | Right-click to toggle Auto mode\": \"夜间模式 | 右键切换自动模式\",\n  \"Online | %1's model | Delivers fast, responsive and well-formatted answers. Disadvantages: not very eager to do stuff; might make up unknown function calls\": \"在线 | %1 的模型 | 提供快速、响应迅速且格式良好的答案。缺点：不太积极主动；可能编造未知的函数调用\",\n  \"Depends on workspace\": \"随工作区移动\",\n  \"Usage: %1tool TOOL_NAME\": \"用法：%1tool 工具名称\",\n  \"Tray\": \"托盘\",\n  \"Usage: %1save CHAT_NAME\": \"用法：%1save 聊天名称\",\n  \"Approve\": \"批准\",\n  \"Depends on sidebars\": \"随侧边栏移动\",\n  \"Commands, edit configs, search.\\nTakes an extra turn to switch to search mode if that's needed\": \"执行命令、编辑配置、搜索。\\n如果需要，会额外执行一次切换到搜索模式\",\n  \"Up %1\": \"运行 %1\",\n  \"Tool set to: %1\": \"工具已设置为 %1\",\n  \"Online | Google's model\\nGoogle's state-of-the-art multipurpose model that excels at coding and complex reasoning tasks.\": \"在线 | Google 的模型\\nGoogle 最先进的多用途模型，在编程和复杂推理任务方面表现卓越。\",\n  \"Tint app icons\": \"应用图标着色\",\n  \"Preferred wallpaper zoom (%)\": \"首选壁纸缩放比例（%）\",\n  \"To set an API key, pass it with the %4 command\\n\\nTo view the key, pass \\\"get\\\" with the command<br/>\\n\\n### For %1:\\n\\n**Link**: %2\\n\\n%3\": \"要设置 API 密钥，请使用 %4 命令传递\\n\\n要查看密钥，请在命令中传递 “get”<br/>\\n\\n### 对于 %1：\\n\\n**链接**：%2\\n\\n%3\",\n  \"No API key\\nSet it with /key YOUR_API_KEY\": \"无 API 密钥\\n使用 /key YOUR_API_KEY 设置\",\n  \"Total token count\\nInput: %1\\nOutput: %2\": \"总词元数\\n输入：%1\\n输出：%2\",\n  \"Disable tools\": \"禁用工具\",\n  \"API key is set\\nChange with /key YOUR_API_KEY\": \"API 密钥已设置\\n使用 /key YOUR_API_KEY 更改\",\n  \"Usage: %1load CHAT_NAME\": \"用法：%1load 聊天名称\",\n  \"Sidebars\": \"侧边栏\",\n  \"Temperature\\nChange with /temp VALUE\": \"温度\\n使用 /temp VALUE 更改\",\n  \"Current tool: %1\\nSet it with %2tool TOOL\": \"当前工具：%1\\n使用 %2tool TOOL 设置\",\n  \"There might be a download in progress\": \"可能有下载正在进行\",\n  \"Online | Google's model\\nNewer model that's slower than its predecessor but should deliver higher quality answers\": \"在线 | Google 的模型\\n比前代模型更慢但应该提供更高质量答案的新模型\",\n  \"EasyEffects | Right-click to configure\": \"EasyEffects | 右键以配置\",\n  \"Command rejected by user\": \"用户拒绝了命令\",\n  \"Invalid tool. Supported tools:\\n- %1\": \"无效工具。支持的工具：\\n- %1\",\n  \"Keep right sidebar loaded\": \"保持右侧边栏加载\",\n  \"Reject\": \"拒绝\",\n  \"Enter password\": \"输入密码\",\n  \"Automatically hide\": \"自动隐藏\",\n  \"**Pricing**: Free tier available with limited rates. See https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**Instructions**: Generate a GitHub personal access token with Models permission, then set as API key here\\n\\n**Note**: To use this you will have to set the temperature parameter to 1\": \"**定价**：提供免费层级，速率有限。详情见 https://docs.github.com/en/billing/concepts/product-billing/github-models\\n\\n**说明**：生成一个具有 Models 权限的 GitHub 个人访问令牌，并在此处将其设置为 API 密钥\\n\\n**注意**：使用此提供商时需要将温度参数设置为 1\",\n  \"Conflicts with the shell's system tray implementation\": \"与 Shell 的系统托盘实现冲突\",\n  \"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\": \"祝你愉快！可以随时按 <tt>Super+Shift+Alt+/</tt> 重新打开欢迎应用。要打开设置应用，请按 <tt>Super+I</tt>\",\n  \"Reset\": \"重置\",\n  \"☕ Break: %1 minutes\": \"☕ 休息：%1 分钟\",\n  \"Pomodoro\": \"番茄钟\",\n  \"Long break\": \"长休息\",\n  \"Resume\": \"继续\",\n  \"Start\": \"开始\",\n  \"Hi there! First things first...\": \"嗨！先做些重要的事情...\",\n  \"Refreshing (manually triggered)\": \"刷新中（手动触发）\",\n  \"Kill conflicting programs?\": \"结束冲突程序？\",\n  \"When enabled keeps the content of the right sidebar loaded to reduce the delay when opening,\\nat the cost of around 15MB of consistent RAM usage. Delay significance depends on your system's performance.\\nUsing a custom kernel like linux-cachyos might help\": \"启用后会保持右侧边栏内容常驻，以减少打开时的延迟，代价是大约 15MB 的持续内存使用。延迟的影响程度取决于系统性能。使用像 linux-cachyos 这样的自定义内核可能会有所帮助\",\n  \"Config file\": \"配置文件\",\n  \"Stopwatch\": \"秒表\",\n  \"Break\": \"休息\",\n  \"Shell conflicts killer\": \"冲突终止程序\",\n  \"Vertical\": \"垂直\",\n  \"🔴 Focus: %1 minutes\": \"🔴 专注：%1 分钟\",\n  \"System uptime:\": \"系统运行时间：\",\n  \"Focus\": \"专注\",\n  \"Attach a file. Only works with Gemini.\": \"附加文件。仅适用于 Gemini。\",\n  \"🌿 Long break: %1 minutes\": \"🌿 长休息：%1 分钟\",\n  \"Always\": \"始终\",\n  \"To Do:\": \"待办：\",\n  \"Incorrect password\": \"密码错误\",\n  \"Timer\": \"计时器\",\n  \"Conflicts with the shell's notification implementation\": \"与 Shell 的通知实现冲突\",\n  \"Pause\": \"暂停\",\n  \"Feels like %1\": \"体感温度 %1\",\n  \"Lap\": \"计时\",\n  \"Welcome app\": \"欢迎应用\",\n  \"Corner style\": \"角落样式\",\n  \"Language\": \"语言\",\n  \"Select the language for the user interface.\\n\\\"Auto\\\" will use your system's locale.\": \"选择用户界面的语言。\\n“自动”将使用系统区域设置。\",\n  \"Auto (System)\": \"自动（系统）\",\n  \"Interface Language\": \"界面语言\",\n  \"Paired\": \"已配对\",\n  \"Hit \\\"/\\\" to search\": \"按 “/” 以搜索\",\n  \"Region width\": \"区域宽度\",\n  \"Math\": \"数学\",\n  \"When this is off you'll have to click\": \"若关闭则需要点击来打开\",\n  \"Edit directory\": \"编辑目录\",\n  \"Pills\": \"胶囊\",\n  \"No active player\": \"无活动播放器\",\n  \"Search wallpapers\": \"搜索壁纸\",\n  \"Rect\": \"矩形\",\n  \"Make sure your player has MPRIS support\\nor try turning off duplicate player filtering\": \"请确保你的播放器支持 MPRIS\\n或尝试关闭重复播放器过滤\",\n  \"Shell command\": \"Shell 命令\",\n  \"Screen round corner\": \"屏幕圆角\",\n  \"Password\": \"密码\",\n  \"Bluetooth devices\": \"蓝牙设备\",\n  \"Wallpaper & Colors\": \"壁纸与配色\",\n  \"Details\": \"详细信息\",\n  \"Connected\": \"已连接\",\n  \"Open network portal\": \"打开网络门户\",\n  \"Bar style\": \"条栏样式\",\n  \"Connect\": \"连接\",\n  \"Superpaste\": \"超级粘贴\",\n  \"Value scroll\": \"滚动调整数值\",\n  \"Line-separated\": \"线条分隔\",\n  \"Region height\": \"区域高度\",\n  \"Pick a wallpaper\": \"挑选壁纸\",\n  \"Visualize region\": \"显示区域\",\n  \"Bottom\": \"底部\",\n  \"Usage: <tt>%1superpaste NUM_OF_ENTRIES[i]</tt>\\nSupply <tt>i</tt> when you want images\\nExamples:\\n<tt>%1superpaste 4i</tt> for the last 4 images\\n<tt>%1superpaste 7</tt> for the last 7 entries\": \"用法：<tt>%1superpaste 条目数[i]</tt>\\n需要图片时加上 <tt>i</tt>\\n示例：\\n<tt>%1superpaste 4i</tt> 粘贴最近 4 张图片\\n<tt>%1superpaste 7</tt> 粘贴最近 7 个条目\",\n  \"Terminal: Harmony (%)\": \"终端：协调度（%）\",\n  \"Forget\": \"忘记\",\n  \"Background\": \"背景\",\n  \"Top\": \"顶部\",\n  \"Not all options are available in this app. You should also check the config file by hitting the \\\"Config file\\\" button on the topleft corner or opening %1 manually.\": \"并非所有选项都在此应用中提供。你还应点击左上角的“配置文件”按钮，或手动打开 %1 以查看配置文件。\",\n  \"General\": \"通用\",\n  \"Right\": \"右侧\",\n  \"Utility buttons\": \"工具按钮\",\n  \"Quick\": \"快速\",\n  \"Terminal: Foreground boost (%)\": \"终端：前景增强（%）\",\n  \"Left\": \"左侧\",\n  \"Tip: right-clicking a group\\nalso expands it\": \"提示：右键点击一个分组\\n也可将其展开\",\n  \"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\": \"稍后可在启动器中通过 /dark、/light、/wallpaper 随时更改\\n如果 Shell 的配色没有变化：\\n    1. 用 Super+N 打开右侧边栏\\n    2. 点击右上角的“重新加载 Hyprland 与 Quickshell”\",\n  \"Place at bottom\": \"放置在底部\",\n  \"Allows you to open sidebars by clicking or hovering screen corners regardless of bar position\": \"无论条栏位置，都允许通过点击或悬停在屏幕角落来打开侧边栏\",\n  \"Positioning\": \"位置\",\n  \"Disconnect\": \"断开连接\",\n  \"Unknown device\": \"未知设备\",\n  \"Make icons pinned by default\": \"默认固定图标\",\n  \"Bar & screen\": \"条栏与屏幕\",\n  \"Corner open\": \"角落打开\",\n  \"Hover to trigger\": \"悬停以触发\",\n  \"Terminal: Harmonize threshold\": \"终端：协调阈值\",\n  \"Bar position\": \"条栏位置\",\n  \"Place the corners to trigger at the bottom\": \"将触发角落放置在底部\",\n  \"Brightness and volume\": \"亮度与音量\",\n  \"Connect to Wi-Fi\": \"连接到 Wi‑Fi\",\n  \"Group style\": \"分组样式\",\n  \"Launch on startup\": \"启动时锁屏\",\n  \"Also unlock keyring\": \"同时解锁密钥环\",\n  \"Tip: Close a window with Super+Q\": \"提示: 使用 Super+Q 关闭窗口\",\n  \"Remember that on most devices one can always hold the power button to force shutdown\\nThis only makes it a tiny bit harder for accidents to happen\": \"请记住，大多数设备仍可以通过长按电源键强制关机\\n此选项只会略微降低误触的可能性而已\",\n  \"This is usually safe and needed for your browser and AI sidebar anyway\\nMostly useful for those who use lock on startup instead of a display manager that does it (GDM, SDDM, etc.)\": \"这通常是安全的，并且对浏览器和 AI 侧边栏也是必要的\\n主要对使用“启动时锁屏”而非显示管理器（如 GDM、SDDM 等）执行锁屏的用户有用\",\n  \"Show \\\"Locked\\\" text\": \"显示“已锁定”字样\",\n  \"at\": \"在\",\n  \"Style: general\": \"样式：通用\",\n  \"Pick random from this folder\": \"从此文件夹随机选择\",\n  \"Back\": \"返回\",\n  \"Cancel wallpaper selection\": \"取消壁纸选择\",\n  \"Timeout duration (if not defined by notification) (ms)\": \"显示时间（若通知未指定）（毫秒）\",\n  \"Enable blur\": \"启用模糊\",\n  \"Click to toggle light/dark mode\\n(applied when wallpaper is chosen)\": \"点击来切换浅色/深色模式\\n（仅在选择壁纸后生效）\",\n  \"Use the system file picker instead\\nRight-click to make this the default behavior\": \"改为使用系统文件选择器\\n右键以将此设为默认行为\",\n  \"Center clock\": \"居中时钟\",\n  \"Lock screen\": \"锁屏\",\n  \"Crosshair code (in Valorant's format)\": \"准星代码（瓦罗兰特格式）\",\n  \"Random: osu! seasonal\": \"随机：osu! 季节性壁纸\",\n  \"Work safety\": \"安全模式\",\n  \"Require password to power off/restart\": \"需要密码来关机或重启\",\n  \"Random osu! seasonal background\\nImage is saved to ~/Pictures/Wallpapers\": \"随机 osu! 季节性壁纸\\n图片会保存到 ~/Pictures/Wallpapers\",\n  \"Open editor\": \"打开编辑器\",\n  \"Extra wallpaper zoom (%)\": \"额外壁纸缩放（%）\",\n  \"Security\": \"安全\",\n  \"Clock style\": \"时钟样式\",\n  \"Style: Blurred\": \"样式：模糊\",\n  \"Locked\": \"已锁定\",\n  \"Wallpaper safety enforced\": \"已启用壁纸安全模式\",\n  \"Hour hand\": \"时针\",\n  \"Automatic\": \"自动\",\n  \"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\": \"想要的语言不在列表内或翻译不完整？\\n你可以选择使用 Gemini 为其生成翻译。\\n1. 按 Super+A 打开左侧边栏，将模型设置为 Gemini（如果不已经是了的话）\\n2. 输入 /key，按 Enter 然后跟随说明\\n3. 输入 /key YOUR_API_KEY\\n4. 在下方输入你的语言代码并按下生成\",\n  \"Auto styling with Gemini\": \"使用 Gemini 自动决定样式\",\n  \"Audio output | Right-click for volume mixer & device selector\": \"音频输出 | 右键打开音量合成器与设备选择器\",\n  \"Most busy\": \"最繁杂处\",\n  \"Title font\": \"标题字体\",\n  \"Nerd font icons\": \"Nerd Font 图标\",\n  \"Could be images or parts of the screen that have some containment.\\nMight not always be accurate.\\nThis is done with an image processing algorithm run locally and no AI is used.\": \"可能是带有一定边界的图片或屏幕部分。结果不一定总是准确。\\n这是通过本地的图片处理算法完成的，过程没有 AI 参与。\",\n  \"Hollow\": \"空心\",\n  \"Turn on from sunset to sunrise\": \"在日落到日出期间开启\",\n  \"Notes\": \"笔记\",\n  \"It may take a few seconds to update\": \"可能需要几秒钟来更新\",\n  \"Google Lens\": \"Google 智能镜头\",\n  \"Monospace font\": \"等宽字体\",\n  \"Auto, \": \"自动，\",\n  \"Classic\": \"经典\",\n  \"Font family name (e.g., JetBrains Mono NF)\": \"字体名称（如 JetBrains Mono NF）\",\n  \"Clear all\": \"全部清除\",\n  \"Show aim lines\": \"显示瞄准线\",\n  \"System sound\": \"系统声音\",\n  \"Search for apps\": \"搜索应用\",\n  \"Circle to Search\": \"圈定即搜\",\n  \"Thin\": \"纤细\",\n  \"Content region\": \"内容区域\",\n  \"Bubble\": \"气泡\",\n  \"Enable opening zoom animation\": \"启用打开时的缩放动画\",\n  \"Secured\": \"安全\",\n  \"Wi-Fi\": \"Wi-Fi\",\n  \"Manage my account\": \"管理我的账户\",\n  \"Regenerate\": \"重新生成\",\n  \"%1\\nInternet access\": \"%1\\nInternet 访问\",\n  \"Font family name (e.g., Space Grotesk)\": \"字体名称（如 Space Grotesk）\",\n  \"Inactive\": \"未启用\",\n  \"Least busy\": \"最空旷处\",\n  \"Constantly rotate\": \"持续旋转\",\n  \"Anti-flashbang (experimental)\": \"防高亮保护（实验性）\",\n  \"Bold\": \"加粗\",\n  \"Open the shell config file\\nAlternatively right-click to copy path\": \"打开 Shell 配置文件\\n或右键以复制路径\",\n  \"Description font size\": \"描述字体大小\",\n  \"Dot\": \"圆点\",\n  \"Second hand\": \"秒针\",\n  \"Generate translation with Gemini\": \"使用 Gemini 生成翻译\",\n  \"Enable GPS based location\": \"启用基于 GPS 的位置\",\n  \"Microphone\": \"麦克风\",\n  \"Virtual Keyboard\": \"屏幕键盘\",\n  \"Shut down\": \"关机\",\n  \"Digits in the middle\": \"在中心显示数字\",\n  \"If you want to somehow use fingerprint unlock...\": \"如果你想想办法用指纹解锁的话...\",\n  \"Couldn't recognize music\": \"未识别到歌曲\",\n  \"Split buttons\": \"拆分按键\",\n  \"Number style\": \"数字样式\",\n  \"Write something here...\\nUse '-' to create copyable bullet points, like this:\\n\\nSheep fricker\\n- 4x Slab\\n- 1x Boat\\n- 4x Redstone Dust\\n- 1x Sticky Piston\\n- 1x End Rod\\n- 4x Redstone Repeater\\n- 1x Redstone Torch\\n- 1x Sheep\": \"在这里写些什么...\\n使用 '-' 来创建可复制的列表项，比如这样：\\n\\n羊羊快♂乐机\\n- 4x 半砖\\n- 1x 船\\n- 4x 红石粉\\n- 1x 黏性活塞\\n- 1x 末地烛\\n- 4x 红石中继器\\n- 1x 红石火把\\n- 1x 绵羊\",\n  \"Network\": \"网络\",\n  \"Overlay: Floating Image\": \"叠加面板：悬浮图像\",\n  \"Unpin from taskbar\": \"从任务栏取消固定\",\n  \"Sound input\": \"声音输入\",\n  \"Not connected\": \"未连接\",\n  \"Use macOS-like symbols for mods keys\": \"为修饰键使用 macOS 风格的图标\",\n  \"Use symbols for mouse\": \"使用图标表示鼠标键\",\n  \"Fonts\": \"字体\",\n  \"Circle\": \"圈选\",\n  \"Wallpaper selector\": \"壁纸选择器\",\n  \"(Plugged in)\": \"（电源已接通）\",\n  \"Sound effects\": \"声音效果\",\n  \"Intensity\": \"强度\",\n  \"Close window\": \"关闭窗口\",\n  \"Video Recording Path\": \"视频录制路径\",\n  \"Speakers (%1): %2\": \"扬声器 (%1): %2\",\n  \"Perhaps what you're listening to is too niche\": \"也许你听的音乐太小众了\",\n  \"Animate time change\": \"时间变化动画\",\n  \"Set FPS limit\": \"设置帧数限制\",\n  \"Identify Music\": \"识别音乐\",\n  \"Focusing\": \"专注\",\n  \"Sound output\": \"声音输出\",\n  \"Type /key to get started with online models\\nCtrl+O to expand sidebar\\nCtrl+P to pin sidebar\\nCtrl+D to detach sidebar\": \"输入 /key 来开始使用在线模型\\nCtrl+O 拓宽侧边栏\\nCtrl+P 固定侧边栏\\nCtrl+D 分离侧边栏\",\n  \"Parallax\": \"视差效果\",\n  \"EasyEffects\": \"EasyEffects\",\n  \"Show only when locked\": \"仅在锁屏时显示\",\n  \"Date style\": \"日期样式\",\n  \"Font family name (e.g., Google Sans Flex)\": \"字体名称（如 Google Sans Flex）\",\n  \"+%1 notifications\": \"+%1 个通知\",\n  \"Hide clipboard images copied from sussy sources\": \"隐藏剪贴板中来自奇奇怪怪来源的图片\",\n  \"Use Hyprlock (instead of Quickshell)\": \"使用 Hyprlock（而非 Quickshell）\",\n  \"Darken screen\": \"屏幕变暗\",\n  \"Generating...\\nDon't close this window!\": \"生成中...\\n请勿关闭此窗口！\",\n  \"Dial style\": \"表盘样式\",\n  \"Anti-flashbang\": \"防高亮保护\",\n  \"Please charge!\\nAutomatic suspend triggers at %1%\": \"请充电！\\n将在电量为 %1% 时自动挂起\",\n  \"Battery full\": \"电量已充满\",\n  \"More Bluetooth settings\": \"更多蓝牙设置\",\n  \"Center icons\": \"图标居中\",\n  \"Generate\\nTypically takes 2 minutes\": \"生成\\n通常需要 2 分钟\",\n  \"Overlay: Crosshair\": \"叠加面板：准星\",\n  \"You can also manually edit cheatsheet.superKey\": \"你也可以手动编辑 cheatsheet.superKey 配置项\",\n  \"Layers\": \"显示层\",\n  \"Sign out\": \"注销\",\n  \"Android\": \"安卓\",\n  \"Recognize music | Right-click to toggle source\": \"识别音乐 | 右键以切换源\",\n  \"Why this is cool:\\nFor non-0 values, it won't trigger when you reach the\\nscreen corner along the horizontal edge, but it will when\\nyou do along the vertical edge\": \"为什么这很酷：\\n对于非 0 的数值，当你沿着水平边缘抵达屏幕角时，它不会触发；\\n但当你沿着垂直边缘抵达屏幕角时，它就会触发\",\n  \"Locale code, e.g. fr_FR, de_DE, zh_CN...\": \"语言代码，如 fr_FR、de_DE、zh_CN 等\",\n  \"of %1\": \"共 %1\",\n  \"Clock style (locked)\": \"时钟样式（锁屏时）\",\n  \"City name\": \"城市名\",\n  \"Make sure you have songrec installed\": \"请确保你已安装 songrec\",\n  \"Windows\": \"窗口\",\n  \"Power Profile\": \"电源模式\",\n  \"Used for code and terminal\": \"用于代码和终端\",\n  \"Select language\": \"选择语言\",\n  \"File Explorer\": \"文件资源管理器\",\n  \"Saved    \": \"已保存    \",\n  \"Not secured\": \"不安全\",\n  \"Overlay: General\": \"叠加面板：通用\",\n  \"Keybind font size\": \"快捷键字体大小\",\n  \"Nothing\": \"空空如也\",\n  \"Main font\": \"主字体\",\n  \"End session\": \"结束专注\",\n  \"Listening...\": \"正在听取...\",\n  \"Font family name (e.g., Readex Pro)\": \"字体名称（如 Readex Pro）\",\n  \"Fill\": \"填充\",\n  \"Dark Mode\": \"深色模式\",\n  \"Restart\": \"重启\",\n  \"Dots\": \"圆点\",\n  \"%1 mins\": \"%1 分钟\",\n  \"Font used for Nerd Font icons\": \"用于 Nerd Font 图标的字体\",\n  \"Region selector (screen snipping/Google Lens)\": \"区域选择器（屏幕截图与 Google 智能镜头）\",\n  \"Battery: %1%2\": \"电池状态：%1%2\",\n  \"Click to cycle through power profiles\": \"点击以循环切换电源模式\",\n  \"When the previous option is off and this is on,\\nyou can still hover the corner's end to open sidebar,\\nand the remaining area can be used for volume/brightness scroll\": \"当上一个选项为关闭且此项开启时，你仍然\\n可以通过悬停在角落的末端来打开侧边栏，\\n剩余的区域将可用于滚动调节音量与亮度\",\n  \"Widgets\": \"小组件\",\n  \"On-screen keyboard\": \"屏幕键盘\",\n  \"Used for general UI text\": \"用于通用 UI 文本\",\n  \"Line\": \"线条\",\n  \"Replace 󱕐   for \\\"Scroll ↓\\\", 󱕑   \\\"Scroll ↑\\\", L󰍽   \\\"LMB\\\", R󰍽   \\\"RMB\\\", 󱕒   \\\"Scroll ↑/↓\\\" and ⇞/⇟ for \\\"Page_↑/↓\\\"\": \"如用 󱕐   来表示 “Scroll ↓”，󱕑   “Scroll ↑”，L󰍽   “LMB”，R󰍽   “RMB”，以及 󱕒   “Scroll ↑/↓” 和 ⇞/⇟ 来表示 “Page_↑/↓”\",\n  \"Unmuted\": \"已打开\",\n  \"Path copied\": \"路径已复制\",\n  \"Uses Gemini to categorize the wallpaper then picks a preset based on it.\\nYou'll need to set Gemini API key on the left sidebar first.\\nImages are downscaled for performance, but just to be safe,\\ndo not select wallpapers with sensitive information.\": \"使用 Gemini 对壁纸进行分类，然后根据分类选择一个预设。\\n你需要先在左侧边栏设置 Gemini API 密钥。\\n图片会被降低分辨率以提高性能, 但为了安全起见，\\n请勿选择包含敏感信息的壁纸。\",\n  \"Unread indicator: show count\": \"未读指示器：显示数量\",\n  \"RAM\": \"内存\",\n  \"Saving...\": \"保存中...\",\n  \"Illegal increment\": \"超过最大增量限制\",\n  \"\\nLMB to enable/disable\\nRMB to toggle size\\nScroll to swap position\": \"\\n左键以启用/禁用\\n右键以切换尺寸\\n滚动以交换位置\",\n  \"Health:\": \"电池健康：\",\n  \"Display modifiers and keys in multiple keycap (e.g., \\\"Ctrl + A\\\" instead of \\\"Ctrl A\\\" or \\\"󰘴 + A\\\" instead of \\\"󰘴 A\\\")\": \"使用多个“键帽”显示修饰键和按键（如显示为 “Ctrl + A” 而非 “Ctrl A”，或 “󰘴 + A” 而非 “󰘴 A”）\",\n  \"Cookie clock settings\": \"曲奇时钟设置\",\n  \"Eye protection\": \"护眼选项\",\n  \"Tooltips\": \"悬停提示\",\n  \"See fewer\": \"查看更少\",\n  \"Click to show\": \"点击以显示\",\n  \"Circle selection\": \"圈定选区\",\n  \"Enter a valid number\": \"请输入有效的数字\",\n  \"Music Recognition\": \"音乐识别\",\n  \"Sounds\": \"提示音\",\n  \"Input device\": \"输入设备\",\n  \"On\": \"开\",\n  \"Hide sussy/anime wallpapers\": \"隐藏可疑或动漫壁纸\",\n  \"Full\": \"完整\",\n  \"Image source\": \"图像来源\",\n  \"Night Light\": \"夜间模式\",\n  \"Digital clock settings\": \"数字时钟设置\",\n  \"e.g. 󰘴  for Ctrl, 󰘵  for Alt, 󰘶  for Shift, etc\": \"如用 󰘴  来表示 Ctrl，󰘵  来表示 Alt，󰘶  来表示 Shift 等\",\n  \"Use system file picker\": \"使用系统文件选择器\",\n  \"Show hidden icons\": \"显示隐藏的图标\",\n  \"Exceeded max allowed\": \"已超过最高限制\",\n  \"Please unplug the charger\": \"请拔掉充电器\",\n  \"Numbers\": \"数字\",\n  \"Example use case: eroge on one workspace, dark Discord window on another\": \"使用示例：在一个工作区玩小黄游，另一个工作区开着深色的 Discord 窗口\",\n  \"Enabled\": \"已打开\",\n  \"Recognize music\": \"识别音乐\",\n  \"Digital\": \"数字\",\n  \"Audio input | Right-click for volume mixer & device selector\": \"音频输入 | 右键打开音量合成器与设备选择器\",\n  \"Use old sine wave cookie implementation\": \"使用旧版正弦波形曲奇实现\",\n  \"Used for displaying numbers\": \"用于显示数字\",\n  \"Music Recognized\": \"识别到歌曲\",\n  \"Numbers font\": \"数字字体\",\n  \"Media\": \"媒体\",\n  \"Quick toggles\": \"快捷设置\",\n  \"Copy path\": \"复制路径\",\n  \"Screenshot Path (leave empty to just copy)\": \"屏幕截图路径（留空则只复制）\",\n  \"Draggable\": \"可移动\",\n  \"Off\": \"关\",\n  \"Super key symbol\": \"Super 键图标\",\n  \"Normal\": \"正常\",\n  \"Scroll to Bottom\": \"滚动到底部\",\n  \"Audio output\": \"音频输出\",\n  \"Use varying shapes for password characters\": \"使用多样形状显示密码字符\",\n  \"Hour marks\": \"时标\",\n  \"Night Light | Right-click to configure\": \"夜间模式 | 右键以配置\",\n  \"Edit quick toggles\": \"编辑快捷设置\",\n  \"Total duration timeout (s)\": \"总持续时长（秒）\",\n  \"Font family name\": \"字体名称\",\n  \"Rectangular selection\": \"矩形选区\",\n  \"Sides\": \"边数\",\n  \"Stroke width\": \"笔画粗细\",\n  \"Widget: Clock\": \"小组件：时钟\",\n  \"Audio input\": \"音频输入\",\n  \"Polling interval (s)\": \"轮询间隔（秒）\",\n  \"Used for decorative/expressive text\": \"用于装饰性或富有表现力的文字\",\n  \"Enable translator\": \"启用翻译\",\n  \"Pin to taskbar\": \"固定到任务栏\",\n  \"Active\": \"已启用\",\n  \"Used for headings and titles\": \"用于标题和副标题\",\n  \"More volume settings\": \"更多音量设置\",\n  \"Minute hand\": \"分针\",\n  \"Enable if you want clocks to show seconds accurately\": \"启用以让时钟精准显示秒数\",\n  \"Keep awake\": \"保持唤醒\",\n  \"Local account\": \"本地账户\",\n  \"Save paths\": \"保存路径\",\n  \"Open recordings folder\": \"打开录像文件夹\",\n  \"Muted\": \"已静音\",\n  \"Sliders\": \"滑块\",\n  \"CPU\": \"CPU\",\n  \"Second precision\": \"精确显秒\",\n  \"Border\": \"内圈\",\n  \"Reading font\": \"阅读字体\",\n  \"Press Super+G to open the overlay and pin the crosshair\": \"按 Super+G 来打开叠加面板，然后固定准星\",\n  \"Show\": \"显示\",\n  \"More Internet settings\": \"更多 Internet 设置\",\n  \"Get the latest features and security improvements with\\nthe newest feature update.\\n\\n%1 packages\": \"通过安装更新获取最新的功能和\\n安全改进。\\n\\n%1 个软件包\",\n  \"Quote\": \"语录\",\n  \"Widget: Weather\": \"小组件：天气\",\n  \"Used for reading large blocks of text\": \"用于阅读大段文字\",\n  \"with vertical offset\": \"使用垂直偏移\",\n  \"Han chars\": \"汉字\",\n  \"e.g. 󱊫 for F1, 󱊶  for F12\": \"如用 󱊫 来表示 F1，󱊶  来表示 F12 等\",\n  \"Internet\": \"网络\",\n  \"Show notifications\": \"显示通知\",\n  \"Force hover open at absolute corner\": \"强制在绝对角落悬停打开\",\n  \"Use symbols for function keys\": \"使用符号表示功能键\",\n  \"Record\": \"屏幕录制\",\n  \"Authentication\": \"身份验证\",\n  \"Hint target regions\": \"建议目标区域\",\n  \"Enable now\": \"现在启用\",\n  \"You'll need to enter your Gemini API key first.\\nType /key on the sidebar for instructions.\": \"你需要先输入 Gemini API 密钥。\\n在侧边栏输入 /key 以获取说明。\",\n  \"Output device\": \"输出设备\",\n  \"Swap\": \"虚拟内存\",\n  \"Full warning\": \"满电警告\",\n  \"Padding\": \"额外边距\",\n  \"Expressive font\": \"表现力字体\",\n  \"Balance brightness based on content\": \"根据内容更改亮度\",\n  \"Cookie\": \"曲奇\",\n  \"Fahrenheit unit\": \"华氏度单位\",\n  \"Roman\": \"罗马\",\n  \"Polling interval (m)\": \"轮询间隔（分钟）\",\n  \"Close all windows\": \"关闭所有窗口\",\n  \"Adjust the color temperature\": \"调整色温\",\n  \"Task View\": \"任务视图\",\n  \"More comfortable viewing at night\": \"夜间浏览更舒适\",\n  \"No new notifications\": \"没有新通知\",\n  \"All\": \"全部\",\n  \"Move to front\": \"移到前面\",\n  \"Emoji\": \"表情符号\",\n  \"Best match\": \"最佳匹配\",\n  \"Other\": \"其他\",\n  \"Unpin from Start\": \"从“开始”屏幕取消固定\",\n  \"Polkit\": \"Polkit\",\n  \"Productivity\": \"效率\",\n  \"Web\": \"网页\",\n  \"Apps\": \"应用\",\n  \"Manage accounts\": \"管理账户\",\n  \"Commands\": \"命令\",\n  \"Do you want to allow this app to make changes to your device?\": \"你要允许此应用对你的设备进行更改吗？\",\n  \"Actions\": \"操作\",\n  \"Open\": \"打开\",\n  \"Pinned\": \"已固定\",\n  \"Move right\": \"右移\",\n  \"Command\": \"命令\",\n  \"Utilities & Tools\": \"实用工具\",\n  \"Change password\": \"更改密码\",\n  \"Command-line-invoked Action\": \"由命令行执行的操作\",\n  \"Unknown Application\": \"未知应用\",\n  \"No applications\": \"没有应用\",\n  \"Creativity\": \"创意\",\n  \"Move left\": \"左移\",\n  \"Pin to Start\": \"固定到“开始”屏幕\",\n  \"Pin\": \"固定\",\n  \"Unpin\": \"取消固定\"\n}"
  },
  {
    "path": "dots/.config/quickshell/ii/welcome.qml",
    "content": "//@ pragma UseQApplication\n//@ pragma Env QS_NO_RELOAD_POPUP=1\n//@ pragma Env QT_QUICK_CONTROLS_STYLE=Basic\n//@ pragma Env QT_QUICK_FLICKABLE_WHEEL_DECELERATION=10000\n\n// Adjust this to make the app smaller or larger\n//@ pragma Env QT_SCALE_FACTOR=1\n\nimport QtQuick\nimport QtQuick.Controls\nimport QtQuick.Layouts\nimport QtQuick.Window\nimport Quickshell\nimport Quickshell.Io\nimport qs.services\nimport qs.modules.common\nimport qs.modules.common.widgets\nimport qs.modules.common.functions\n\nApplicationWindow {\n    id: root\n    property string firstRunFilePath: FileUtils.trimFileProtocol(`${Directories.state}/user/first_run.txt`)\n    property string firstRunFileContent: \"This file is just here to confirm you've been greeted :>\"\n    property real contentPadding: 8\n    property bool showNextTime: false\n    visible: true\n    onClosing: {\n        Quickshell.execDetached([\"notify-send\", Translation.tr(\"Welcome app\"), Translation.tr(\"Enjoy! You can reopen the welcome app any time with <tt>Super+Shift+Alt+/</tt>. To open the settings app, hit <tt>Super+I</tt>\"), \"-a\", \"Shell\"]);\n        Qt.quit();\n    }\n    title: Translation.tr(\"illogical-impulse Welcome\")\n\n    Component.onCompleted: {\n        MaterialThemeLoader.reapplyTheme();\n        Config.readWriteDelay = 0 // Welcome app always only sets one var at a time so delay isn't needed\n    }\n\n    minimumWidth: 600\n    minimumHeight: 400\n    width: 900\n    height: 650\n    color: Appearance.m3colors.m3background\n\n    Process {\n        id: konachanWallProc\n        property string status: \"\"\n        command: [\"bash\", \"-c\", Quickshell.shellPath(\"scripts/colors/random/random_konachan_wall.sh\")]\n        stdout: SplitParser {\n            onRead: data => {\n                console.log(`Konachan wall proc output: ${data}`);\n                konachanWallProc.status = data.trim();\n            }\n        }\n    }\n\n    Process {\n        id: translationProc\n        property string locale: \"\"\n        command: [Directories.aiTranslationScriptPath, translationProc.locale]\n    }\n\n    ColumnLayout {\n        anchors {\n            fill: parent\n            margins: contentPadding\n        }\n\n        Item {\n            // Titlebar\n            visible: Config.options?.windows.showTitlebar\n            Layout.fillWidth: true\n            implicitHeight: Math.max(welcomeText.implicitHeight, windowControlsRow.implicitHeight)\n            StyledText {\n                id: welcomeText\n                anchors {\n                    left: Config.options.windows.centerTitle ? undefined : parent.left\n                    horizontalCenter: Config.options.windows.centerTitle ? parent.horizontalCenter : undefined\n                    verticalCenter: parent.verticalCenter\n                    leftMargin: 12\n                }\n                color: Appearance.colors.colOnLayer0\n                text: Translation.tr(\"Hi there! First things first...\")\n                font {\n                    family: Appearance.font.family.title\n                    pixelSize: Appearance.font.pixelSize.title\n                    variableAxes: Appearance.font.variableAxes.title\n                }\n            }\n            RowLayout { // Window controls row\n                id: windowControlsRow\n                anchors.verticalCenter: parent.verticalCenter\n                anchors.right: parent.right\n                StyledText {\n                    font.pixelSize: Appearance.font.pixelSize.smaller\n                    text: Translation.tr(\"Show next time\")\n                }\n                StyledSwitch {\n                    id: showNextTimeSwitch\n                    checked: root.showNextTime\n                    scale: 0.6\n                    Layout.alignment: Qt.AlignVCenter\n                    onCheckedChanged: {\n                        if (checked) {\n                            Quickshell.execDetached([\"rm\", root.firstRunFilePath]);\n                        } else {\n                            Quickshell.execDetached([\"bash\", \"-c\", `echo '${StringUtils.shellSingleQuoteEscape(root.firstRunFileContent)}' > '${StringUtils.shellSingleQuoteEscape(root.firstRunFilePath)}'`]);\n                        }\n                    }\n                }\n                RippleButton {\n                    buttonRadius: Appearance.rounding.full\n                    implicitWidth: 35\n                    implicitHeight: 35\n                    onClicked: root.close()\n                    contentItem: MaterialSymbol {\n                        anchors.centerIn: parent\n                        horizontalAlignment: Text.AlignHCenter\n                        text: \"close\"\n                        iconSize: 20\n                    }\n\n                    StyledToolTip {\n                        text: Translation.tr(\"Tip: Close a window with Super+Q\")\n                    }\n                }\n            }\n        }\n\n        Rectangle {\n            // Content container\n            color: Appearance.m3colors.m3surfaceContainerLow\n            radius: Appearance.rounding.windowRounding - root.contentPadding\n            implicitHeight: contentColumn.implicitHeight\n            implicitWidth: contentColumn.implicitWidth\n            Layout.fillWidth: true\n            Layout.fillHeight: true\n\n            ContentPage {\n                id: contentColumn\n                anchors.fill: parent\n\n                ContentSection {\n                    Layout.fillWidth: true\n                    icon: \"language\"\n                    title: Translation.tr(\"Language\")\n\n                    ContentSubsection {\n                        title: Translation.tr(\"Select language\")\n                        ConfigSelectionArray {\n                            id: languageSelector\n                            currentValue: Config.options.language.ui\n                            onSelected: newValue => {\n                                Config.options.language.ui = newValue;\n                            }\n                            options: [\n                                {\n                                    displayName: Translation.tr(\"Auto (System)\"),\n                                    value: \"auto\"\n                                },\n                                ...Translation.allAvailableLanguages.map(lang => {\n                                    return {\n                                        displayName: lang,\n                                        value: lang\n                                    };\n                                })]\n                        }\n                    }\n\n                    NoticeBox {\n                        Layout.fillWidth: true\n                        text: Translation.tr(\"Language not listed or incomplete translations?\\nYou can choose to generate translations for it with Gemini.\\n1. Open the left sidebar with Super+A, set model to Gemini (if it isn't already)\\n2. Type /key, hit Enter and follow the instructions\\n3. Type /key YOUR_API_KEY\\n4. Type the locale of your language below and press Generate\")\n                    }\n\n                    ContentSubsection {\n                        title: Translation.tr(\"Generate translation with Gemini\")\n                        \n                        ConfigRow {\n                            MaterialTextArea {\n                                id: localeInput\n                                Layout.fillWidth: true\n                                placeholderText: Translation.tr(\"Locale code, e.g. fr_FR, de_DE, zh_CN...\")\n                                text: Config.options.language.ui === \"auto\" ? Qt.locale().name : Config.options.language.ui\n                            }\n                            RippleButtonWithIcon {\n                                id: generateTranslationBtn\n                                Layout.fillHeight: true\n                                nerdIcon: \"\"\n                                enabled: !translationProc.running || (translationProc.locale !== localeInput.text.trim())\n                                mainText: enabled ? Translation.tr(\"Generate\\nTypically takes 2 minutes\") : Translation.tr(\"Generating...\\nDon't close this window!\")\n                                onClicked: {\n                                    translationProc.locale = localeInput.text.trim();\n                                    translationProc.running = false;\n                                    translationProc.running = true;\n                                }\n                            }\n                        }\n                    }\n                }\n\n                ContentSection {\n                    icon: \"screenshot_monitor\"\n                    title: Translation.tr(\"Bar\")\n\n                    ConfigRow {\n                        ContentSubsection {\n                            title: Translation.tr(\"Bar position\")\n                            ConfigSelectionArray {\n                                currentValue: (Config.options.bar.bottom ? 1 : 0) | (Config.options.bar.vertical ? 2 : 0)\n                                onSelected: newValue => {\n                                    Config.options.bar.bottom = (newValue & 1) !== 0;\n                                    Config.options.bar.vertical = (newValue & 2) !== 0;\n                                }\n                                options: [\n                                    {\n                                        displayName: Translation.tr(\"Top\"),\n                                        icon: \"arrow_upward\",\n                                        value: 0 // bottom: false, vertical: false\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Left\"),\n                                        icon: \"arrow_back\",\n                                        value: 2 // bottom: false, vertical: true\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Bottom\"),\n                                        icon: \"arrow_downward\",\n                                        value: 1 // bottom: true, vertical: false\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Right\"),\n                                        icon: \"arrow_forward\",\n                                        value: 3 // bottom: true, vertical: true\n                                    }\n                                ]\n                            }\n                        }\n                        ContentSubsection {\n                            title: Translation.tr(\"Bar style\")\n\n                            ConfigSelectionArray {\n                                currentValue: Config.options.bar.cornerStyle\n                                onSelected: newValue => {\n                                    Config.options.bar.cornerStyle = newValue; // Update local copy\n                                }\n                                options: [\n                                    {\n                                        displayName: Translation.tr(\"Hug\"),\n                                        icon: \"line_curve\",\n                                        value: 0\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Float\"),\n                                        icon: \"page_header\",\n                                        value: 1\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Rect\"),\n                                        icon: \"toolbar\",\n                                        value: 2\n                                    }\n                                ]\n                            }\n                        }\n                    }\n                }\n\n                ContentSection {\n                    icon: \"format_paint\"\n                    title: Translation.tr(\"Style & wallpaper\")\n\n                    ButtonGroup {\n                        Layout.alignment: Qt.AlignHCenter\n                        LightDarkPreferenceButton {\n                            dark: false\n                        }\n                        LightDarkPreferenceButton {\n                            dark: true\n                        }\n                    }\n\n                    RowLayout {\n                        Layout.alignment: Qt.AlignHCenter\n                        RippleButtonWithIcon {\n                            id: rndWallBtn\n                            visible: Config.options.policies.weeb === 1\n                            Layout.alignment: Qt.AlignHCenter\n                            buttonRadius: Appearance.rounding.small\n                            materialIcon: \"ifl\"\n                            mainText: konachanWallProc.running ? Translation.tr(\"Be patient...\") : Translation.tr(\"Random: Konachan\")\n                            onClicked: {\n                                console.log(konachanWallProc.command.join(\" \"));\n                                konachanWallProc.running = true;\n                            }\n                            StyledToolTip {\n                                text: Translation.tr(\"Random SFW Anime wallpaper from Konachan\\nImage is saved to ~/Pictures/Wallpapers\")\n                            }\n                        }\n                        RippleButtonWithIcon {\n                            materialIcon: \"wallpaper\"\n                            StyledToolTip {\n                                text: Translation.tr(\"Pick wallpaper image on your system\")\n                            }\n                            onClicked: {\n                                Quickshell.execDetached([`${Directories.wallpaperSwitchScriptPath}`]);\n                            }\n                            mainContentComponent: Component {\n                                RowLayout {\n                                    spacing: 10\n                                    StyledText {\n                                        font.pixelSize: Appearance.font.pixelSize.small\n                                        text: Translation.tr(\"Choose file\")\n                                        color: Appearance.colors.colOnSecondaryContainer\n                                    }\n                                    RowLayout {\n                                        spacing: 3\n                                        KeyboardKey {\n                                            key: \"Ctrl\"\n                                        }\n                                        KeyboardKey {\n                                            key: \"󰖳\"\n                                        }\n                                        StyledText {\n                                            Layout.alignment: Qt.AlignVCenter\n                                            text: \"+\"\n                                        }\n                                        KeyboardKey {\n                                            key: \"T\"\n                                        }\n                                    }\n                                }\n                            }\n                        }\n                    }\n\n                    NoticeBox {\n                        Layout.fillWidth: true\n                        text: Translation.tr(\"Change any time later with /dark, /light, /wallpaper in the launcher\\nIf the shell's colors aren't changing:\\n    1. Open the right sidebar with Super+N\\n    2. Click \\\"Reload Hyprland & Quickshell\\\" in the top-right corner\")\n                    }\n                }\n\n                ContentSection {\n                    icon: \"rule\"\n                    title: Translation.tr(\"Policies\")\n\n                    ConfigRow {\n                        Layout.fillWidth: true\n\n                        ContentSubsection {\n                            title: \"Weeb\"\n\n                            ConfigSelectionArray {\n                                currentValue: Config.options.policies.weeb\n                                onSelected: newValue => {\n                                    Config.options.policies.weeb = newValue;\n                                }\n                                options: [\n                                    {\n                                        displayName: Translation.tr(\"No\"),\n                                        icon: \"close\",\n                                        value: 0\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Yes\"),\n                                        icon: \"check\",\n                                        value: 1\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Closet\"),\n                                        icon: \"ev_shadow\",\n                                        value: 2\n                                    }\n                                ]\n                            }\n                        }\n\n                        ContentSubsection {\n                            title: \"AI\"\n\n                            ConfigSelectionArray {\n                                currentValue: Config.options.policies.ai\n                                onSelected: newValue => {\n                                    Config.options.policies.ai = newValue;\n                                }\n                                options: [\n                                    {\n                                        displayName: Translation.tr(\"No\"),\n                                        icon: \"close\",\n                                        value: 0\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Yes\"),\n                                        icon: \"check\",\n                                        value: 1\n                                    },\n                                    {\n                                        displayName: Translation.tr(\"Local only\"),\n                                        icon: \"sync_saved_locally\",\n                                        value: 2\n                                    }\n                                ]\n                            }\n                        }\n                    }\n                }\n\n                ContentSection {\n                    icon: \"info\"\n                    title: Translation.tr(\"Info\")\n\n                    Flow {\n                        Layout.fillWidth: true\n                        spacing: 5\n\n                        RippleButtonWithIcon {\n                            materialIcon: \"keyboard_alt\"\n                            onClicked: {\n                                Quickshell.execDetached([\"qs\", \"-p\", Quickshell.shellPath(\"\"), \"ipc\", \"call\", \"cheatsheet\", \"toggle\"]);\n                            }\n                            mainContentComponent: Component {\n                                RowLayout {\n                                    spacing: 10\n                                    StyledText {\n                                        font.pixelSize: Appearance.font.pixelSize.small\n                                        text: Translation.tr(\"Keybinds\")\n                                        color: Appearance.colors.colOnSecondaryContainer\n                                    }\n                                    RowLayout {\n                                        spacing: 3\n                                        KeyboardKey {\n                                            key: \"󰖳\"\n                                        }\n                                        StyledText {\n                                            Layout.alignment: Qt.AlignVCenter\n                                            text: \"+\"\n                                        }\n                                        KeyboardKey {\n                                            key: \"/\"\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                        RippleButtonWithIcon {\n                            materialIcon: \"help\"\n                            mainText: Translation.tr(\"Usage\")\n                            onClicked: {\n                                Qt.openUrlExternally(\"https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/02usage/\");\n                            }\n                        }\n                        RippleButtonWithIcon {\n                            materialIcon: \"construction\"\n                            mainText: Translation.tr(\"Configuration\")\n                            onClicked: {\n                                Qt.openUrlExternally(\"https://end-4.github.io/dots-hyprland-wiki/en/ii-qs/03config/\");\n                            }\n                        }\n                    }\n                }\n\n                ContentSection {\n                    icon: \"monitoring\"\n                    title: Translation.tr(\"Useless buttons\")\n\n                    Flow {\n                        Layout.fillWidth: true\n                        spacing: 5\n\n                        RippleButtonWithIcon {\n                            nerdIcon: \"󰊤\"\n                            mainText: Translation.tr(\"GitHub\")\n                            onClicked: {\n                                Qt.openUrlExternally(\"https://github.com/end-4/dots-hyprland\");\n                            }\n                        }\n                        RippleButtonWithIcon {\n                            materialIcon: \"favorite\"\n                            mainText: \"Funny number\"\n                            onClicked: {\n                                Qt.openUrlExternally(\"https://github.com/sponsors/end-4\");\n                            }\n                        }\n                    }\n                }\n\n                Item {\n                    Layout.fillWidth: true\n                    Layout.fillHeight: true\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "dots/.config/starship.toml",
    "content": "# Don't print a new line at the start of the prompt\nadd_newline = false\n# Pipes ╰─ ╭─\n# Powerline symbols                                     \n# Wedges 🭧🭒 🭣🭧🭓\n# Random noise 🬖🬥🬔🬗\n# Cool stuff 󰜥   \n\n# format = \"\"\"\n# $directory $fill $git_branch $cmd_duration\n#  $character\"\"\"\nformat = \"\"\"\n$cmd_duration $directory$git_branch\n  $character\"\"\"\n\n[fill]\nsymbol = '-'\nstyle = 'fg:245'\n\n# Replace the \"❯\" symbol in the prompt with \"➜\"\n[character]                            # The name of the module we are configuring is \"character\"\nsuccess_symbol = \"[ ](bold fg:243)\"\nerror_symbol = \"[ ](bold fg:244)\"\n\n# Disable the package module, hiding it from the prompt completely\n[package]\ndisabled = true\n\n[git_branch]\nstyle = \"bg: 252\"\nsymbol = \"󰘬\"\ntruncation_length = 12\ntruncation_symbol = \"\"\nformat = \" 󰜥 [](bold fg:252)[$symbol $branch(:$remote_branch)](fg:235 bg:252)[ ](bold fg:252)\"\n\n[git_commit]\ncommit_hash_length = 4\ntag_symbol = \" \"\n\n[git_state]\nformat = '[\\($state( $progress_current of $progress_total)\\)]($style) '\ncherry_pick = \"[🍒 PICKING](bold red)\"\n\n[git_status]\nconflicted = \" 🏳 \"\nahead = \" 🏎💨 \"\nbehind = \" 😰 \"\ndiverged = \" 😵 \"\nuntracked = \" 🤷 ‍\"\nstashed = \" 📦 \"\nmodified = \" 📝 \"\nstaged = '[++\\($count\\)](green)'\nrenamed = \" ✍️ \"\ndeleted = \" 🗑 \"\n\n[hostname]\nssh_only = false\nformat =  \"[•$hostname](bg:252 bold fg:235)[](bold fg:252)\"\ntrim_at = \".companyname.com\"\ndisabled = false\n\n[line_break]\ndisabled = false\n\n[memory_usage]\ndisabled = true\nthreshold = -1\nsymbol = \" \"\nstyle = \"bold dimmed green\"\n\n[time]\ndisabled = true\nformat = '🕙[\\[ $time \\]]($style) '\ntime_format = \"%T\"\n\n[username]\nstyle_user = \"bold bg:252 fg:235\"\nstyle_root = \"red bold\"\nformat = \"[](bold fg:252)[$user]($style)\"\ndisabled = false\nshow_always = true\n\n[directory]\nhome_symbol = \" \"\nread_only = \"  \"\nstyle = \"bg:255 fg:240\"\ntruncation_length = 2\ntruncation_symbol = \".../\"\nformat = '[](bold fg:255)[󰉋 → $path]($style)[](bold fg:255)'\n\n\n[directory.substitutions]\n\"Desktop\" = \"  \"\n\"Documents\" = \"  \"\n\"Downloads\" = \"  \"\n\"Music\" = \" 󰎈 \"\n\"Pictures\" = \"  \"\n\"Videos\" = \"  \"\n\"GitHub\" = \" 󰊤 \"\n\n[cmd_duration]\nmin_time = 0\nformat = '[](bold fg:252)[󰪢 $duration](bold bg:252 fg:235)[](bold fg:252)'\n"
  },
  {
    "path": "dots/.config/thorium-flags.conf",
    "content": "--password-store=gnome-libsecret\n# --ozone-platform-hint=wayland\n--gtk-version=4\n--ignore-gpu-blocklist\n--enable-features=TouchpadOverscrollHistoryNavigation\n"
  },
  {
    "path": "dots/.config/wlogout/layout",
    "content": "{\n    \"label\" : \"lock\",\n    \"action\" : \"loginctl lock-session\",\n    \"text\" : \"lock\",\n    \"keybind\" : \"l\"\n}\n{\n    \"label\" : \"hibernate\",\n    \"action\" : \"systemctl hibernate || loginctl hibernate\",\n    \"text\" : \"downloading\",\n    \"keybind\" : \"h\"\n}\n{\n    \"label\" : \"logout\",\n    \"action\" : \"hyprctl clients -j | jq -r '.[].pid' | xargs kill; pkill Hyprland || pkill sway || pkill niri || loginctl terminate-user $USER\",\n    \"text\" : \"logout\",\n    \"keybind\" : \"e\"\n}\n{\n    \"label\" : \"shutdown\",\n    \"action\" : \"hyprctl clients -j | jq -r '.[].pid' | xargs kill; systemctl poweroff || loginctl poweroff\",\n    \"text\" : \"power_settings_new\",\n    \"keybind\" : \"s\"\n}\n{\n    \"label\" : \"suspend\",\n    \"action\" : \"systemctl suspend || loginctl suspend\",\n    \"text\" : \"bedtime\",\n    \"keybind\" : \"u\"\n}\n{\n    \"label\" : \"reboot\",\n    \"action\" : \"hyprctl clients -j | jq -r '.[].pid' | xargs kill; systemctl reboot || loginctl reboot\",\n    \"text\" : \"restart_alt\",\n    \"keybind\" : \"r\"\n}\n"
  },
  {
    "path": "dots/.config/wlogout/style.css",
    "content": "* {\n\tall: unset;\n\tbackground-image: none;\n\ttransition: 400ms cubic-bezier(0.05, 0.7, 0.1, 1);\n}\n\nwindow {\n\tbackground: rgba(0, 0, 0, 0.5);\n}\n\nbutton {\n\tfont-family: 'Material Symbols Outlined';\n\tfont-size: 10rem;\n\tbackground-color: rgba(11, 11, 11, 0.4);\n\tcolor: #FFFFFF;\n\tmargin: 2rem;\n\tborder-radius: 2rem;\n\tpadding: 3rem;\n}\n\nbutton:focus,\nbutton:active,\nbutton:hover {\n\tbackground-color: rgba(51, 51, 51, 0.5);\n\tborder-radius: 4rem;\n}"
  },
  {
    "path": "dots/.config/xdg-desktop-portal/hyprland-portals.conf",
    "content": "[preferred]\ndefault = hyprland;gtk\norg.freedesktop.impl.portal.FileChooser = kde\n"
  },
  {
    "path": "dots/.config/zshrc.d/auto-Hypr.sh",
    "content": "# Auto start Hyprland on tty1\nif [ -z \"$DISPLAY\" ] && [ \"$XDG_VTNR\" -eq 1 ]; then\n  mkdir -p ~/.cache\n  exec start-hyprland > ~/.cache/hyprland.log 2>&1\nfi\n"
  },
  {
    "path": "dots/.config/zshrc.d/dots-hyprland.zsh",
    "content": "# Use the generated color scheme\n\nif test -f ~/.local/state/quickshell/user/generated/terminal/sequences.txt; then\n    cat ~/.local/state/quickshell/user/generated/terminal/sequences.txt\nfi\n"
  },
  {
    "path": "dots/.config/zshrc.d/shortcuts.zsh",
    "content": "# Created by newuser for 5.9\n\nbindkey '^H' backward-kill-word \nbindkey '^Z' undo\n"
  },
  {
    "path": "dots/.local/share/konsole/Profile 1.profile",
    "content": "[Appearance]\nColorScheme=MaterialYou\n\n[General]\nCommand=/bin/fish\nEnvironment=COLORTERM=truecolor\nName=Profile 1\nParent=FALLBACK/\n\n[Keyboard]\nKeyBindings=default\n\n"
  },
  {
    "path": "dots-extra/emacs/material-theme.el",
    "content": ";;; material-theme.el --- Theme using Matugen SCSS variables\n\n;; Copyright (C) 2025 \n\n;; Author: Generated (Improved)\n;; Version: 1.2\n;; Package-Requires: ((emacs \"24.1\"))\n;; Keywords: faces\n\n;;; Commentary:\n\n;; A theme using Matugen SCSS variables with quality of life improvements:\n;; - Better source block distinction\n;; - Improved text visibility when selected\n;; - Refined org-mode styling with hidden asterisks\n;; - Enhanced contrast and readability\n;; - Seamless integration of source blocks with consistent styling\n\n;;; Code:\n\n(deftheme material \"Theme using Matugen SCSS variables with quality of life improvements.\")\n\n;; Define function to read SCSS variables\n(defun material-get-color-from-scss (var-name)\n  \"Extract color value for VAR-NAME from material_colors.scss file.\"\n  (let* ((scss-file (expand-file-name \"~/.local/state/quickshell/user/generated/material_colors.scss\"))\n         (scss-content (with-temp-buffer\n                         (insert-file-contents scss-file)\n                         (buffer-string)))\n         (var-pattern (concat \"\\\\$\" var-name \":\\\\s-+\\\\(#[0-9a-fA-F]\\\\{6\\\\}\\\\);\"))\n         (match (string-match var-pattern scss-content)))\n    (if match\n        (match-string 1 scss-content)\n      (message \"Could not find color variable: %s\" var-name)\n      \"#000000\")))\n\n;; Define function to adjust color brightness\n(defun material-adjust-color (hex-color factor)\n  \"Adjust HEX-COLOR brightness by FACTOR (0-2).\nValues < 1 darken, values > 1 lighten.\"\n  (let* ((r (string-to-number (substring hex-color 1 3) 16))\n         (g (string-to-number (substring hex-color 3 5) 16))\n         (b (string-to-number (substring hex-color 5 7) 16))\n         (adjust-channel (lambda (channel)\n                           (max 0 (min 255 (round (* channel factor))))))\n         (new-r (funcall adjust-channel r))\n         (new-g (funcall adjust-channel g))\n         (new-b (funcall adjust-channel b)))\n    (format \"#%02x%02x%02x\" new-r new-g new-b)))\n\n;; Define all the color variables\n(let* ((bg (material-get-color-from-scss \"background\"))\n      (err (material-get-color-from-scss \"error\"))\n      (err-container (material-get-color-from-scss \"errorContainer\"))\n      (inverse-on-surface (material-get-color-from-scss \"inverseOnSurface\"))\n      (inverse-primary (material-get-color-from-scss \"inversePrimary\"))\n      (inverse-surface (material-get-color-from-scss \"inverseSurface\"))\n      (on-background (material-get-color-from-scss \"onBackground\"))\n      (on-err (material-get-color-from-scss \"onError\"))\n      (on-err-container (material-get-color-from-scss \"onErrorContainer\"))\n      (on-primary (material-get-color-from-scss \"onPrimary\"))\n      (on-primary-container (material-get-color-from-scss \"onPrimaryContainer\"))\n      (on-primary-fixed (material-get-color-from-scss \"onPrimaryFixed\"))\n      (on-primary-fixed-variant (material-get-color-from-scss \"onPrimaryFixedVariant\"))\n      (on-secondary (material-get-color-from-scss \"onSecondary\"))\n      (on-secondary-container (material-get-color-from-scss \"onSecondaryContainer\"))\n      (on-secondary-fixed (material-get-color-from-scss \"onSecondaryFixed\"))\n      (on-secondary-fixed-variant (material-get-color-from-scss \"onSecondaryFixedVariant\"))\n      (on-surface (material-get-color-from-scss \"onSurface\"))\n      (on-surface-variant (material-get-color-from-scss \"onSurfaceVariant\"))\n      (on-tertiary (material-get-color-from-scss \"onTertiary\"))\n      (on-tertiary-container (material-get-color-from-scss \"onTertiaryContainer\"))\n      (on-tertiary-fixed (material-get-color-from-scss \"onTertiaryFixed\"))\n      (on-tertiary-fixed-variant (material-get-color-from-scss \"onTertiaryFixedVariant\"))\n      (outline-color (material-get-color-from-scss \"outline\"))\n      (outline-variant (material-get-color-from-scss \"outlineVariant\"))\n      (primary (material-get-color-from-scss \"primary\"))\n      (primary-container (material-get-color-from-scss \"primaryContainer\"))\n      (primary-fixed (material-get-color-from-scss \"primaryFixed\"))\n      (primary-fixed-dim (material-get-color-from-scss \"primaryFixedDim\"))\n      (scrim (material-get-color-from-scss \"scrim\"))\n      (secondary (material-get-color-from-scss \"secondary\"))\n      (secondary-container (material-get-color-from-scss \"secondaryContainer\"))\n      (secondary-fixed (material-get-color-from-scss \"secondaryFixed\"))\n      (secondary-fixed-dim (material-get-color-from-scss \"secondaryFixedDim\"))\n      (shadow (material-get-color-from-scss \"shadow\"))\n      (surface (material-get-color-from-scss \"surface\"))\n      (surface-bright (material-get-color-from-scss \"surfaceBright\"))\n      (surface-container (material-get-color-from-scss \"surfaceContainer\"))\n      (surface-container-high (material-get-color-from-scss \"surfaceContainerHigh\"))\n      (surface-container-highest (material-get-color-from-scss \"surfaceContainerHighest\"))\n      (surface-container-low (material-get-color-from-scss \"surfaceContainerLow\"))\n      (surface-container-lowest (material-get-color-from-scss \"surfaceContainerLowest\"))\n      (surface-dim (material-get-color-from-scss \"surfaceDim\"))\n      (surface-tint (material-get-color-from-scss \"surfaceTint\"))\n      (surface-variant (material-get-color-from-scss \"surfaceVariant\"))\n      (tertiary (material-get-color-from-scss \"tertiary\"))\n      (tertiary-container (material-get-color-from-scss \"tertiaryContainer\"))\n      (tertiary-fixed (material-get-color-from-scss \"tertiaryFixed\"))\n      (tertiary-fixed-dim (material-get-color-from-scss \"tertiaryFixedDim\"))\n      (success (material-get-color-from-scss \"success\"))\n      (on-success (material-get-color-from-scss \"onSuccess\"))\n      (success-container (material-get-color-from-scss \"successContainer\"))\n      (on-success-container (material-get-color-from-scss \"onSuccessContainer\"))\n      (term0 (material-get-color-from-scss \"term0\"))\n      (term1 (material-get-color-from-scss \"term1\"))\n      (term2 (material-get-color-from-scss \"term2\"))\n      (term3 (material-get-color-from-scss \"term3\"))\n      (term4 (material-get-color-from-scss \"term4\"))\n      (term5 (material-get-color-from-scss \"term5\"))\n      (term6 (material-get-color-from-scss \"term6\"))\n      (term7 (material-get-color-from-scss \"term7\"))\n      (term8 (material-get-color-from-scss \"term8\"))\n      (term9 (material-get-color-from-scss \"term9\"))\n      (term10 (material-get-color-from-scss \"term10\"))\n      (term11 (material-get-color-from-scss \"term11\"))\n      (term12 (material-get-color-from-scss \"term12\"))\n      (term13 (material-get-color-from-scss \"term13\"))\n      (term14 (material-get-color-from-scss \"term14\"))\n      (term15 (material-get-color-from-scss \"term15\")))\n\n  (custom-theme-set-faces\n   'material\n   ;; Basic faces\n   `(default ((t (:background ,bg :foreground ,on-background))))\n   `(cursor ((t (:background ,primary))))\n   `(highlight ((t (:background ,primary-container :foreground ,on-primary-container))))\n   `(region ((t (:background ,primary-container :foreground ,on-primary-container :extend t))))\n   `(secondary-selection ((t (:background ,secondary-container :foreground ,on-secondary-container :extend t))))\n   `(isearch ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold))))\n   `(lazy-highlight ((t (:background ,secondary-container :foreground ,on-secondary-container))))\n   `(vertical-border ((t (:foreground ,surface-variant))))\n   `(border ((t (:background ,surface-variant :foreground ,surface-variant))))\n   `(fringe ((t (:background ,surface :foreground ,outline-variant))))\n   `(shadow ((t (:foreground ,outline-variant))))\n   `(link ((t (:foreground ,primary :underline t))))\n   `(link-visited ((t (:foreground ,tertiary :underline t))))\n   `(success ((t (:foreground ,success))))\n   `(warning ((t (:foreground ,secondary))))\n   `(error ((t (:foreground ,err))))\n   `(match ((t (:background ,secondary-container :foreground ,on-secondary-container))))\n   \n   ;; Font-lock\n   `(font-lock-builtin-face ((t (:foreground ,primary))))\n   `(font-lock-comment-face ((t (:foreground ,outline-color :slant italic))))\n   `(font-lock-comment-delimiter-face ((t (:foreground ,outline-variant))))\n   `(font-lock-constant-face ((t (:foreground ,tertiary :weight bold))))\n   `(font-lock-doc-face ((t (:foreground ,on-surface-variant :slant italic))))\n   `(font-lock-function-name-face ((t (:foreground ,primary :weight bold))))\n   `(font-lock-keyword-face ((t (:foreground ,secondary :weight bold))))\n   `(font-lock-string-face ((t (:foreground ,tertiary))))\n   `(font-lock-type-face ((t (:foreground ,primary-fixed))))\n   `(font-lock-variable-name-face ((t (:foreground ,on-surface))))\n   `(font-lock-warning-face ((t (:foreground ,err :weight bold))))\n   `(font-lock-preprocessor-face ((t (:foreground ,secondary-fixed-dim))))\n   `(font-lock-negation-char-face ((t (:foreground ,tertiary-fixed))))\n\n   ;; Show paren\n   `(show-paren-match ((t (:background ,primary-container :foreground ,on-primary-container :weight bold))))\n   `(show-paren-mismatch ((t (:background ,err-container :foreground ,on-err-container :weight bold))))\n   \n   ;; Mode line - improved status bar styling\n   `(mode-line ((t (:background ,surface-container :foreground ,on-surface :box nil))))\n   `(mode-line-inactive ((t (:background ,surface :foreground ,on-surface-variant :box nil))))\n   `(mode-line-buffer-id ((t (:foreground ,primary :weight bold))))\n   `(mode-line-emphasis ((t (:foreground ,primary :weight bold))))\n   `(mode-line-highlight ((t (:foreground ,primary :box nil))))\n   \n   ;; Improved Source blocks - make them integrated with the theme\n   `(org-block ((t (:background ,surface-container-low :extend t :inherit fixed-pitch))))\n   `(org-block-begin-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch))))\n   `(org-block-end-line ((t (:background ,surface-container-low :foreground ,primary-fixed-dim :extend t :slant italic :inherit fixed-pitch))))\n   `(org-code ((t (:background ,surface-container-low :foreground ,tertiary-fixed :inherit fixed-pitch))))\n   `(org-verbatim ((t (:background ,surface-container-low :foreground ,primary-fixed :inherit fixed-pitch))))\n   `(org-meta-line ((t (:foreground ,outline-color :slant italic))))\n   \n   ;; Org mode with hidden asterisks\n   `(org-level-1 ((t (:foreground ,primary :weight bold :height 1.2))))\n   `(org-level-2 ((t (:foreground ,primary-container :weight bold :height 1.1))))\n   `(org-level-3 ((t (:foreground ,secondary :weight bold))))\n   `(org-level-4 ((t (:foreground ,secondary-container :weight bold))))\n   `(org-level-5 ((t (:foreground ,tertiary :weight bold))))\n   `(org-level-6 ((t (:foreground ,tertiary-container :weight bold))))\n   `(org-level-7 ((t (:foreground ,primary-fixed :weight bold))))\n   `(org-level-8 ((t (:foreground ,primary-fixed-dim :weight bold))))\n   `(org-document-title ((t (:foreground ,primary :weight bold :height 1.3))))\n   `(org-document-info ((t (:foreground ,primary-container))))\n   `(org-todo ((t (:foreground ,err :weight bold))))\n   `(org-done ((t (:foreground ,success :weight bold))))\n   `(org-headline-done ((t (:foreground ,on-surface-variant))))\n   `(org-hide ((t (:foreground ,bg)))) ;; Hide leading asterisks\n   `(org-ellipsis ((t (:foreground ,tertiary :underline nil)))) ;; Style for folded content indicator\n   `(org-table ((t (:foreground ,secondary-fixed :inherit fixed-pitch))))\n   `(org-formula ((t (:foreground ,tertiary :inherit fixed-pitch))))\n   `(org-checkbox ((t (:foreground ,primary :weight bold :inherit fixed-pitch))))\n   `(org-date ((t (:foreground ,secondary-fixed :underline t))))\n   `(org-special-keyword ((t (:foreground ,on-surface-variant :slant italic))))\n   `(org-tag ((t (:foreground ,outline-color :weight normal))))\n   \n   ;; Magit\n   `(magit-section-highlight ((t (:background ,surface-container-low))))\n   `(magit-diff-hunk-heading ((t (:background ,surface-container :foreground ,on-surface-variant))))\n   `(magit-diff-hunk-heading-highlight ((t (:background ,surface-container-high :foreground ,on-surface))))\n   `(magit-diff-context ((t (:foreground ,on-surface-variant))))\n   `(magit-diff-context-highlight ((t (:background ,surface-container-low :foreground ,on-surface))))\n   `(magit-diff-added ((t (:background ,success-container :foreground ,on-success-container))))\n   `(magit-diff-added-highlight ((t (:background ,success-container :foreground ,on-success-container :weight bold))))\n   `(magit-diff-removed ((t (:background ,err-container :foreground ,on-err-container))))\n   `(magit-diff-removed-highlight ((t (:background ,err-container :foreground ,on-err-container :weight bold))))\n   `(magit-hash ((t (:foreground ,outline-color))))\n   `(magit-branch-local ((t (:foreground ,tertiary :weight bold))))\n   `(magit-branch-remote ((t (:foreground ,primary :weight bold))))\n   \n   ;; Company\n   `(company-tooltip ((t (:background ,surface-container :foreground ,on-surface))))\n   `(company-tooltip-selection ((t (:background ,primary-container :foreground ,on-primary-container))))\n   `(company-tooltip-common ((t (:foreground ,primary))))\n   `(company-tooltip-common-selection ((t (:foreground ,on-primary-container :weight bold))))\n   `(company-tooltip-annotation ((t (:foreground ,tertiary))))\n   `(company-scrollbar-fg ((t (:background ,primary))))\n   `(company-scrollbar-bg ((t (:background ,surface-variant))))\n   `(company-preview ((t (:foreground ,on-surface-variant :slant italic))))\n   `(company-preview-common ((t (:foreground ,primary :slant italic))))\n   \n   ;; Ido\n   `(ido-first-match ((t (:foreground ,primary :weight bold))))\n   `(ido-only-match ((t (:foreground ,tertiary :weight bold))))\n   `(ido-subdir ((t (:foreground ,secondary))))\n   `(ido-indicator ((t (:foreground ,err))))\n   `(ido-virtual ((t (:foreground ,outline-color))))\n   \n   ;; Helm\n   `(helm-selection ((t (:background ,primary-container :foreground ,on-primary-container))))\n   `(helm-match ((t (:foreground ,primary :weight bold))))\n   `(helm-source-header ((t (:background ,surface-container-high :foreground ,primary :weight bold :height 1.1))))\n   `(helm-candidate-number ((t (:foreground ,tertiary :weight bold))))\n   `(helm-ff-directory ((t (:foreground ,primary :weight bold))))\n   `(helm-ff-file ((t (:foreground ,on-surface))))\n   `(helm-ff-executable ((t (:foreground ,tertiary))))\n   \n   ;; Which-key\n   `(which-key-key-face ((t (:foreground ,primary :weight bold))))\n   `(which-key-separator-face ((t (:foreground ,outline-variant))))\n   `(which-key-command-description-face ((t (:foreground ,on-surface))))\n   `(which-key-group-description-face ((t (:foreground ,secondary))))\n   `(which-key-special-key-face ((t (:foreground ,tertiary :weight bold))))\n   \n   ;; Line numbers\n   `(line-number ((t (:foreground ,outline-variant :inherit fixed-pitch))))\n   `(line-number-current-line ((t (:foreground ,primary :weight bold :inherit fixed-pitch))))\n   \n   ;; Parenthesis matching\n   `(sp-show-pair-match-face ((t (:background ,primary-container :foreground ,on-primary-container))))\n   `(sp-show-pair-mismatch-face ((t (:background ,err-container :foreground ,on-err-container))))\n   \n   ;; Rainbow delimiters\n   `(rainbow-delimiters-depth-1-face ((t (:foreground ,primary))))\n   `(rainbow-delimiters-depth-2-face ((t (:foreground ,secondary))))\n   `(rainbow-delimiters-depth-3-face ((t (:foreground ,tertiary))))\n   `(rainbow-delimiters-depth-4-face ((t (:foreground ,primary-fixed))))\n   `(rainbow-delimiters-depth-5-face ((t (:foreground ,secondary-fixed))))\n   `(rainbow-delimiters-depth-6-face ((t (:foreground ,tertiary-fixed))))\n   `(rainbow-delimiters-depth-7-face ((t (:foreground ,primary-fixed-dim))))\n   `(rainbow-delimiters-depth-8-face ((t (:foreground ,secondary-fixed-dim))))\n   `(rainbow-delimiters-depth-9-face ((t (:foreground ,tertiary-fixed-dim))))\n   `(rainbow-delimiters-mismatched-face ((t (:foreground ,err :weight bold))))\n   `(rainbow-delimiters-unmatched-face ((t (:foreground ,err :weight bold))))\n   \n   ;; Dired\n   `(dired-directory ((t (:foreground ,primary :weight bold))))\n   `(dired-ignored ((t (:foreground ,outline-variant))))\n   `(dired-flagged ((t (:foreground ,err))))\n   `(dired-marked ((t (:foreground ,tertiary :weight bold))))\n   `(dired-symlink ((t (:foreground ,secondary :slant italic))))\n   `(dired-header ((t (:foreground ,primary :weight bold :height 1.1))))\n   \n   ;; Terminal colors\n   `(term-color-black ((t (:foreground ,term0 :background ,term0))))\n   `(term-color-red ((t (:foreground ,term1 :background ,term1))))\n   `(term-color-green ((t (:foreground ,term2 :background ,term2))))\n   `(term-color-yellow ((t (:foreground ,term3 :background ,term3))))\n   `(term-color-blue ((t (:foreground ,term4 :background ,term4))))\n   `(term-color-magenta ((t (:foreground ,term5 :background ,term5))))\n   `(term-color-cyan ((t (:foreground ,term6 :background ,term6))))\n   `(term-color-white ((t (:foreground ,term7 :background ,term7))))\n   \n   ;; EShell\n   `(eshell-prompt ((t (:foreground ,primary :weight bold))))\n   `(eshell-ls-directory ((t (:foreground ,primary :weight bold))))\n   `(eshell-ls-symlink ((t (:foreground ,secondary :slant italic))))\n   `(eshell-ls-executable ((t (:foreground ,tertiary))))\n   `(eshell-ls-archive ((t (:foreground ,on-tertiary-container))))\n   `(eshell-ls-backup ((t (:foreground ,outline-variant))))\n   `(eshell-ls-clutter ((t (:foreground ,err))))\n   `(eshell-ls-missing ((t (:foreground ,err))))\n   `(eshell-ls-product ((t (:foreground ,on-surface-variant))))\n   `(eshell-ls-readonly ((t (:foreground ,on-surface-variant))))\n   `(eshell-ls-special ((t (:foreground ,secondary-fixed))))\n   `(eshell-ls-unreadable ((t (:foreground ,outline-variant))))\n   \n   ;; Improved markdown mode\n   `(markdown-header-face ((t (:foreground ,primary :weight bold))))\n   `(markdown-header-face-1 ((t (:foreground ,primary :weight bold :height 1.2))))\n   `(markdown-header-face-2 ((t (:foreground ,primary-container :weight bold :height 1.1))))\n   `(markdown-header-face-3 ((t (:foreground ,secondary :weight bold))))\n   `(markdown-header-face-4 ((t (:foreground ,secondary-container :weight bold))))\n   `(markdown-inline-code-face ((t (:foreground ,tertiary-fixed :background ,surface-container-low :inherit fixed-pitch))))\n   `(markdown-code-face ((t (:background ,surface-container-low :extend t :inherit fixed-pitch))))\n   `(markdown-pre-face ((t (:background ,surface-container-low :inherit fixed-pitch))))\n   `(markdown-table-face ((t (:foreground ,secondary-fixed :inherit fixed-pitch))))\n   \n   ;; Web mode\n   `(web-mode-html-tag-face ((t (:foreground ,primary))))\n   `(web-mode-html-tag-bracket-face ((t (:foreground ,on-surface-variant))))\n   `(web-mode-html-attr-name-face ((t (:foreground ,secondary))))\n   `(web-mode-html-attr-value-face ((t (:foreground ,tertiary))))\n   `(web-mode-css-selector-face ((t (:foreground ,primary))))\n   `(web-mode-css-property-name-face ((t (:foreground ,secondary))))\n   `(web-mode-css-string-face ((t (:foreground ,tertiary))))\n   \n   ;; Flycheck\n   `(flycheck-error ((t (:underline (:style wave :color ,err)))))\n   `(flycheck-warning ((t (:underline (:style wave :color ,secondary)))))\n   `(flycheck-info ((t (:underline (:style wave :color ,tertiary)))))\n   `(flycheck-fringe-error ((t (:foreground ,err))))\n   `(flycheck-fringe-warning ((t (:foreground ,secondary))))\n   `(flycheck-fringe-info ((t (:foreground ,tertiary))))\n   \n   ;; Mini-buffer customization\n   `(minibuffer-prompt ((t (:foreground ,primary :weight bold))))\n   \n   ;; Improved search highlighting\n   `(lsp-face-highlight-textual ((t (:background ,primary-container :foreground ,on-primary-container :weight bold))))\n   `(lsp-face-highlight-read ((t (:background ,secondary-container :foreground ,on-secondary-container :weight bold))))\n   `(lsp-face-highlight-write ((t (:background ,tertiary-container :foreground ,on-tertiary-container :weight bold))))\n   \n   ;; Info and help modes\n   `(info-title-1 ((t (:foreground ,primary :weight bold :height 1.3))))\n   `(info-title-2 ((t (:foreground ,primary-container :weight bold :height 1.2))))\n   `(info-title-3 ((t (:foreground ,secondary :weight bold :height 1.1))))\n   `(info-title-4 ((t (:foreground ,secondary-container :weight bold))))\n   `(Info-quoted ((t (:foreground ,tertiary))))\n   `(info-menu-header ((t (:foreground ,primary :weight bold))))\n   `(info-menu-star ((t (:foreground ,primary))))\n   `(info-node ((t (:foreground ,tertiary :weight bold))))\n   \n   ;; Fixed-pitch faces\n   `(fixed-pitch ((t (:family \"monospace\"))))\n   `(fixed-pitch-serif ((t (:family \"monospace serif\"))))\n   \n   ;; Variable-pitch face\n   `(variable-pitch ((t (:family \"sans serif\"))))\n   ))\n\n;; Add org-mode hooks for hiding leading stars\n(with-eval-after-load 'org\n  (setq org-hide-leading-stars t)\n  (setq org-startup-indented t))\n\n;;;###autoload\n(when load-file-name\n  (add-to-list 'custom-theme-load-path\n               (file-name-as-directory (file-name-directory load-file-name))))\n\n(provide-theme 'material)\n;;; material-theme.el ends here\n"
  },
  {
    "path": "dots-extra/fcitx5/conf/classicui.conf",
    "content": "# Vertical Candidate List\nVertical Candidate List=False\n# Use mouse wheel to go to prev or next page\nWheelForPaging=True\n# Font\nFont=\"Google Sans Flex 11\"\n# Menu Font\nMenuFont=\"Google Sans Flex 11\"\n# Tray Font\nTrayFont=\"Google Sans Flex 11\"\n# Prefer Text Icon\nPreferTextIcon=False\n# Show Layout Name In Icon\nShowLayoutNameInIcon=True\n# Use input method language to display text\nUseInputMethodLanguageToDisplayText=True\n# Theme\nTheme=plasma\n# Dark Theme\nDarkTheme=plasma\n# Follow system light/dark color scheme\nUseDarkTheme=False\n# Follow system accent color if it is supported by theme and desktop\nUseAccentColor=True\n# Use Per Screen DPI on X11\nPerScreenDPI=False\n# Force font DPI on Wayland\nForceWaylandDPI=0\n# Enable fractional scale under Wayland\nEnableFractionalScale=True\n\n"
  },
  {
    "path": "dots-extra/fedora/hypr/hyprland/execs.conf",
    "content": "# Bar, wallpaper\nexec-once = ~/.config/hypr/hyprland/scripts/start_geoclue_agent.sh\nexec-once = qs -c $qsConfig &\n\n# Input method\n# exec-once = fcitx5\n\n# Core components (authentication, lock screen, notification daemon)\nexec-once = gnome-keyring-daemon --start --components=secrets\nexec-once = hypridle\nexec-once = dbus-update-activation-environment --all\nexec-once = sleep 1 && dbus-update-activation-environment --systemd WAYLAND_DISPLAY XDG_CURRENT_DESKTOP # Some fix idk\n\n# Audio\nexec-once = easyeffects --gapplication-service\n\n# Clipboard: history\n# exec-once = wl-paste --watch cliphist store &\nexec-once = wl-paste --type text --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update'\nexec-once = wl-paste --type image --watch bash -c 'cliphist store && qs -c $qsConfig ipc call cliphistService update'\n\n# Cursor\nexec-once = hyprctl setcursor Bibata-Modern-Classic 24\n\n# Fix dock pinned apps not launching properly (https://github.com/end-4/dots-hyprland/issues/2200)\n# exec-once = sleep 3.5 && hyprctl reload && sleep 0.5 && touch ~/.config/quickshell/ii/shell.qml\n\n# For fedora to setup polkit\nexec-once = /usr/libexec/kf6/polkit-kde-authentication-agent-1\n"
  },
  {
    "path": "dots-extra/fontsets/ar/fonts.conf",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE fontconfig SYSTEM \"urn:fontconfig:fonts.dtd\">\n<fontconfig>\n  <match target=\"font\">\n    <edit name=\"rgba\" mode=\"assign\">\n      <const>none</const>\n    </edit>\n  </match>\n\n  <!-- Fix for: arabic fonts rendering in Noto Nastaliq Urdu | affects Chromium, Discord (maybe all chromium based apps, but not Spotify somehow) -->\n  <match target=\"pattern\">\n    <test compare=\"eq\" name=\"family\">\n      <string>sans-serif</string>\n    </test>\n    <edit name=\"family\" mode=\"prepend\" binding=\"strong\">\n      <string>Noto Sans Arabic</string>\n    </edit>\n  </match>\n</fontconfig>\n"
  },
  {
    "path": "dots-extra/swaylock/config",
    "content": "daemonize\nignore-empty-password\nfont=Rubik\nfont-size=23\n\nclock\ntimestr=%R\n#datestr=%a, %e of %B\n\ncolor=000000dd\n\n#fade-in=0.1\n#effect-blur=20x2\n#effect-greyscale\n#effect-scale=0.3\n\nindicator\nindicator-radius=80\nindicator-thickness=10\nindicator-caps-lock\n\nkey-hl-color=d4d4d4\ncaps-lock-key-hl-color=d4d4d4\n\nseparator-color=00000000\n\ninside-color=000000ff\ninside-clear-color=00000099\ninside-caps-lock-color=00000099\ninside-ver-color=00000099\ninside-wrong-color=00000099\n\nring-color=1c1c1c\nring-clear-color=1c1c1c\nring-caps-lock-color=1c1c1c\nring-ver-color=1c1c1c\nring-wrong-color=400000\n\nline-color=00000000\nline-clear-color=d4d4d4\nline-caps-lock-color=d4d4d4\nline-ver-color=d9d8d8FF\nline-wrong-color=ee2e24FF\n\ntext-color=c9c9c9\ntext-clear-color=c9c9c9\ntext-ver-color=c9c9c9\ntext-wrong-color=c9c9c9\n\nbs-hl-color=470400\ncaps-lock-bs-hl-color=470400\n# caps-lock-key-hl-color=ffd204FF\n# caps-lock-bs-hl-color=ee2e24FF\n# disable-caps-lock-text\ntext-caps-lock-color=d4d4d4\n"
  },
  {
    "path": "dots-extra/via-nix/README.md",
    "content": "This folder contains tweakd configs when --via-nix is specified.\n"
  },
  {
    "path": "dots-extra/via-nix/hypridle.conf",
    "content": "$lock_cmd = swaylock -c 000000\n# $lock_cmd = pidof hyprlock || hyprlock\n$suspend_cmd = systemctl suspend || loginctl suspend\n\ngeneral {\n    lock_cmd = $lock_cmd\n    before_sleep_cmd = loginctl lock-session\n    after_sleep_cmd = hyprctl dispatch global quickshell:lockFocus\n    inhibit_sleep = 3\n}\n\nlistener {\n    timeout = 300 # 5mins\n    on-timeout = loginctl lock-session\n}\n\nlistener {\n    timeout = 600 # 10mins\n    on-timeout = hyprctl dispatch dpms off\n    on-resume = hyprctl dispatch dpms on\n}\n\nlistener {\n    timeout = 900 # 15mins\n    on-timeout = $suspend_cmd\n}\n"
  },
  {
    "path": "licenses/LGPL-3.0.txt",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "licenses/MIT.txt",
    "content": "MIT License\n\nCopyright <YEAR> <COPYRIGHT HOLDER>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "licenses/README.md",
    "content": "# Licenses\n\nThis repository contains code from other repositories. Files containing such code should include a license notice, and a copy should be stored in this folder.\n"
  },
  {
    "path": "sdata/README.md",
    "content": "This folder mainly contains data for the script `setup`.\n\n- TODO: output the logs to a temp file, then show the path of the file so users will be able to read it again or upload it to issue.\n- TODO: unify the message output via functions (for example `log_error`, `log_warning`).\n"
  },
  {
    "path": "sdata/deps-info.md",
    "content": "This file contains information about the dependencies.\n\nIt mainly describes about `sdata/dist-arch` which is actively maintained by the devs.\n\nTips:\n- The packages which name has prefix `illogical-impulse-` are defined with local files `PKGBUILD`. There're two types:\n  - **Meta packages**, which do not have actual content but only include other packages specified in the array `depends`.\n  - **Actual packages**, which not only install dependencies listed in `depends`, but also build packages which have actual content to be installed later.\n- For each package included in the local `PKGBUILD`s which name does **not** have prefix `illogical-impulse-`, for example `rsync`, it's either from [Arch Linux Packages](https://archlinux.org/packages) or the [AUR](https://aur.archlinux.org/packages). Search the package name on them to get the info (e.g. what executable(s) the package provides).\n\n# Meta packages\n## illogical-impulse-audio\n- `cava`\n  - Used in Quickshell config.\n- `pavucontrol-qt`\n  - Used in Hyprland and Quickshell config.\n- `wireplumber`\n  - Not explicitly used.\n- `pipewire-pulse`\n  - not explicitly used.\n- `libdbusmenu-gtk3`\n  - not explicitly used.\n- `playerctl`\n  - Used in Hyprland and Quickshell config.\n\n## illogical-impulse-backlight\n- `geoclue`\n  - Which demo agent used in Quickshell config.\n- `brightnessctl`\n  - Used in Hyprland and Quickshell config.\n- `ddcutil`\n  - Used in Quickshell config.\n\n## illogical-impulse-basic\n- `bc`\n  - Used in `quickshell/ii/scripts/colors/switchwall.sh` for example.\n- `coreutils`\n  - Too many executables involved, not sure where been used.\n- `cliphist`\n  - Used in Hyprland and Quickshell config.\n- `cmake`\n  - Used in building quickshell and MicroTeX.\n- `curl`\n  - Used in Quickshell config.\n- `wget`\n  - Used in Quickshell config.\n- `ripgrep`\n  - Not sure where been used.\n- `jq`\n  - Widely used.\n- `xdg-user-dirs`\n  - Used in Hyprland and Quickshell config.\n- `rsync`\n  - Used in install script.\n- `go-yq`\n  - Used in install script.\n\n## illogical-impulse-fonts-themes\n- `adw-gtk-theme-git`\n  - [source](https://github.com/lassekongo83/adw-gtk3)\n  - Used in Quickshell config.\n- `breeze`\n  - Used in kdeglobals config.\n- `breeze-plus`\n  - [source](https://github.com/mjkim0727/breeze-plus)\n  - Used in kde-material-you-colors config.\n- `darkly-bin`\n  - `darkly` is supposed to be set as the theme for Qt apps, just have not figured out how to properly set it yet.\n- `eza`\n  - Used in Fish config: `alias ls 'eza --icons'`\n- `fish`\n  - Widely used.\n- `fontconfig`\n  - Basic component which is nearly a must.\n- `kitty`\n  - Used in fuzzel, Hyprland, kdeglobals and Quickshell config; kitty config is also included as dots.\n- `matugen-bin`\n  - Used in Quickshell.\n- `otf-space-grotesk`\n  - [source](https://events.ccc.de/congress/2024/infos/styleguide.html)\n  - Used in Quickshell and matugen config.\n- `starship`\n  - Used in Fish config.\n- `ttf-jetbrains-mono-nerd`\n  - Font name: `JetBrains Mono NF`, `JetBrainsMono Nerd Font`.\n  - Used in foot, kdeglobals, kitty, qt5ct, qt6ct and Quickshell config.\n- `ttf-material-symbols-variable-git`\n  - Font name: `Material Symbols Rounded`, `Material Symbols Outlined`\n  - Used in Hyprland, matugen, Quickshell and wlogout config.\n- `ttf-readex-pro`\n  - Font name: `Readex Pro`\n  - Used in Quickshell config.\n- `ttf-rubik-vf`\n  - Font name: `Rubik`, `Rubik Light`\n  - Used in Hyprland, kdeglobals, matugen, qt5ct, qt6ct and Quickshell config.\n- `ttf-twemoji`\n  - Not explicitly used, but it may help as fallback for displaying emoji characters.\n\n## illogical-impulse-hyprland\n- `hyprland`\n  - Surely needed.\n- `hyprsunset`\n  - Used in Quickshell config.\n- `wl-clipboard`\n  - Surely needed.\n\n## illogical-impulse-kde\n- `bluedevil`\n  - Provide command `kcmshell6 kcm_bluetooth` used by Quickshell bluetooth functionality.\n- `gnome-keyring`\n  - Provide executable `gnome-keyring-daemon`, used in Hyprland and Quickshell config.\n- `networkmanager`\n  - Basic component.\n- `plasma-nm`\n  - Provide command `kcmshell6 kcm_networkmanagement` used by Quickshell network functionality.\n- `polkit-kde-agent`\n  - Basic component.\n- `dolphin`\n  - Used in Hyprland and Quickshell config.\n- `systemsettings`\n  - Used in Hyprland `keybinds.conf`.\n\n\n## illogical-impulse-portal\n- `xdg-desktop-portal`\n  - Basic component.\n- `xdg-desktop-portal-kde`\n  - Basic component.\n- `xdg-desktop-portal-gtk`\n  - Basic component.\n- `xdg-desktop-portal-hyprland`\n  - Basic component.\n\n## illogical-impulse-python\n- `clang`\n  - Some python package may need this to be built, e.g. #1235. This may varies on different distros though.\n- `uv`\n  - Used for python venv.\n- `gtk4`\n  - Not explicitly used.\n- `libadwaita`\n  - Not explicitly used.\n- `libsoup3`\n  - Not explicitly used.\n- `libportal-gtk4`\n  - Not explicitly used.\n- `gobject-introspection`\n  - Not explicitly used.\n\n## illogical-impulse-screencapture\n- `hyprshot`\n  - Used in Hyprland `keybinds.conf` as fallback.\n- `slurp`\n  - Used in Hyprland and Quickshell config.\n- `swappy`\n  - Used in Quickshell config.\n- `tesseract`\n  - Used in Quickshell and Hyprland config.\n- `tesseract-data-eng`\n  - Used as data for tesseract.\n- `wf-recorder`\n  - Used in Quickshell config.\n\n\n## illogical-impulse-toolkit\n- `upower`\n  - Used in Quickshell config.\n- `wtype`\n  - Used in Hyprland `scripts/fuzzel-emoji.sh`\n- `ydotool`\n  - Used in Quickshell config.\n\n## illogical-impulse-widgets\n- `fuzzel`\n  - Used in Hyprland and Quickshell config; its config is also included.\n- `glib2`\n  - Provides executable `gsettings`\n  - Used in install script, also in matugen and quickshell config.\n- `imagemagick`\n  - Provides executable: `magick`\n  - Used in Quickshell config.\n- `hypridle`\n  - Used for loginctl to lock session.\n- `hyprlock`\n  - Installed as fallback; its config is also included.\n- `hyprpicker`\n  - Used in Hyprland and Quickshell config.\n- `songrec`\n  - Used in Quickshell config.\n- `translate-shell`\n  - Used in Quickshell config.\n- `wlogout`\n  - Used in Hyprland config.\n- `libqalculate`\n  - Used in Quickshell config, providing math ability in searchbar.\n  - Note that `qalc` is the needed executable. In Arch Linux [libqalculate](https://archlinux.org/packages/extra/x86_64/libqalculate) provides it, but in Fedora [qalculate](https://packages.fedoraproject.org/pkgs/libqalculate/qalculate/fedora-43.html#files) does and [libqalculate](https://packages.fedoraproject.org/pkgs/libqalculate/libqalculate/fedora-43.html#files) does not.\n\n\n# Actual packages\n## illogical-impulse-quickshell-git\n- Pinned commit.\n- Also with extra dependencies (mainly Qt things) needed by the illogical-impulse Quickshell config.\n\nExtra dependencies.\n- `qt6-base`\n- `qt6-declarative`\n- `qt6-5compat`\n- `qt6-avif-image-plugin`\n- `qt6-imageformats`\n- `qt6-multimedia`\n- `qt6-positioning`\n- `qt6-quicktimeline`\n- `qt6-sensors`\n- `qt6-svg`\n- `qt6-tools`\n- `qt6-translations`\n- `qt6-virtualkeyboard`\n- `qt6-wayland`\n- `kirigami`\n- `kdialog`\n- `syntax-highlighting`\n- `vulkan-headers`\n- `libdrm`\n- `cpptrace`\n- `jemalloc`\n- `mesa`\n\n## illogical-impulse-bibata-modern-classic-bin\n- [source](https://github.com/ful1e5/Bibata_Cursor)\n- Used in Hyprland config, not necessary.\n\n## illogical-impulse-microtex-git\n- [source](https://github.com/NanoMichael/MicroTeX)\n- This package will be installed as `/opt/MicroTeX`.\n"
  },
  {
    "path": "sdata/dist-arch/.gitignore",
    "content": "/*/*.tar.*\n/*/pkg/\n/*/src/\n"
  },
  {
    "path": "sdata/dist-arch/README.md",
    "content": "# Install scripts for Arch Linux\n\n- See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/)\n\n## Old Dependency Installation Method\nThe old deps install method mainly involved `./sdata/dependencies.conf` (which has been removed now).\n\n## Current Dependency Installation\nLocal PKGBUILDs under `./sdata/dist-arch/` are used to install dependencies.\n\nThe mechanism is introduced by [Makrennel](https://github.com/Makrennel) in [PR#570](https://github.com/end-4/dots-hyprland/pull/570).\n\nWhy is this awesome?\n- It makes it possible to control version since some packages may involve breaking changes from time to time.\n- It makes the dependency trackable for package manager, so that you always know why you have installed some package.\n- As a result, it enables a workable uninstall process.\n\nThe PKGBUILDs contains two forms of dependencies:\n- Package name written in dependencies, like a \"meta\" package.\n- Normal PKGBUILD content to build dependencies, e.g. AGS, which is often for version controlling.\n\n## Note\n- `pkgver()` should be removed from `PKGBUILD` cuz it will modify the `PKGBUILD` which is tracked by Git and should not be modified during building.\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-audio/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-audio\npkgver=1.0\npkgrel=3\npkgdesc='Illogical Impulse Audio Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  cava\n  pavucontrol-qt\n  wireplumber\n  pipewire-pulse\n  libdbusmenu-gtk3\n  playerctl\n)\n\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-backlight/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-backlight\npkgver=1.0\npkgrel=2\npkgdesc='Illogical Impulse Backlight Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  geoclue\n  brightnessctl\n  ddcutil\n)\n\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-basic/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-basic\npkgver=1.0\npkgrel=3\npkgdesc='Illogical Impulse Basic Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  bc\n  coreutils\n  cliphist\n  cmake\n  curl\n  wget\n  ripgrep\n  jq\n  xdg-user-dirs\n  # Used in install script\n  rsync\n  go-yq # https://github.com/mikefarah/yq\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-bibata-modern-classic-bin/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-bibata-modern-classic-bin\npkgver=2.0.6\npkgrel=2\npkgdesc=\"Material Based Cursor Theme, installed for illogical-impulse dotfiles\"\narch=('any')\nurl=\"https://github.com/ful1e5/Bibata_Cursor\"\nlicense=('GPL-3.0-or-later')\nconflicts=(\"bibata-cursor-theme\" \"bibata-cursor-theme-bin\")\noptions=('!strip')\n_variant=Bibata-Modern-Classic\nsource=(\"${pkgname%-bin}-$pkgver.tar.xz::$url/releases/download/v$pkgver/$_variant.tar.xz\")\nsha256sums=('SKIP')\n\npackage() {\n  install -dm755 \"$pkgdir/usr/share/icons\"\n  cp -dr --no-preserve=mode $_variant \"$pkgdir/usr/share/icons\"\n}\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-fonts-themes/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-fonts-themes\npkgver=1.0\npkgrel=6\npkgdesc='Illogical Impulse Fonts and Theming Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  adw-gtk-theme-git\n  breeze\n  breeze-plus\n  darkly-bin\n  eza\n  fish\n  fontconfig\n  kitty\n  matugen\n  otf-space-grotesk\n  starship\n  ttf-jetbrains-mono-nerd\n  ttf-material-symbols-variable-git\n  ttf-readex-pro\n  ttf-rubik-vf\n  ttf-twemoji\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-hyprland/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-hyprland\npkgver=1.0\npkgrel=5\npkgdesc='Illogical Impulse Hyprland relatated packages'\narch=(any)\nlicense=(None)\ndepends=(\n  hyprland\n  hyprsunset\n  wl-clipboard\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-kde/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-kde\npkgver=1.0\npkgrel=3\npkgdesc='Illogical Impulse KDE Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  bluedevil\n  gnome-keyring\n  networkmanager\n  plasma-nm\n  polkit-kde-agent\n  dolphin\n  systemsettings\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-microtex-git/.gitignore",
    "content": "/MicroTeX/\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-microtex-git/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-microtex-git\n_pkgname=MicroTeX\npkgver=r494.0e3707f\npkgrel=3\npkgdesc='MicroTeX for illogical-impulse dotfiles.'\n#pkgdesc=\"A dynamic, cross-platform, and embeddable LaTeX rendering library\"\narch=(\"x86_64\")\nurl=\"https://github.com/NanoMichael/${_pkgname}\"\nlicense=('MIT')\ndepends=(\n  tinyxml2\n  gtkmm3\n  gtksourceviewmm\n  cairomm\n)\nmakedepends=(\"git\" \"cmake\")\nsource=(\"git+${url}.git\")\nsha256sums=(\"SKIP\")\n\nprepare() {\n  cd $_pkgname\n  sed -i 's/gtksourceviewmm-3.0/gtksourceviewmm-4.0/' CMakeLists.txt\n  sed -i 's/tinyxml2.so.10/tinyxml2.so.11/' CMakeLists.txt\n}\n\nbuild() {\n  cd $_pkgname\n  cmake -B build -S . -DCMAKE_BUILD_TYPE=None\n  cmake --build build\n}\n\npackage() {\n  cd $_pkgname\n  install -Dm0755 -t \"$pkgdir/opt/$_pkgname/\" build/LaTeX\n  cp -r build/res \"$pkgdir/opt/$_pkgname/\"\n  install -Dm0644 -t \"$pkgdir/usr/share/licenses/$pkgname/\" LICENSE\n}\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-portal/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-portal\npkgver=1.0\npkgrel=3\npkgdesc='Illogical Impulse XDG Desktop Portals'\narch=(any)\nlicense=(None)\ndepends=(\n  xdg-desktop-portal\n  xdg-desktop-portal-kde\n  xdg-desktop-portal-gtk\n  xdg-desktop-portal-hyprland\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-python/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-python\npkgver=1.1\npkgrel=5\npkgdesc='Illogical Impulse Python Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  clang\n  uv\n  gtk4\n  libadwaita\n  libsoup3\n  libportal-gtk4\n  gobject-introspection\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-quickshell-git/.gitignore",
    "content": "/quickshell"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-quickshell-git/PKGBUILD",
    "content": "_commit='7511545ee20664e3b8b8d3322c0ffe7567c56f7a'\n# Useful links:\n# https://git.outfoxxed.me/quickshell/quickshell/commits/branch/master\n# https://aur.archlinux.org/packages/quickshell-git\n\ngroups=(illogical-impulse)\n_prefix='illogical-impulse'\n_pkgname=quickshell\npkgname=\"$_prefix-$_pkgname-git\"\npkgver=0.1.0.r1\npkgrel=8\npkgdesc=\"$_pkgname-git pinned commit and extra deps for $_prefix\"\narch=(x86_64 aarch64)\nurl='https://git.outfoxxed.me/quickshell/quickshell'\noptions=(!strip)\nlicense=('LGPL-3.0-only')\ndepends=(\n  'cpptrace'\n  'jemalloc'\n  'mesa'\n  'qt6-declarative'\n  'qt6-base'\n  'qt6-svg'\n  'libdrm'\n  'libpipewire'\n  'libxcb'\n  'wayland'\n  # NOTE: Below are custom dependencies of illogical-impulse\n  qt6-5compat \n  qt6-avif-image-plugin\n  qt6-imageformats\n  qt6-multimedia\n  qt6-positioning\n  qt6-quicktimeline\n  qt6-sensors\n  qt6-svg\n  qt6-tools\n  qt6-translations\n  qt6-virtualkeyboard\n  qt6-wayland\n  kirigami\n  kdialog\n  syntax-highlighting\n)\nmakedepends=(\n  'cli11'\n  'cmake'\n  'git'\n  'ninja'\n  'qt6-shadertools'\n  'spirv-tools'\n  'vulkan-headers'\n  'wayland'\n  'wayland-protocols'\n)\nprovides=(\"$_pkgname\" \"$_pkgname-git\")\nconflicts=(\"$_pkgname\" \"$_pkgname-git\")\n\n_pkgsrc=\"$_pkgname\"\nsource=(\"$_pkgsrc::git+$url.git#commit=$_commit\"\n  quickshell-check.hook)\nsha256sums=('SKIP'\n  '8543e21aeaaa5441b73a679160e7601a957f16c433e8d6bd9257e80bd0e94083')\n\nbuild() {\n  cd \"$_pkgname\"\n  cmake -GNinja -B build \\\n    -DCMAKE_BUILD_TYPE=\"RelWithDebInfo\" \\\n    -DCMAKE_INSTALL_PREFIX=/usr \\\n    -DDISTRIBUTOR=\"AUR (package: quickshell-git)\" \\\n    -DDISTRIBUTOR_DEBUGINFO_AVAILABLE=NO \\\n    -DINSTALL_QML_PREFIX=lib/qt6/qml\n\n  cmake --build build\n}\n\npackage() {\n  install -Dm644 \"quickshell-check.hook\" -t \"$pkgdir/usr/share/libalpm/hooks\"\n  cd \"$_pkgname\"\n  DESTDIR=\"$pkgdir\" cmake --install build\n  install -Dm644 \"LICENSE\" -t \"$pkgdir/usr/share/licenses/$_pkgname\"\n}\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-quickshell-git/quickshell-check.hook",
    "content": "[Trigger]\nOperation = Upgrade\nOperation = Install\nType = Package\nTarget = qt6-base\nTarget = qt6-wayland\n[Action]\nDescription = Querying Quickshell compatibility after Qt6 update...\nWhen = PostTransaction\nExec = /usr/bin/quickshell --private-check-compat\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-screencapture/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-screencapture\npkgver=1.0\npkgrel=2\npkgdesc='Illogical Impulse Screenshot and Recording Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  hyprshot\n  slurp\n  swappy\n  tesseract\n  tesseract-data-eng\n  wf-recorder\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-toolkit/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-toolkit\npkgver=1.0\npkgrel=3\npkgdesc='Illogical Impulse Toolkit Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  upower\n  wtype\n  ydotool\n)\n"
  },
  {
    "path": "sdata/dist-arch/illogical-impulse-widgets/PKGBUILD",
    "content": "groups=(illogical-impulse)\npkgname=illogical-impulse-widgets\npkgver=1.0\npkgrel=7\npkgdesc='Illogical Impulse Widget Dependencies'\narch=(any)\nlicense=(None)\ndepends=(\n  fuzzel\n  glib2\n  imagemagick\n  hypridle\n  hyprlock\n  hyprpicker\n  songrec\n  translate-shell\n  wlogout\n  libqalculate\n)\n"
  },
  {
    "path": "sdata/dist-arch/install-deps.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\ninstall-yay(){\n  x sudo pacman -S --needed --noconfirm base-devel\n  x git clone https://aur.archlinux.org/yay-bin.git /tmp/buildyay\n  x cd /tmp/buildyay\n  x makepkg -o\n  x makepkg -se\n  x makepkg -i --noconfirm\n  x cd ${REPO_ROOT}\n  rm -rf /tmp/buildyay\n}\n\nremove_deprecated_dependencies(){\n  printf \"${STY_CYAN}[$0]: Removing deprecated dependencies:${STY_RST}\\n\"\n  local list=()\n  list+=(illogical-impulse-{microtex,pymyc-aur,oneui4-icons-git})\n  list+=(hyprland-qtutils)\n  list+=({quickshell,hyprutils,hyprpicker,hyprlang,hypridle,hyprland-qt-support,hyprland-qtutils,hyprlock,xdg-desktop-portal-hyprland,hyprcursor,hyprwayland-scanner,hyprland}-git)\n  list+=(matugen-bin)\n  for i in ${list[@]};do try sudo pacman --noconfirm -Rdd $i;done\n}\n# NOTE: `implicitize_old_dependencies()` was for the old days when we just switch from dependencies.conf to local PKGBUILDs.\n# However, let's just keep it as references for other distros writing their `sdata/dist-<OS_GROUP_ID>/install-deps.sh`, if they need it.\nimplicitize_old_dependencies(){\n# Convert old dependencies to non explicit dependencies so that they can be orphaned if not in meta packages\n  remove_bashcomments_emptylines ./sdata/dist-arch/previous_dependencies.conf ./cache/old_deps_stripped.conf\n  readarray -t old_deps_list < ./cache/old_deps_stripped.conf\n  pacman -Qeq > ./cache/pacman_explicit_packages\n  readarray -t explicitly_installed < ./cache/pacman_explicit_packages\n\n  echo \"Attempting to set previously explicitly installed deps as implicit...\"\n  for i in \"${explicitly_installed[@]}\"; do for j in \"${old_deps_list[@]}\"; do\n    [ \"$i\" = \"$j\" ] && yay -D --asdeps \"$i\"\n  done; done\n\n  return 0\n}\n\n#####################################################################################\nif ! command -v pacman >/dev/null 2>&1; then\n  printf \"${STY_RED}[$0]: pacman not found, it seems that the system is not ArchLinux or Arch-based distros. Aborting...${STY_RST}\\n\"\n  exit 1\nfi\n\n# Keep makepkg from resetting sudo credentials\nif [[ -z \"${PACMAN_AUTH:-}\" ]]; then\n  export PACMAN_AUTH=\"sudo\"\nfi\n\nshowfun remove_deprecated_dependencies\nv remove_deprecated_dependencies\n\n# Issue #363\ncase $SKIP_SYSUPDATE in\n  true) sleep 0;;\n  *) v sudo pacman -Syu;;\nesac\n\n# Use yay. Because paru does not support cleanbuild.\n# Also see https://wiki.hyprland.org/FAQ/#how-do-i-update\nif ! command -v yay >/dev/null 2>&1;then\n  echo -e \"${STY_YELLOW}[$0]: \\\"yay\\\" not found.${STY_RST}\"\n  showfun install-yay\n  v install-yay\nfi\n\nshowfun implicitize_old_dependencies\nv implicitize_old_dependencies\n\n# https://github.com/end-4/dots-hyprland/issues/581\n# yay -Bi is kinda hit or miss, instead cd into the relevant directory and manually source and install deps\ninstall-local-pkgbuild() {\n  local location=$1\n  local installflags=$2\n\n  x pushd $location\n\n  source ./PKGBUILD\n  x yay -S --sudoloop $installflags --asdeps \"${depends[@]}\"\n  # man makepkg:\n  # -A, --ignorearch: Ignore a missing or incomplete arch field in the build script.\n  # -s, --syncdeps: Install missing dependencies using pacman. When build-time or run-time dependencies are not found, pacman will try to resolve them.\n  # -f, --force: build a package even if it already exists in the PKGDEST\n  # -i, --install: Install or upgrade the package after a successful build using pacman(8).\n  # In https://github.com/end-4/dots-hyprland/issues/823#issuecomment-3394774645 it's suggested to use `sudo pacman -U --noconfirm *.pkg.tar.zst` instead of `makepkg -i`, however it's possible that multiple *.pkg.tar.zst exist, which makes this command not reliable.\n  x makepkg -Afsi --noconfirm\n  x popd\n}\n\n# Install core dependencies from the meta-packages\nmetapkgs=(./sdata/dist-arch/illogical-impulse-{audio,backlight,basic,fonts-themes,kde,portal,python,screencapture,toolkit,widgets})\nmetapkgs+=(./sdata/dist-arch/illogical-impulse-hyprland)\nmetapkgs+=(./sdata/dist-arch/illogical-impulse-microtex-git)\nmetapkgs+=(./sdata/dist-arch/illogical-impulse-quickshell-git)\nmetapkgs+=(./sdata/dist-arch/illogical-impulse-bibata-modern-classic-bin)\n\nfor i in \"${metapkgs[@]}\"; do\n  metainstallflags=\"--needed\"\n  $ask && showfun install-local-pkgbuild || metainstallflags=\"$metainstallflags --noconfirm\"\n  v install-local-pkgbuild \"$i\" \"$metainstallflags\"\ndone\n\n## Optional dependencies\nif pacman -Qs ^plasma-browser-integration$ ;then SKIP_PLASMAINTG=true;fi\ncase $SKIP_PLASMAINTG in\n  true) sleep 0;;\n  *)\n    if $ask;then\n      echo -e \"${STY_YELLOW}[$0]: NOTE: The size of \\\"plasma-browser-integration\\\" is ~600 KiB, but if you don't yet have KDE on your system it'll pull an extra ~600MiB of packages.${STY_RST}\"\n      echo -e \"${STY_YELLOW}It is needed if you want playtime of media in Firefox to be shown on the music controls widget.${STY_RST}\"\n      echo -e \"${STY_YELLOW}Install it? [y/N]${STY_RST}\"\n      read -p \"====> \" p\n    else\n      p=y\n    fi\n    case $p in\n      y) x sudo pacman -S --needed --noconfirm plasma-browser-integration ;;\n      *) echo \"Ok, won't install\"\n    esac\n    ;;\nesac\n"
  },
  {
    "path": "sdata/dist-arch/outdate-detect-mode",
    "content": "AUTO\n"
  },
  {
    "path": "sdata/dist-arch/previous_dependencies.conf",
    "content": "### This file contains a list of dependencies which were previously explicitly installed that are now installed using meta packages\n### Must be one package per line as it needs to be compared against the explicitly installed list from pacman\nillogical-impulse-ags\narchlinux-xdg-menu\nbc\ncoreutils\ncliphist\ncmake\ncurl\nfuzzel\nrsync\nwget\nripgrep\ngojq\nnpm\ntypescript\ngjs\nxdg-user-dirs\ntinyxml2\ngtkmm3\ngtksourceviewmm\ncairomm\npython-build\npython-pillow\npython-pywal\npython-setuptools-scm\npython-wheel\nxdg-desktop-portal\nxdg-desktop-portal-gtk\nxdg-desktop-portal-hyprland-git\npavucontrol\nwireplumber\nlibdbusmenu-gtk3\nplayerctl\nswww\nwebp-pixbuf-loader\ngtk-layer-shell\ngtk3\ngtksourceview3\ngobject-introspection\nupower\nyad\nydotool\nxdg-user-dirs-gtk\npolkit-gnome\ngnome-keyring\ngnome-control-center\nblueberry\ngammastep\ngnome-bluetooth-3.0\nbrightnessctl\nddcutil\ndart-sass\npython-pywayland\npython-psutil\nhypridle-git\nhyprlock-git\nwlogout\nwl-clipboard\nhyprpicker-git\nadw-gtk3-git\nqt5ct\nqt5-wayland\nfontconfig\nttf-readex-pro\nttf-jetbrains-mono-nerd\nttf-material-symbols-variable-git\nttf-space-mono-nerd\nfish\nfoot\nstarship\nswappy\nwf-recorder\ngrim\ntesseract\ntesseract-data-eng\nslurp\n"
  },
  {
    "path": "sdata/dist-arch/uninstall-deps.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\nfor i in illogical-impulse-{quickshell-git,audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,screencapture,toolkit,widgets} plasma-browser-integration; do\n  v yay -Rns $i\ndone\n"
  },
  {
    "path": "sdata/dist-fedora/.gitignore",
    "content": "/*/*.tar.*\n/*/pkg/\n/*/src/\nuser_data.yaml"
  },
  {
    "path": "sdata/dist-fedora/README.md",
    "content": "# Install scripts for Fedora Linux\n\nNote:\n- The scripts here are **not** meant to be executed directly.\n- This folder should reflect the equivalents of `/sdata/dist-arch/` but under Fedora.\n  - **When `/sdata/dist-arch/` is newer than this folder, an update on this folder is very likely needed.**\n  - Useful link: [Commit history on sdata/dist-arch/](https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-arch)\n- See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/)\n\n## Contributors\n- Author: [ririko6z](https://github.com/ririko6z)\n\n## Tested\n- It has been tested on Fedora Everything 44 on the `x86_64` platform.\n\n## Post installation\n- Fix the issue of the right column crashing when clicking the `Details` button in Wi-Fi mode. Edit this file: `~/.config/illogical-impulse/config.json`\n```diff\n@@ 44,3 44,3 @@\n-  \"apps\": {\n-    \"bluetooth\": \"kcmshell6 kcm_bluetooth\",\n-    \"network\": \"kitty -1 fish -c nmtui\",\n+  \"apps\": {\n+    \"bluetooth\": \"kcmshell6 kcm_bluetooth\",\n+    \"network\": \"plasmawindowed org.kde.plasma.networkmanagement\",\n```\n"
  },
  {
    "path": "sdata/dist-fedora/feddeps.toml",
    "content": "# COPR repositories list\n# \"solopasha/hyprland\" is not up to date to the current Hyprland version, replaced with the fork \"sdegler/hyprland\"\n[copr]\nrepos = [\n  \"ririko66z/dots-hyprland\",\n  \"sdegler/hyprland\",\n  \"deltacopy/darkly\",\n  \"alternateved/eza\",\n  \"atim/starship\"\n]\n\n# Fedora dependencies list\n\n# Audio\n[groups.audio]\npackages = [\n  \"cava\",\n  \"pavucontrol\",\n  \"wireplumber\",\n  \"libdbusmenu-gtk3-devel\",\n  \"playerctl\"\n]\n\n# Backlight\n[groups.backlight]\npackages = [\n  \"geoclue2\",\n  \"brightnessctl\",\n  \"ddcutil\"\n]\ninstall_opts = [\"--setopt=install_weak_deps=False\"]\n\n# Basic\n[groups.basic]\npackages = [\n  \"bc\",\n  \"coreutils\",\n  \"cliphist\",\n  \"cmake\",\n  \"curl\",\n  \"wget2\",\n  \"ripgrep\",\n  \"jq\",\n  \"xdg-utils\",\n  \"rsync\",\n  \"yq\"\n]\n\n# Cursor themes\n[groups.cursor_themes]\npackages = [\n  \"bibata-cursor-theme\"\n]\n\n# Fonts & Themes\n[groups.fonts_and_themes]\npackages = [\n  \"adw-gtk3-theme\",\n  \"breeze-cursor-theme\",\n  \"grub2-breeze-theme\",\n  \"breeze-icon-theme\",\n  \"breeze-icon-theme-fedora\",\n  \"kf6-breeze-icons\",\n  \"sddm-breeze\",\n  \"breeze-plus-icon-theme\",\n  \"darkly\",\n  \"eza\",\n  \"fish\",\n  \"fontconfig\",\n  \"kitty\",\n  \"florian-karsten-space-grotesk-fonts\",\n  \"starship\",\n  \"jetbrains-mono-nerd-fonts\",\n  \"google-material-symbols-vf-rounded-fonts\",\n  \"material-icons-fonts\",\n  \"readex-pro-fonts-all\",\n  \"google-rubik-vf-fonts\",\n  \"twitter-twemoji-fonts\",\n  \"google-sans-flex-vf-fonts\"\n]\n\n# Hyprland\n[groups.hyprland]\npackages = [\n  \"hyprland\",\n  \"hyprland-guiutils\",\n  \"hyprland-qt-support\",\n  \"hyprsunset\",\n  \"wl-clipboard\"\n]\ninstall_opts = [\"--setopt=install_weak_deps=False\"]\n\n# KDE\n[groups.kde]\npackages = [\n  \"bluedevil\",\n  \"gnome-keyring\",\n  \"NetworkManager\",\n  \"plasma-nm\",\n  \"polkit-kde\",\n  \"dolphin\",\n  \"plasma-systemsettings\"\n]\n\n# MicroTeX-git\n[groups.microtex]\npackages = [\n  \"microtex\"\n]\n\n# Portal\n[groups.portal]\npackages = [\n  \"xdg-desktop-portal\",\n  \"xdg-desktop-portal-gtk\",\n  \"xdg-desktop-portal-kde\",\n  \"xdg-desktop-portal-hyprland\"\n]\n\n# Python\n[groups.python]\npackages = [\n  \"clang\",\n  \"uv\",\n  \"gtk4-devel\",\n  \"libadwaita-devel\",\n  \"libsoup3-devel\",\n  \"libportal-gtk4\",\n  \"gobject-introspection-devel\",\n  \"python3\",\n  \"python3.12\",\n  \"python3-devel\",\n  \"python3.12-devel\"\n]\ninstall_opts = [\"--setopt=install_weak_deps=False\"]\n\n[groups.illogical-impulse]\npackages = [\n    \"quickshell-git\",\n    \"matugen\"\n]\n\n# Screencapture\n[groups.screencapture]\npackages = [\n  \"hyprshot\",\n  \"slurp\",\n  \"swappy\",\n  \"tesseract\",\n  \"tesseract-langpack-eng\",\n  \"tesseract-langpack-chi_sim\",\n  \"wf-recorder\"\n]\n\n# Toolkit\n[groups.toolkit]\npackages = [\n  \"upower\",\n  \"wtype\",\n  \"ydotool\"\n]\n\n# Widgets\n[groups.widgets]\npackages = [\n  \"fuzzel\",\n  \"glib2\",\n  \"ImageMagick\",\n  \"hypridle\",\n  \"hyprlock\",\n  \"hyprpicker\",\n  \"songrec\",\n  \"translate-shell\",\n  \"qalculate\",\n  \"wlogout\"\n]\n\n# Extra\n[groups.extra]\npackages = [\n  \"mpvpaper\",\n  \"plasma-systemmonitor\",\n  \"unzip\"\n]\ninstall_opts = [\"--setopt=install_weak_deps=False\"]\n"
  },
  {
    "path": "sdata/dist-fedora/install-deps.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n# -------------------------\n# CONFIG\n# -------------------------\nuser_config=\"${REPO_ROOT}/sdata/dist-fedora/user_data.yaml\"\nrpmbuildroot=\"${REPO_ROOT}/cache/rpmbuild\"\nrpm_specs=\"${REPO_ROOT}/sdata/dist-fedora/SPECS\"\ndeps_data_file=\"${REPO_ROOT}/sdata/dist-fedora/feddeps.toml\"\n\n# -------------------------\n# FUNCTIONS\n# -------------------------\n\n# Recording DNF Transaction ID\nfunction r() {\n  original_id=$(dnf history info | grep -Po '^Transaction ID\\s+:\\s+\\K\\d+')\n  \"$@\" || {\n    echo -e \"${STY_RED}[$0]: Stack Exception...${STY_RST}\"\n  }\n  last_id=$(dnf history info | grep -Po '^Transaction ID\\s+:\\s+\\K\\d+')\n  [ -f \"$user_config\" ] || { touch \"$user_config\" && yq -i \".dnf.original_transaction_id = $original_id\" \"$user_config\"; } || :\n  [ \"$original_id\" == \"$last_id\" ] || yq -i \".dnf.transaction_ids += [ $last_id ]\" \"$user_config\" || :\n}\n\n# Init local RPM repo and download rpms from releases there.\nfunction init_local_repo() {\n    url=\"https://api.github.com/repos/end-4/ii-package-builds/releases/tags/packages-fedora\"\n    path=\"$HOME/.cache/illogical-impulse-repo\"\n\n    rm -rf -- \"$path\"\n    mkdir -p \"$path\"\n\n    for file in $(curl -s \"$url\" | jq -r '.assets[].browser_download_url'); do\n        name=$(basename \"$file\")\n        echo \"Downloading $file\"\n        curl --max-time 10 -L --fail --show-error --progress-bar -o \"$path/$name\" \"$file\"\n        createrepo_c \"$path\"\n    done\n}\n\n# -------------------------\n# MAIN\n# -------------------------\n\nif ! command -v dnf >/dev/null 2>&1; then\n  printf \"${STY_RED}[$0]: dnf not found, it seems that the system is not Fedora 42 or later distros. Aborting...${STY_RST}\\n\"\n  exit 1\nfi\n\n# Update System\ncase $SKIP_SYSUPDATE in\n  true) sleep 0 ;;\n  *) v sudo dnf upgrade --refresh -y ;;\nesac\n\n# Remove version lock\nv sudo dnf versionlock delete quickshell-git 2>/dev/null\n\n# Install yq for parsing config files\nv sudo dnf install yq -y\n\n# Install development tools\nr v sudo dnf install createrepo_c -y\n\n# Install COPR repositories\ncopr_repos_json=$(yq -o=j '.copr.repos // []' \"$deps_data_file\")\neval \"$(jq -r '@sh \"copr_repos_array+=(\\(.[]))\"' <<<\"$copr_repos_json\")\" # Fedora distro contains jq\nfor copr in ${copr_repos_array[@]}; do\n  v sudo dnf copr enable \"$copr\" -y\ndone\n\n# Init local repo with prebuilt rpms\nshowfun init_local_repo\nv init_local_repo\n\n# Install packages from toml file\ndeps_data=$(yq -o=j '.' \"$deps_data_file\")\necho \"Starting to install packages from $deps_data_file ...\"\n\nwhile IFS= read -r deps_list_key; do\n  echo \"Installing package list: $deps_list_key\"\n\n  install_opts=$(echo $deps_data | yq \".groups.\\\"$deps_list_key\\\" | select(has(\\\"install_opts\\\")) | .install_opts[]\")\n  package_list=$(echo $deps_data | yq \".groups.\\\"$deps_list_key\\\".packages | unique | .[]\")\n\n  if [[ $deps_list_key == 'illogical-impulse' ]]; then\n      install_opts=\"$install_opts --repofrompath=illogical-impulse,file://$HOME/.cache/illogical-impulse-repo --nogpgcheck\"\n  fi\n\n  r v sudo dnf install -y $install_opts $package_list </dev/tty\n\n  echo \"----------------------------------------\"\ndone < <(echo \"$deps_data\" | yq '.groups | keys[]? | select(length > 0)')\n\n# Add back versionlock at the end\n[ -n $nolock_qs ] || v sudo dnf versionlock add quickshell-git || true\n\necho -e \"\\n========================================\"\necho \"All installations are completed.\"\necho \"========================================\"\n"
  },
  {
    "path": "sdata/dist-fedora/outdate-detect-mode",
    "content": "AUTO\n"
  },
  {
    "path": "sdata/dist-fedora/uninstall-deps.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\nuser_data=\"${REPO_ROOT}/sdata/dist-fedora/user_data.yaml\"\nyq eval '.dnf.transaction_ids // [] | reverse[]' \"$user_data\" | while read -r tx_id; do\n    echo -e \"\\n========================================\"\n    echo \"Rolling back DNF Transactions ID: $tx_id\"\n    echo \"========================================\"\n    dnf history info \"$tx_id\"\n    echo -e \"\\nProceed to undo this transaction? \"\n    v sudo dnf history undo -y \"$tx_id\" </dev/tty\ndone"
  },
  {
    "path": "sdata/dist-gentoo/README.md",
    "content": "# Install scripts for Gentoo\n\nNote:\n- The scripts here are **not** meant to be executed directly.\n- This folder should reflect the equivalents of `/sdata/dist-arch/` but under Gentoo.\n  - **When `/sdata/dist-arch/` is newer than this folder, an update on this folder is very likely needed.**\n  - Useful link: [Commit history on sdata/dist-arch/](https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-arch)\n- See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/)\n\n## Contributors\n- Author: [jwihardi](https://github.com/jwihardi)\n\n## install-deps.sh\n1. Enables localrepo and guru overlays if not already enabled.\n2. Copies _keywords_ to _keywords-user_ and appends the correct unmask keywords for the user's architecture (adm64, arm64, and x86 are supported).\n3. _keywords-user_ and _useflags_ are copies over into the proper portage directories. Quickshell also uses a live ebuild.\n4. Syncs, updates, and depcleans @world.\n5. Copies over the custom live ebuilds (hyprgraphics, hyprland-qt-support, hyprland-qtutils, hyprlang, hyprwayland-scanner) into localrepo and digests them.\n6. Loops through all illogical-impulse ebuilds to digest and emerge them.\n\n## Recommended use flags (useflags)\n- **The recommended useflags are not required, this is a more out of the box experience with these**\n- Pipewire is used, alsa and pulseaudio are disabled (enabling them won't hurt).\n- Init system is not assumed or considered so disabling systemd should be done in make.conf, same with session managers (elogind is recommended).\n\n## Making the dot-files work\n- elogind is expected to be installed and run as a service on OpenRC to set `XDG_RUNTIME_DIR`\n  - NOT recommended: seatd will require more manual setup\n- pipewire, pipewire-pulse, and wireplumber must be started after a dbus-session is created and before Hyprland is launched.\n\nIf you want to start after logging into tty1 you can do something like this.\n```fish\nif status --is-interactive; and [ (tty) = \"/dev/tty1\" ]\n    # Start DBus session if not running\n    if not set -q DBUS_SESSION_BUS_ADDRESS\n        dbus-launch --sh-syntax | sed 's/^/set -gx /; s/=/ /' | source\n    end\n\n    # Start PipeWire if not running\n    pgrep -x pipewire >/dev/null; or pipewire &\n    pgrep -x pipewire-pulse >/dev/null; or pipewire-pulse &\n    pgrep -x wireplumber >/dev/null; or wireplumber &\n\n    # Launch Hyprland with DBus session\n    exec Hyprland\nend\n```\n\n## Known Issues\n- If Hyprland is just blank, rebuild Quickshell (`emerge -q gui-apps/quickshell`)\n- `Hyprland: error while loading shared libraries: libhyprgraphics.so.0: cannot open shared object file: No such file or directory`\n  - The Hyprland live ebuild sometimes has linkage issues, deleting _Hyprland_ and _hyprland_ from `/usr/bin/` and then re-emerging usually fixes this.\n- When emerging Hyprland if you get an issue relating to `undefined reference to ``Hyprutils::Math::Vector2D::˜Vector2D()`` `\n  - Clear the cache folder (`rm -fr /var/tmp/portage/gui-wm/hyprland*`) then try again\n"
  },
  {
    "path": "sdata/dist-gentoo/additional-useflags",
    "content": "############ Additional needed useflags\nx11-libs/cairo X\ndev-cpp/cairomm X\ndev-python/pycairo X\nmedia-libs/libglvnd X\nx11-libs/libxkbcommon X\nmedia-libs/libpulse X\nmedia-plugins/alsa-plugins pulseaudio\nkde-frameworks/kcoreaddons dbus\nkde-frameworks/prison qml\nkde-frameworks/kguiaddons X dbus wayland\nkde-frameworks/kidletime X wayland\nkde-frameworks/kwindowsystem X wayland\nkde-frameworks/kconfig dbus qml\nkde-frameworks/sonnet qml\nx11-base/xwayland libei\nsys-apps/dbus X\nsys-libs/zlib minizip\ndev-qt/qtquick3d opengl\nmedia-video/ffmpeg X bzip2 dav1d drm fontconfig gnutls gpl libass opencl opengl openh264 opus postproc svg truetype vaapi vpx vulkan x264 x265 xml zlib\nmedia-libs/leptonica jpeg png zlib tiff webp\nmedia-libs/libva X wayland\napp-text/xmlto text\napp-crypt/gcr gtk\nmedia-video/vlc wayland ogg dbus mp3\ndev-libs/qcoro dbus qml\nmedia-libs/freetype harfbuzz\nmedia-libs/mesa wayland \nx11-libs/gtk+ wayland\n"
  },
  {
    "path": "sdata/dist-gentoo/hyprland-qtutils-private.patch",
    "content": "--- a/utils/dialog/CMakeLists.txt\n+++ b/utils/dialog/CMakeLists.txt\n@@ -8,7 +8,7 @@\n set(CMAKE_CXX_STANDARD 23)\n set(CMAKE_CXX_STANDARD_REQUIRED ON)\n \n-find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient)\n+find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate)\n find_package(PkgConfig REQUIRED)\n \n pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils)\n--- a/utils/donate-screen/CMakeLists.txt\n+++ b/utils/donate-screen/CMakeLists.txt\n@@ -8,7 +8,7 @@\n set(CMAKE_CXX_STANDARD 23)\n set(CMAKE_CXX_STANDARD_REQUIRED ON)\n \n-find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient)\n+find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate)\n find_package(PkgConfig REQUIRED)\n \n pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils)\n--- a/utils/update-screen/CMakeLists.txt\n+++ b/utils/update-screen/CMakeLists.txt\n@@ -8,7 +8,7 @@\n set(CMAKE_CXX_STANDARD 23)\n set(CMAKE_CXX_STANDARD_REQUIRED ON)\n \n-find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient)\n+find_package(Qt6 6.5 REQUIRED COMPONENTS Widgets Quick QuickControls2 WaylandClient WaylandClientPrivate)\n find_package(PkgConfig REQUIRED)\n \n pkg_check_modules(hyprutils REQUIRED IMPORTED_TARGET hyprutils)\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-audio/illogical-impulse-audio-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Audio Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tmedia-sound/cava\n\tmedia-sound/pavucontrol-qt\n\tmedia-video/wireplumber\n\tdev-libs/libdbusmenu[gtk3]\n\tmedia-sound/playerctl\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-backlight/illogical-impulse-backlight-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Backlight Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tapp-misc/geoclue\n\tapp-misc/brightnessctl\n\tapp-misc/ddcutil\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-basic/illogical-impulse-basic-1.0-r2.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Basic Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tsys-devel/bc\n\tsys-apps/coreutils\n\tapp-misc/cliphist\n\tdev-build/cmake\n\tnet-misc/curl\n\tnet-misc/wget\n\tsys-apps/ripgrep\n\tdev-python/jq\n\tx11-misc/xdg-user-dirs\n\tnet-misc/rsync\n\tapp-misc/yq-go\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-bibata-modern-classic-bin/illogical-impulse-bibata-modern-classic-bin-2.0.6-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Material Based Cursor Theme, installed for illogical-impulse dotfiles\"\nHOMEPAGE=\"\"\nSRC_URI=\"https://github.com/ful1e5/Bibata_Cursor/releases/download/v${PV}/Bibata-Modern-Classic.tar.xz -> bibata-modern-classic.tar.xz\"\n\nLICENSE=\"GPL-3+\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\"\n\nS=\"${WORKDIR}/Bibata-Modern-Classic\"\n\nsrc_install() {\n\tinsinto /usr/share/icons\n\tdoins -r \"${S}\"\n}\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-fonts-themes/illogical-impulse-fonts-themes-1.0-r2.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Fonts and Theming Dependencies\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tx11-themes/adw-gtk3\n\tkde-plasma/breeze\n\tkde-plasma/breeze-plus\n\tx11-themes/darkly\n\tsys-apps/eza\n\tapp-shells/fish\n\tmedia-libs/fontconfig\n\tx11-terms/kitty\n\tx11-misc/matugen\n\tmedia-fonts/space-grotesk\n\tapp-shells/starship\n\tmedia-fonts/jetbrains-mono\n\tmedia-fonts/material-symbols-variable\n\tmedia-fonts/readex-pro\n\tmedia-fonts/rubik-vf\n\tmedia-fonts/twemoji\n\"\n##### CUSTOM EBUILDS\n# x11-themes/adw-gtk3\n# x11-themes/darkly\n# media-fonts/space-grotesk\n# media-fonts/material-symbols-variable\n# media-fonts/readex-pro\n# media-fonts/rubik-vf\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-hyprland/illogical-impulse-hyprland-1.0-r3.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Hyprland related packages\"\nHOMEPAGE=\"\"\n\nLICENSE=\"GPL-2\"\nSLOT=\"0\"\nKEYWORDS=\"amd64 arm64 x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tgui-apps/hyprsunset\n\t>=gui-wm/hyprland-0.53.3:=\n\tgui-apps/wl-clipboard\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-kde/illogical-impulse-kde-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse KDE Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"GPL-2\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tkde-plasma/bluedevil\n\tgnome-base/gnome-keyring\n\tnet-misc/networkmanager\n\tkde-plasma/plasma-nm\n\tkde-plasma/polkit-kde-agent\n\tkde-apps/dolphin\n\tkde-plasma/systemsettings\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-microtex-git/illogical-impulse-microtex-git-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\nMICROTEX_VER=\"0e3707f\"\n\nDESCRIPTION=\"MicroTeX for illogical-impulse dotfiles\"\nHOMEPAGE=\"https://github.com/NanoMichael/MicroTeX\"\nSRC_URI=\"https://github.com/NanoMichael/MicroTeX/archive/${MICROTEX_VER}.tar.gz -> MicroTeX-${MICROTEX_VER}.tar.gz\"\n\nLICENSE=\"MIT\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tdev-libs/tinyxml2\n\tdev-cpp/gtkmm\n\tdev-cpp/gtksourceviewmm\n\tdev-cpp/cairomm\n\"\n\nS=\"${WORKDIR}\"\n\nsrc_unpack() {\n\t# If I don't strip it it has an insane hash\n\ttar xvf \"${DISTDIR}/MicroTeX-${MICROTEX_VER}.tar.gz\" --strip-components=1 -C \"${WORKDIR}\"\n}\n\nsrc_prepare() {\n\tdefault\n\tcd \"${S}\" || die\n\t# Gentoo doesn't have gtksourceviewmm4 even on testing so I just left it on 3\n\t# sed -i 's/gtksourceviewmm-3.0/gtksourceviewmm-4.0/' CMakeLists.txt\n\tsed -i 's/tinyxml2.so.10/tinyxml2.so.11/' CMakeLists.txt\n}\n\nsrc_compile() {\n\tcd \"${S}\"\n\tmkdir -p build\n\tcmake -B build -S . -DCMAKE_BUILD_TYPE=None\n\tcmake --build build\n}\n\nsrc_install() {\n\tcd \"${S}\" || die\n\tinsinto /opt/illogical-impulse-microtex-git\n\tdoins -r build/LaTeX\n\tdoins -r build/res\n\n\tinsinto /usr/share/licenses/illogical-impulse-microtex-git\n\tdoins LICENSE\n}\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-portal/illogical-impulse-portal-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse XDG Desktop Portals\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tsys-apps/xdg-desktop-portal\n\tkde-plasma/xdg-desktop-portal-kde\n\tsys-apps/xdg-desktop-portal-gtk\n\tgui-libs/xdg-desktop-portal-hyprland\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-python/illogical-impulse-python-1.1-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Python Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tdev-python/clang\n\tdev-python/uv\n\tgui-libs/gtk\n\tgui-libs/libadwaita\n\tnet-libs/libsoup\n\tdev-libs/libportal\n\tdev-libs/gobject-introspection\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-quickshell-git/illogical-impulse-quickshell-git-0.1.0-r6.ebuild",
    "content": "# Copyright 1999-2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\ninherit cmake git-r3\n\nDESCRIPTION=\"Toolkit for building desktop widgets using QtQuick\"\nHOMEPAGE=\"https://quickshell.org/\"\n\nEGIT_REPO_URI=\"https://github.com/quickshell-mirror/quickshell.git\"\nEGIT_COMMIT=\"7511545ee20664e3b8b8d3322c0ffe7567c56f7a\"\n\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nLICENSE=\"LGPL-3\"\nSLOT=\"0\"\n\nIUSE=\"-breakpad +jemalloc +sockets +wayland +layer-shell +session-lock +toplevel-management +screencopy +X +pipewire +tray +mpris +pam +hyprland +hyprland-global-shortcuts +hyprland-focus-grab -i3 -i3-ipc +bluetooth\"\n\nRDEPEND=\"\n\tdev-qt/qtbase:6=\n\tdev-qt/qtdeclarative:6=\n\tdev-qt/qt5compat:6=\n\tkde-frameworks/kimageformats:6=[avif]\n\tdev-cpp/cpptrace[unwind]\n\tdev-qt/qtimageformats:6=\n\tdev-qt/qtmultimedia:6=\n\tdev-qt/qtpositioning:6=\n\tdev-qt/qtquicktimeline:6=\n\tdev-qt/qtsensors:6=\n\tdev-qt/qtsvg:6=\n\tdev-qt/qttools:6=\n\tdev-qt/qttranslations:6=\n\tdev-qt/qtvirtualkeyboard:6=\n\tdev-qt/qtwayland:6=\n\tkde-apps/kdialog\n\tkde-frameworks/syntax-highlighting:6=\n\tkde-frameworks/kirigami:6=\n\n\tjemalloc? ( dev-libs/jemalloc:= )\n\twayland? (\n\t\tdev-libs/wayland\n\t\tdev-qt/qtwayland:6=\n\t)\n\tscreencopy? (\n\t\tx11-libs/libdrm\n\t\tmedia-libs/mesa\n\t)\n\tX? ( x11-libs/libxcb:= )\n\tpipewire? ( media-video/pipewire:= )\n\tmpris? ( dev-qt/qtdbus:= )\n\tpam? ( sys-libs/pam )\n\tbluetooth? ( net-wireless/bluez )\n\"\nDEPEND=\"${RDEPEND}\"\nBDEPEND=\"\n\tdev-cpp/cli11\n\tdev-build/cmake\n\tdev-vcs/git\n\tdev-build/ninja\n\tdev-qt/qtshadertools\n\n\tdev-util/spirv-tools\n\twayland? (\n\t\tdev-util/wayland-scanner\n\t\tdev-libs/wayland-protocols\n\t)\n\tvirtual/pkgconfig\n\tbreakpad? ( dev-util/breakpad )\n\tdev-util/vulkan-headers\n\"\n\nsrc_configure(){\n\tmycmakeargs=(\n\t\t\t-DCMAKE_BUILD_TYPE=RelWithDebInfo\n\t\t\t-DDISTRIBUTOR=\"Gentoo Illogical-Impulses\"\n\t\t\t-DINSTALL_QML_PREFIX=\"$(get_libdir)/qt6/qml\"\n\t\t\t-DCRASH_REPORTER=$(usex breakpad ON OFF)\n\t\t\t-DUSE_JEMALLOC=$(usex jemalloc ON OFF)\n\t\t\t-DSOCKETS=$(usex sockets ON OFF)\n\t\t\t-DWAYLAND=$(usex wayland ON OFF)\n\t\t\t-DWAYLAND_WLR_LAYERSHELL=$(usex layer-shell ON OFF)\n\t\t\t-DWAYLAND_SESSION_LOCK=$(usex session-lock ON OFF)\n\t\t\t-DWAYLAND_TOPLEVEL_MANAGEMENT=$(usex toplevel-management ON OFF)\n\t\t\t-DSCREENCOPY=$(usex screencopy ON OFF)\n\t\t\t-DX11=$(usex X ON OFF)\n\t\t\t-DSERVICE_PIPEWIRE=$(usex pipewire ON OFF)\n\t\t\t-DSERVICE_STATUS_NOTIFIER=$(usex tray ON OFF)\n\t\t\t-DSERVICE_MPRIS=$(usex mpris ON OFF)\n\t\t\t-DSERVICE_PAM=$(usex pam ON OFF)\n\t\t\t-DHYPRLAND=$(usex hyprland ON OFF)\n\t\t\t-DHYPRLAND_GLOBAL_SHORTCUTS=$(usex hyprland-global-shortcuts)\n\t\t\t-DHYPRLAND_FOCUS_GRAB=$(usex hyprland-focus-grab)\n\t\t\t-DI3=$(usex i3 ON OFF)\n\t\t\t-DI3_IPC=$(usex i3-ipc ON OFF)\n\t\t\t-DBLUETOOTH=$(usex bluetooth ON OFF)\n\t\t)\n\t\tcmake_src_configure\n}\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-screencapture/illogical-impulse-screencapture-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Screenshot and Recording Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tgui-apps/hyprshot\n\tgui-apps/slurp\n\tgui-apps/swappy\n\tapp-text/tesseract\n\tgui-apps/wf-recorder\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-toolkit/illogical-impulse-toolkit-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogical Impulse Toolkit Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tsys-power/upower\n\tgui-apps/wtype\n\tx11-misc/ydotool\n\"\n"
  },
  {
    "path": "sdata/dist-gentoo/illogical-impulse-widgets/illogical-impulse-widgets-1.0-r5.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Illogicall Impulse Widget Dependencies\"\nHOMEPAGE=\"\"\n\nLICENSE=\"metapackage\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nDEPEND=\"\"\nRDEPEND=\"\n\tgui-apps/fuzzel\n\tdev-libs/glib\n\tmedia-gfx/imagemagick\n\tgui-apps/hypridle\n\tgui-apps/hyprlock\n\tgui-apps/hyprpicker\n\tmedia-sound/songrec\n\tapp-i18n/translate-shell\n\tgui-apps/wlogout\n\tsci-libs/libqalculate\n\"\n##### CUSTOM EBUILDS\n# app-misc/songrec\n"
  },
  {
    "path": "sdata/dist-gentoo/import-local-pkgs.sh",
    "content": "HYPR_DIR=\"local-pkgs/hyprland\"\nFT_DIR=\"local-pkgs/fonts-and-themes\"\nWIDGETS_DIR=\"local-pkgs/widgets\"\n\nfunction import_ebuild(){\n\tfrom_dir=\"$1\"\n\tto_dir=\"$2\"\n\tename=\"$3\"\n\tx sudo rm -rf \"${ebuild_dir}/${to_dir}/${ename}\"\n\tx sudo mkdir -p \"${ebuild_dir}/${to_dir}/${ename}\"\n\tv sudo cp ./sdata/dist-gentoo/${from_dir}/${ename}*.ebuild \"${ebuild_dir}/${to_dir}/${ename}\"\n\tv sudo ebuild \"${ebuild_dir}/${to_dir}/${ename}/${ename}\"*.ebuild digest\n}\n\n############### FONTS AND THEMES\nimport_ebuild \"${FT_DIR}\" \"media-fonts\" \"material-symbols-variable\"\nimport_ebuild \"${FT_DIR}\" \"media-fonts\" \"readex-pro\"\nimport_ebuild \"${FT_DIR}\" \"media-fonts\" \"rubik-vf\"\nimport_ebuild \"${FT_DIR}\" \"media-fonts\" \"space-grotesk\"\nimport_ebuild \"${FT_DIR}\" \"kde-plasma\" \"breeze-plus\"\nimport_ebuild \"${FT_DIR}\" \"x11-themes\" \"darkly\"\n"
  },
  {
    "path": "sdata/dist-gentoo/install-deps.sh",
    "content": "printf \"${STY_YELLOW}\"\nprintf \"============WARNING/NOTE (1)============\\n\"\nprintf \"Ensure you have a global use flag for elogind or systemd in your make.conf for simplicity\\n\"\nprintf \"Or you can manually add the use flags for each package that requires it\\n\"\nprintf \"${STY_RST}\"\npause\n\nprintf \"${STY_YELLOW}\"\nprintf \"============WARNING/NOTE (2)============\\n\"\nprintf \"https://github.com/end-4/dots-hyprland/blob/main/sdata/dist-gentoo/README.md\\n\"\nprintf \"Checkout the above README for potential bug fixes or additional information\\n\\n\"\nprintf \"${STY_RST}\"\npause\n\nx sudo emerge --update --quiet app-eselect/eselect-repository\nx sudo emerge --update --quiet app-portage/smart-live-rebuild\n# Currently using 3.12 python, this doesn't need to be default though\nx sudo emerge --update --quiet dev-lang/python:3.12\n\nif [[ -z $(eselect repository list | grep ii-dots) ]]; then\n\tv sudo eselect repository create ii-dots\n\tv sudo eselect repository enable ii-dots\nfi\n\nif [[ -z $(eselect repository list | grep -E \".*guru \\*.*\") ]]; then\n        v sudo eselect repository enable guru\nfi\n\nif [[ -z $(eselect repository list | grep -E \".*hyproverlay \\*.*\") ]]; then\n\tv sudo eselect repository enable hyproverlay\nfi\n\narch=$(portageq envvar ACCEPT_KEYWORDS)\n\n# Exclude hyprland, will deal with that separately\nmetapkgs=(illogical-impulse-{audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,quickshell-git,screencapture,toolkit,widgets})\n\nebuild_dir=\"/var/db/repos/ii-dots\"\n\n\n########## IMPORT KEYWORDS (START)\n# Illogical-Impulse\nx sudo cp ./sdata/dist-gentoo/keywords ./sdata/dist-gentoo/keywords-user\nx sed -i \"s/$/ ~${arch}/\" ./sdata/dist-gentoo/keywords-user\nv sudo cp ./sdata/dist-gentoo/keywords-user /etc/portage/package.accept_keywords/illogical-impulse\n\n########## IMPORT USEFLAGS\nv sudo cp ./sdata/dist-gentoo/useflags /etc/portage/package.use/illogical-impulse\nv sudo sh -c 'cat ./sdata/dist-gentoo/additional-useflags >> /etc/portage/package.use/illogical-impulse'\n\n########## UPDATE SYSTEM\nv sudo emerge --sync\nv sudo emerge --quiet --newuse --update --deep @world\nv sudo emerge --quiet @smart-live-rebuild\n\n# Remove old ebuilds (if this isn't done the wildcard will fuck upon a version change)\nx sudo rm -fr ${ebuild_dir}/app-misc/illogical-impulse-*\n\nsource ./sdata/dist-gentoo/import-local-pkgs.sh\n\n########## INSTALL ILLOGICAL-IMPUSEL EBUILDS\nfor i in \"${metapkgs[@]}\"; do\n\tx sudo mkdir -p ${ebuild_dir}/app-misc/${i}\n\tv sudo cp ./sdata/dist-gentoo/${i}/${i}*.ebuild ${ebuild_dir}/app-misc/${i}/\n\tv sudo ebuild ${ebuild_dir}/app-misc/${i}/*.ebuild digest\n\tv sudo emerge --update --quiet app-misc/${i}\ndone\n\nv sudo emerge --depclean\n"
  },
  {
    "path": "sdata/dist-gentoo/keywords",
    "content": "app-misc/illogical-impulse-audio\napp-misc/illogical-impulse-backlight\napp-misc/illogical-impulse-basic\napp-misc/illogical-impulse-bibata-modern-classic-bin\napp-misc/illogical-impulse-fonts-themes\napp-misc/illogical-impulse-hyprland\napp-misc/illogical-impulse-kde\napp-misc/illogical-impulse-microtex-git\napp-misc/illogical-impulse-portal\napp-misc/illogical-impulse-python\napp-misc/illogical-impulse-quickshell-git\napp-misc/illogical-impulse-screencapture\napp-misc/illogical-impulse-toolkit\napp-misc/illogical-impulse-widgets\nx11-misc/matugen\nmedia-fonts/twemoji\napp-misc/brightnessctl\napp-misc/cliphist\ngui-apps/hypridle\ngui-apps/hyprlock\ndev-libs/hyprlang\ngui-apps/hyprpicker\ngui-apps/hyprsunset\ngui-libs/xdg-desktop-portal-hyprland\ngui-apps/hyprshot\ngui-libs/hyprcursor\ngui-apps/wf-recorder\ngui-apps/wtype\ngui-apps/fuzzel\ngui-apps/wlogout\ndev-cpp/sdbus-c++\ndev-libs/hyprland-protocols\ngui-libs/hyprutils\ngui-libs/hyprwire\ndev-util/hyprwayland-scanner\ngui-libs/hyprland-qt-support\ngui-libs/hyprland-guiutils\ngui-libs/hyprtoolkit\ngui-wm/hyprland\ndev-libs/hyprgraphics\ngui-libs/aquamarine\nx11-libs/libxkbcommon\ndev-util/breakpad\ndev-libs/linux-syscall-support\ndev-embedded/libdisasm\nkde-plasma/breeze-plus\nx11-themes/darkly\nx11-themes/adw-gtk3\nmedia-fonts/space-grotesk\nmedia-fonts/material-symbols-variable **\nmedia-fonts/readex-pro\nmedia-fonts/rubik-vf\nmedia-sound/songrec\ndev-cpp/glaze\ndev-cpp/cpptrace\ndev-libs/libdwarf\n"
  },
  {
    "path": "sdata/dist-gentoo/local-pkgs/fonts-and-themes/breeze-plus-6.2.5-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nDESCRIPTION=\"Breeze styled extra icon theme for KDE\"\nHOMEPAGE=\"https://github.com/mjkim0727/breeze-plus\"\nSRC_URI=\"https://github.com/mjkim0727/breeze-plus/archive/refs/tags/${PV}.tar.gz -> ${P}.tar.gz\"\n\nLICENSE=\"LGPL-2.1\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRDEPEND=\"kde-plasma/breeze\"\nBDEPEND=\"\"\n\nS=\"${WORKDIR}/${PN}-${PV}\"\n\nsrc_install() {\n\tinsinto /usr/share/icons\n\tdoins -r src/breeze-plus*\n}\n\n"
  },
  {
    "path": "sdata/dist-gentoo/local-pkgs/fonts-and-themes/darkly-0.5.24-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\n# NOTE: Did not include QT5 backwards compatibility\n\ninherit cmake\n\nDESCRIPTION=\"Fork of Lightly - A modern style for Qt applications\"\nHOMEPAGE=\"https://github.com/Bali10050/Darkly\"\nSRC_URI=\"https://github.com/Bali10050/darkly/archive/refs/tags/v${PV}.tar.gz -> ${P}.tar.gz\"\n\nLICENSE=\"GPL-2+\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nDEPEND=\"\n\tkde-frameworks/kcoreaddons:6\n\tkde-frameworks/kconfig:6\n\tkde-frameworks/kguiaddons:6\n\tkde-frameworks/ki18n:6\n\tkde-frameworks/kiconthemes:6\n\tkde-frameworks/kwindowsystem:6\n\tkde-frameworks/kcmutils:6\n\tkde-frameworks/frameworkintegration:6\n\tkde-frameworks/kconfigwidgets:6\n\tkde-plasma/kdecoration:6\n\tdev-qt/qtdeclarative:6\n\"\nRDEPEND=\"${DEPEND}\"\n\nBDEPEND=\"\n\tdev-build/cmake\n\tkde-frameworks/extra-cmake-modules\n\tdev-vcs/git\n\"\n\nS=\"${WORKDIR}/Darkly-${PV}\"\n\nsrc_configure() {\n\tlocal mycmakeargs=(\n\t\t-DBUILD_TESTING=OFF\n\t\t-DBUILD_QT5=OFF\n\t\t-DBUILD_QT6=ON\n\t)\n\tcmake_src_configure\n}\n\nsrc_install() {\n\tcmake_src_install\n\trm -rf \"${ED}/usr/$(get_libdir)/cmake\" || die\n}\n\n"
  },
  {
    "path": "sdata/dist-gentoo/local-pkgs/fonts-and-themes/material-symbols-variable-9999.ebuild",
    "content": "# Copyright 1999-2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\ninherit font\n\nDESCRIPTION=\"Material Design icons by Google - variable fonts\"\nHOMEPAGE=\"https://github.com/google/material-design-icons\"\n\nBASE_URL=\"https://github.com/google/material-design-icons/raw/refs/heads/master\"\n\nSRC_URI=\"\n\t${BASE_URL}/variablefont/MaterialSymbolsOutlined%5BFILL,GRAD,opsz,wght%5D.ttf -> MaterialSymbolsOutlined-FILL-GRAD-opsz-wght.ttf\n\t${BASE_URL}/variablefont/MaterialSymbolsRounded%5BFILL,GRAD,opsz,wght%5D.ttf -> MaterialSymbolsRounded-FILL-GRAD-opsz-wght.ttf\n\t${BASE_URL}/variablefont/MaterialSymbolsSharp%5BFILL,GRAD,opsz,wght%5D.ttf -> MaterialSymbolsSharp-FILL-GRAD-opsz-wght.ttf\n\"\n\nLICENSE=\"Apache-2.0\"\nSLOT=\"0\"\nKEYWORDS=\"\"\n\nS=\"${WORKDIR}\"\n\nFONT_SUFFIX=\"ttf\"\n\nsrc_unpack() {\n\tmkdir -p \"${S}\"\n\tcp \"${DISTDIR}/MaterialSymbolsOutlined-FILL-GRAD-opsz-wght.ttf\" \\\n\t\t\"${S}/MaterialSymbolsOutlined[FILL,GRAD,opsz,wght].ttf\"\n\tcp \"${DISTDIR}/MaterialSymbolsRounded-FILL-GRAD-opsz-wght.ttf\" \\\n\t\t\"${S}/MaterialSymbolsRounded[FILL,GRAD,opsz,wght].ttf\"\n\tcp \"${DISTDIR}/MaterialSymbolsSharp-FILL-GRAD-opsz-wght.ttf\" \\\n\t\t\"${S}/MaterialSymbolsSharp[FILL,GRAD,opsz,wght].ttf\"\n}\n\nsrc_install() {\n\tfont_src_install\n}\n\n"
  },
  {
    "path": "sdata/dist-gentoo/local-pkgs/fonts-and-themes/readex-pro-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\ninherit font\n\nDESCRIPTION=\"Illogical Impulse Fonts and Theming Dependencies\"\nHOMEPAGE=\"\"\nSRC_URI=\"https://github.com/ThomasJockin/readexpro/archive/refs/heads/master.tar.gz -> ${P}-readexpro.tar.gz\"\nLICENSE=\"GPL-2\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\nRESTRICT=\"strip\"\n\nS=\"${WORKDIR}/readexpro-master\"\n\nsrc_install() {\n\tinsinto /usr/share/fonts/ttf-readex-pro\n\tdoins \"${S}\"/fonts/ttf/*.ttf\n}\n"
  },
  {
    "path": "sdata/dist-gentoo/local-pkgs/fonts-and-themes/rubik-vf-1.0-r1.ebuild",
    "content": "# Copyright 2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\nCOMMIT=\"e337a5f69a9bea30e58d05bd40184d79cc099628\"\n\ninherit font\n\nDESCRIPTION=\"A sans serif font family with slightly rounded corners: variable font version\"\nHOMEPAGE=\"https://github.com/googlefonts/rubik\"\nSRC_URI=\"https://github.com/googlefonts/rubik/archive/${COMMIT}.tar.gz -> ${P}.tar.gz\"\n\nLICENSE=\"OFL-1.1\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nS=\"${WORKDIR}/rubik-${COMMIT}\"\n\nFONT_S=\"${S}/fonts/variable\"\nFONT_SUFFIX=\"ttf\"\n\nsrc_install() {\n\tfont_src_install\n\tdodoc OFL.txt AUTHORS.txt CONTRIBUTORS.txt\n}\n\n"
  },
  {
    "path": "sdata/dist-gentoo/local-pkgs/fonts-and-themes/space-grotesk-1.1.4-r1.ebuild",
    "content": "# Copyright 1999-2025 Gentoo Authors\n# Distributed under the terms of the GNU General Public License v2\n\nEAPI=8\n\ninherit font\n\nDESCRIPTION=\"Space Grotesk OTF font from 38C3 styleguide\"\nHOMEPAGE=\"https://events.ccc.de/congress/2024/infos/styleguide.html\"\nSRC_URI=\"https://events.ccc.de/congress/2024/infos/styleguide/38c3-styleguide-full-v2.zip\"\n\nLICENSE=\"OFL-1.1\"\nSLOT=\"0\"\nKEYWORDS=\"~amd64 ~arm64 ~x86\"\n\nBDEPEND=\"app-arch/unzip\"\n\nS=\"${WORKDIR}/fonts/space-grotesk-${PV}\"\n\nFONT_S=\"${S}/otf\"\nFONT_SUFFIX=\"otf\"\n\nsrc_install() {\n\tfont_src_install\n\n\tdodoc OFL.txt\n}\n\n"
  },
  {
    "path": "sdata/dist-gentoo/outdate-detect-mode",
    "content": "AUTO\n"
  },
  {
    "path": "sdata/dist-gentoo/uninstall-deps.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\nfor i in illogical-impulse-{quickshell-git,audio,backlight,basic,bibata-modern-classic-bin,fonts-themes,hyprland,kde,microtex-git,portal,python,screencapture,toolkit,widgets}; do\n  v sudo emerge --unmerge $i\ndone\n\nv sudo emerge --depclean\n"
  },
  {
    "path": "sdata/dist-gentoo/useflags",
    "content": "################### AUDIO ################### \nmedia-sound/cava pipewire -pulseaudio\n#media-sound/pavucontrol-qt (no use flags)\n#media-video/wireplumber (no use flags)\ndev-libs/libdbusmenu gtk3 introspection\nmedia-sound/playerctl introspection\n\n################### BACKLIGHT ################### \napp-misc/geoclue introspection\n#app-misc/brightnessctl\napp-misc/ddcutil X\n\n################### BASIC ################### \nnet-misc/axel nls ssl\nsys-devel/bc readline\nsys-apps/coreutils acl nls openssl xattr gmp\n#app-misc/cliphist (no use flags)\ndev-build/cmake ncurses\nnet-misc/curl adns alt-svvc ftp hsts http2 http3 httpsrr imap openssl pop3 psl quic smtp ssl lftp websockets idn brotli zstd\nnet-misc/rsync acl iconv ssl xattr\nnet-misc/wget nls pore ssl zlib idn\nsys-apps/ripgrep pcre\n#dev-python/jq (nothing needed)\n#dev-build/meson (nothing needed)\n#x11-misc/xdg-user-dirs (nothing needed)\n\n################### FONTS & THEMES ################### \n#kde-plasma/breeze (nothing needed)\nsys-apps/eza git\napp-shell/fish nls \nmedia-libs/fontconfig nls\nx11-terms/kitty X wayland\n#x11-misc/matugen\n#app-shell/starship (nothing needed)\nmedia-fonts/jetbrains-mono X\nmedia-fonts/twemoji X\n# The rest are handled in the ebuild\n\n################### HYPRLAND ################### \n#gui-apps/hypridle (no use flags)\n#gui-libs/hyprcursor (no use flags)\ngui-wm/hyprland::hyproverlay X guiutils\n#gui-libs/hyprland-qtutils (no use flags)\n#gui-libs/hyprland-qt-support (no use flags)\n#dev-libs/hyprlang (no use flags)\n#gui-apps/hyprlock (no use flags)\n#gui-apps/hyprpicker (no use flags)\n#gui-apps/hyprsunset (no use flags)\n#gui-libs/hyprutils (no use flags)\n#dev-util/hyprwayland-scanner (no use flags)\n#gui-apps/wl-clipboard (no use fags)\n\n\n################### KDE ################### \nkde-plasma/bluedevil -handbook\ngnome-base/gnome-keyring pam ssh-agent \nnet-misc/networkmanager concheck introspection nss ppp tools wifi -wext -modemmanager\n#kde-plasma/plasma-nm (nothing needed)\n#kde-plasma/polkit-kde-agent (nothing needed)\nkde-apps/dolphin -handbook\nkde-plasma/systemsettings -handbook\n\n################### MICROTEX-GIT ################### \ndev-cpp/gtkmm X wayland\n\n################### PORTAL ################### \nsys-apps/xdg-desktop-portal seccomp\n#sys-plasma/xdg-desktop-portal-kde (nothing needed)\nsys-apps/xdg-desktop-portal-gtk X wayland\n#gui-libs/xdg-desktop-portal-hyprland (elogiind maybe)\n\n################### PYTHON ################### \n#dev-python/clang (nothing needed)\n#dev-python/uv (nothing needed)\ngui-libs/gtk X introspection wayland\ngui-libs/libadwaita introspection\nnet-libs/libsoup brotli introspection ssl\n#dev-libs/gobject-introspection (nothing needed)\n# dart-sassc handled in the ebuild\n\n\n################### SCREENCAPTURE ################### \n#gui-apps/hyprshot (no use flags)\ngui-apps/slurp -man\n#gui-apps/swappy (no use flags)\napp-text/tesseract jpeg openmp png webp\ngui-apps/wf-recorder pipewire\n\n\n################### TOOLKIT ###################\nkde-apps/kdialog X\ndev-qt/qt5compat gui icu qml\ndev-qt/qtbase X concurrent cups dbus gui icu libinput libproxy network nls opengl sql sqlite ssl udev wayland widgets xml zstd\ndev-qt/qtdeclarative jit network opengl sql ssl svg widgets\n#dev-qt/qtimageformats (nothing needed)\ndev-qt/qtmultimedia X dbus ffmpeg opengl pipewire qml wayland v4l\ndev-qt/qtpositioning qml geoclue\n#dev-qt/qtquicktimeline (nothing needed)\n#dev-qt/qtsensors (nothing needed)\n#dev-qt/qtsvg (nothing needed)\ndev-qt/qttools assistant linguist opengl qdbus widgets zstd\n#dev-qt/qttranslations (no use flags)\ndev-qt/qtvirtualkeyboard sound spell\n#dev-qt/qtwayland (nothing needed)\n#kde-framework/syntax-highlighting (nothing needed)\nsys-power/upower introspection\n#gui-apps/wtype (no use flags)\n#x11-misc/ydotool (no use flags)\n\n################### WIDGETS ###################\ngui-apps/fuzzel png svg\ndev-libs/glib dbus elf introspection mime xattr\n# ngl idk about nm-connection-editor. Works fine without\n#app-i18n/translate-shell (nothing needed)\n#gui-apps/wlogout (no use flags)\nmedia-gfx/imagemagick xml\n\n################### OTHER ###################\ndev-cpp/cpptrace unwind\n"
  },
  {
    "path": "sdata/dist-nix/README.md",
    "content": "# Install scripts using Nix to achieve cross-distros\n- This directory is currently WIP.\n- See also [Install scripts | illogical-impulse](https://ii.clsty.link/en/dev/inst-script/)\n- See also [#1061](https://github.com/end-4/dots-hyprland/issues/1061)\n\n**NOTE: The `dist-nix` is not for NixOS but every distro, using Nix and home-manager.**\n\nAs we all know Nix and Home-manager has two major functionalities:\n- Handling dependencies (i.e. package installation)\n- Handling dotfiles\n\nThey are discussed in following sections.\n\n# Handling dependencies\n## Status\nPartially works. See [Discussion #2382](https://github.com/end-4/dots-hyprland/discussions/2382).\n## plan\nNote that this script must be idempotent.\n\nTODO:\n- [ ] Fix all TODOs inside `dist-nix`. ([search online](https://github.com/search?q=repo%3Aend-4%2Fdots-hyprland+path%3A%2F%5Esdata%5C%2Fdist-nix%5C%2F%2F+TODO&type=code))\n- [ ] Since Nix uses a large number of inodes, need to warn user if inode-limited filesystem (typically ext4) is used.\n- [ ] Deal with error when running `systemctl --user enable ydotool --now`:\n  ```plain\n  Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined (consider using --machine=<user>@.host --user to connect to bus of other user)\n  ```\n- [ ] Handle problem that `pkill qs` and `pkill hyprland` does not work (should be `.quickshell-wra` and `.Hyprland-wrapp` when installed via Nix).\n\n## Attentions\n### PAM\nOn non-NixOS distros, programs using PAM (typically screen locker) will not work if installed via Nix, so user has to use their own distro's package for the screen lock.\n\n- One problem is that Debian(-based) distros use modified version of PAM which supports `@include` directive in `/etc/pam.d` config files but the PAM from Nix does not support it, see [this comment](https://github.com/NixOS/nixpkgs/issues/128523#issuecomment-1086106614).\n- Another problem is the location of a suid helper binary that is necessary, see [this comment](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3403195230).\n\nAs [commented](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3403195230) by @Cu3PO42 , both the problem could be solved by using the system-provided libpam instead.\n\nSee also [caelestia-dots/shell#668](https://github.com/caelestia-dots/shell/issues/668).\n\n### GPU\nOn non-NixOS distros, packages installed via home-manager have problem accessing GPU, especially Hyprland because it requires GPU acceleration to launch.\n\n~~`nixGL` should be used to address the problem.~~\n\nSince home-manager 25.11, for non-NixOS just set the following:\n```nix\ntargets.genericLinux.enable = true;\n```\nThen during building, home-manager will show a message to tell you running a command manually to configure GPU, like:\n```bash\nsudo /nix/store/<HASH>-non-nixos-gpu/bin/non-nixos-gpu-setup\n```\nIt runs a bash script with following content:\n```\n#!/nix/store/<HASH>-bash-<VERSION>/bin/bash\n\nset -e\n\n# Install the systemd service file and ensure that the store path won't be\n# garbage-collected as long as it's installed.\nunit_path=/etc/systemd/system/non-nixos-gpu.service\nln -sf /nix/store/<HASH>-non-nixos-gpu/resources/non-nixos-gpu.service \"$unit_path\"\nln -sf \"$unit_path\" \"/nix/var/nix\"/gcroots/non-nixos-gpu.service\n\nsystemctl daemon-reload\nsystemctl enable non-nixos-gpu.service\nsystemctl restart non-nixos-gpu.service\n```\n_Note: it uses `systemctl`, maybe won't work for OpenRC..._\n\nSee [gpu-non-nixos](https://nix-community.github.io/home-manager/index.xhtml#sec-usage-gpu-non-nixos).\n\n# Handling dot files\n## Status\nPaused, until some suitable method has been confirmed to meet the requirements below.\n## Requirements\nAbout handling the dotfiles, i.e. `dots/`, if we are doing this using Nix then the following requirements must be fulfilled.\n\n**1. Allow modifications over the existing dotfiles.**\n\nCurrent state of `./setup install`:\n- After finishing running `./setup install`, users can modify any dotfiles in a traditional way, and if they run `./setup install` again to update then they need to skip the steps which overwrite the targets that they have modified and later sync the upgrade manually for such targets by themselves.\n  - For Hyprland, specially we have a `custom` folder along with `~/.config/hypr/hyprland.conf` which will only get overwritten the first time but not the later times running `./setup install`.\n- This works but is not elegant. An experimental solution is using yaml config to store the selected behavior for each target, see [#2137](https://github.com/end-4/dots-hyprland/issues/2137).\n\nIf we use Nix to handle dotfiles, then it must be at least better than the current state described above, mainly in terms of convenience and automation.\n\n**2. Allow choosing targets.**\n\nThis is similar to the above. For example user may want to use their own `~/.config/foot` instead of the files under `dots/.config/foot` entirely.\n\n**3. Easy developing dotfiles or at least not worse than current state.**\n\nAbout the current state:\n- @clsty: \"If I were the one who develops the dotfiles, I will make changes to the local Git repo `dots-hyprland` and rerun `./setup install-files -f` to apply the changes to observe the outcome.\"\n- @end-4 (who develops the dots; see [comment](https://github.com/end-4/dots-hyprland/pull/2278#issuecomment-3454929577)): \"I modify my local copy of stuff, copy the relevant parts over, optionally selectively pick changes then commit. It's.... the most obvious way but I guess not necessarily the cleanest\"\n\nIf we use Nix to handle dotfiles, then it must be at least better than the current state described above, mainly in terms of convenience and automation.\n\n**4. Others**\n\nFind out a good method to avoid what @end-4 [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-2954725029):\n\n> About home-manager, from my limited understanding of and experience with it, any change to the config files require a rebuild right? If this is indeed the case, switching entirely to this is not okay. Having to wait 20 seconds for each change is absurd.\n\nSome information may help, e.g. @darsh032 [commented](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3336839862):\n\n> I mean thats not really needed you can use mkOutOfStoreSymlink or use hjem-impure to change the configs without rebuilding\n\nAnd also the \"hmrice\" [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3353345504) by @Markus328 , and the `flake.nix` (for quickshell only) [mentioned](https://github.com/end-4/dots-hyprland/issues/1061#issuecomment-3354387126) by @darsh032 .\n"
  },
  {
    "path": "sdata/dist-nix/home-manager/flake.nix",
    "content": "# flake.nix\n{\n  description = \"illogical-impulse\";\n\n  inputs = {\n    nixpkgs.url = \"nixpkgs/nixos-25.11\";\n    #nixpkgs.url = \"nixpkgs/nixos-unstable\";\n\n    home-manager = {\n      url = \"github:nix-community/home-manager/release-25.11\";\n      #url = \"github:nix-community/home-manager/master\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n    #nixgl.url = \"github:nix-community/nixGL\";\n    quickshell = {\n      url = \"github:quickshell-mirror/quickshell/7511545ee20664e3b8b8d3322c0ffe7567c56f7a\";\n      inputs.nixpkgs.follows = \"nixpkgs\";\n    };\n  };\n\n  outputs = { nixpkgs, home-manager, \n  #nixgl,\n  quickshell, ... }:\n    let\n      home_attrs = rec {\n        username = import ./username.nix;\n        homeDirectory = \"/home/${username}\";\n        # Do not edit stateVersion value, see https://github.com/nix-community/home-manager/issues/5794\n        stateVersion = \"25.05\";\n      };\n      system = \"x86_64-linux\";\n      lib = nixpkgs.lib;\n      pkgs = import nixpkgs {\n        inherit system;\n      };\n    in {\n      homeConfigurations = {\n        illogical_impulse = home-manager.lib.homeManagerConfiguration {\n          inherit pkgs;\n          extraSpecialArgs = { inherit home_attrs \n          #nixgl\n          quickshell; };\n          modules = [ \n            ./home.nix\n          ];\n        };\n      };\n    };\n}\n"
  },
  {
    "path": "sdata/dist-nix/home-manager/home.nix",
    "content": "{ config, lib, pkgs, \n#nixgl, \nquickshell, home_attrs, ... }:\n{\n  programs.home-manager.enable = true;\n\n  # Necessary for non-NixOS to handle GPU (since home-manager version 25.11)\n  targets.genericLinux.enable = true;\n  #nixGL.packages = nixgl.packages;\n  #nixGL.defaultWrapper = \"mesa\";\n\n  xdg.portal = {\n    enable = true;\n    extraPortals = with pkgs; [\n      xdg-desktop-portal-gnome\n      xdg-desktop-portal-gtk\n      xdg-desktop-portal-wlr\n      kdePackages.xdg-desktop-portal-kde\n    ];\n    # The following seems to generate ~/.config/xdg-desktop-portal conflicting with the one under dots/\n    #config.hyprland = {\n    #  default = [ \"hyprland\" \"gtk\" ];\n    #  \"org.freedesktop.impl.portal.ScreenCast\" = [ \"gnome\" ];\n    #};\n  };\n  # Note: The following generate files under ~/.config/fontconfig/conf.d/\n  # fontconfig may rely on this to properly find fonts installed via Nix.\n  fonts.fontconfig.enable = true;\n\n  wayland.windowManager.hyprland = {\n    ## Make sure home-manager not generate ~/.config/hypr/hyprland.conf\n    systemd.enable = false; plugins = []; settings = {}; extraConfig = \"\";\n    enable = true;\n    ## Use NixGL\n    #package = config.lib.nixGL.wrap pkgs.hyprland;\n    package = pkgs.hyprland;\n  };\n\n  home = {\n    packages = with pkgs; [\n      ##### Sure #####\n      ## Basic cli tool\n      ## inetutils: provides hostname, ifconfig, ping, etc.\n      ## libnotify: provides notify-send\n      inetutils libnotify\n\n      ##### Other MISC #####\n      dbus xorg.xlsclients # some basic things\n      foot # Used in Quickshell and Hyprland config; its config is also included\n      kdePackages.kconfig # provide kwriteconfig6, used in install script\n\n\n      ##### Not work, to be solved #####\n      # hyprlock pamtester\n      \n\n      # NOTE: below are migrated from dist-arch. For each package, must know why it's needed and how it's used specifically, cuz things may be need tweak to properly use the package installed by Nix, for example those have hardcoded path /usr/* . See sdata/deps-info.md\n      ### illogical-impulse-audio\n      libcava #cava\n      lxqt.pavucontrol-qt #pavucontrol-qt\n      wireplumber #wireplumber\n      pipewire #pipewire-pulse\n      libdbusmenu-gtk3 #libdbusmenu-gtk3\n      playerctl #playerctl\n\n\n      ### illogical-impulse-backlight\n      (geoclue2.override { withDemoAgent = true; }) #geoclue\n      brightnessctl #brightnessctl\n      ddcutil #ddcutil\n\n\n      ### illogical-impulse-basic\n      bc #bc\n      uutils-coreutils-noprefix #coreutils\n      cliphist #cliphist\n      cmake #cmake\n      curlFull #curl\n      wget #wget\n      ripgrep #ripgrep\n      jq #jq\n      xdg-user-dirs #xdg-user-dirs\n      rsync #rsync\n      yq-go #go-yq\n\n\n      ### illogical-impulse-bibata-modern-classic-bin\n      bibata-cursors\n\n\n      ### illogical-impulse-fonts-themes\n      adw-gtk3 #adw-gtk-theme-git\n      kdePackages.breeze kdePackages.breeze-icons #breeze\n      #breeze-plus (TODO: Not available as nixpkg)\n      darkly darkly-qt5 #darkly-bin\n      eza #eza\n      #fish (Currently install via system PM; TODO: should install via nix in future when authentication problem fixed)\n      fontconfig #fontconfig\n      kitty #kitty (Used in fuzzel, Hyprland, kdeglobals and Quickshell config; kitty config is also included as dots)\n      matugen #matugen-bin (Used in Quickshell)\n      #otf-space-grotesk (TODO: Not available as Nixpkg)\n      starship #starship\n      nerd-fonts.jetbrains-mono #ttf-jetbrains-mono-nerd\n      material-symbols #ttf-material-symbols-variable-git\n      #ttf-readex-pro (TODO: seems not available as nixpkg)\n      rubik #ttf-rubik-vf\n      twemoji-color-font #ttf-twemoji\n\n\n      ### illogical-impulse-hyprland\n      #hyprland\n      hyprsunset #hyprsunset\n      wl-clipboard #wl-clipboard\n\n\n      ### illogical-impulse-kde\n      kdePackages.bluedevil #bluedevil\n      #gnome-keyring #gnome-keyring (TODO: Install via system PM instead; should install via nix in future when authentication problem fixed)\n      networkmanager #networkmanager\n      kdePackages.plasma-nm #plasma-nm\n      #polkit-kde-agent (TODO: Install via system PM instead; should install via nix in future when authentication problem fixed)\n      kdePackages.dolphin #dolphin\n      kdePackages.systemsettings #systemsettings\n\n      \n      ### illogical-impulse-microtex-git\n      # TODO: Not available as nixpkg\n\n\n      ### illogical-impulse-portal\n      #xdg-desktop-portal (Included elsewhere)\n      #xdg-desktop-portal-kde (Included elsewhere)\n      #xdg-desktop-portal-gtk (Included elsewhere)\n      #xdg-desktop-portal-hyprland (Included elsewhere)\n\n\n      ### illogical-impulse-python\n      #clang (Not needed for Nix. However, when cmake is installed by Nix, then pkg-config, cairo etc will be used but they can only be accessible in Nix development environment for example nix-shell, nix develop, etc. See `sdata/uv/shell.nix`. )\n      uv #uv\n      gtk4 #gtk4\n      libadwaita #libadwaita\n      libsoup_3 #libsoup3\n      libportal-gtk4 #libportal-gtk4\n      gobject-introspection #gobject-introspection\n\n\n      ### illogical-impulse-screencapture\n      hyprshot #hyprshot\n      slurp #slurp\n      swappy #swappy\n      tesseract #tesseract\n      #tesseract-data-eng (TODO: Seems not available as nixpkg)\n      wf-recorder #wf-recorder\n\n\n      ### illogical-impulse-toolkit\n      upower #upower\n      wtype #wtype\n      ydotool #ydotool\n\n\n      ### illogical-impulse-widgets\n      fuzzel #fuzzel\n      glib #glib2\n      imagemagick #imagemagick\n      hypridle #hypridle\n      #hyprlock (Should not be installed via Nix; TODO: should install via nix in future when authentication problem fixed)\n      hyprpicker #hyprpicker\n      songrec #songrec\n      translate-shell #translate-shell\n      wlogout #wlogout\n      libqalculate #libqalculate\n\n    ]\n    ++ [\n    #(config.lib.nixGL.wrap pkgs.hyprland)\n\n    ### illogical-impulse-quickshell-git\n    #(config.lib.nixGL.wrap quickshell.packages.x86_64-linux.default)\n    (import ./quickshell.nix { inherit pkgs quickshell; \n    #nixGLWrap = config.lib.nixGL.wrap;\n    })\n    ];\n  }//home_attrs;\n}\n"
  },
  {
    "path": "sdata/dist-nix/home-manager/quickshell.nix",
    "content": "{ pkgs, quickshell, \n#nixGLWrap,\n... }:\nlet\n  #qs = nixGLWrap quickshell.packages.x86_64-linux.default;\n  qs = quickshell.packages.x86_64-linux.default;\nin pkgs.stdenv.mkDerivation {\n  name = \"illogical-impulse-quickshell-wrapper\";\n  meta = with pkgs.lib; {\n    #description = \"Quickshell wrapped with NixGL + bundled Qt deps for home-manager usage\";\n    description = \"Quickshell bundled Qt deps for home-manager usage\";\n    license = licenses.gpl3Only;\n  };\n\n  dontUnpack = true;\n  dontConfigure = true;\n  dontBuild = true;\n\n  nativeBuildInputs = [\n    pkgs.makeWrapper\n    pkgs.qt6.wrapQtAppsHook\n  ];\n\n  buildInputs = with pkgs; [\n    qs\n    kdePackages.qtwayland\n    kdePackages.qtpositioning\n    kdePackages.qtlocation\n    kdePackages.syntax-highlighting\n    gsettings-desktop-schemas\n    # https://nixos.wiki/wiki/Qt\n    # https://github.com/NixOS/nixpkgs/blob/master/pkgs/development/libraries/qt-6/srcs.nix\n    qt6.qtbase #qt6-base\n    qt6.qtdeclarative #qt6-declarative\n    qt6.qt5compat #qt6-5compat\n    #qt6-avif-image-plugin (TODO: seems not available as nixpkg)\n    qt6.qtimageformats #qt6-imageformats\n    qt6.qtmultimedia #qt6-multimedia\n    qt6.qtpositioning #qt6-positioning\n    qt6.qtquicktimeline #qt6-quicktimeline\n    qt6.qtsensors #qt6-sensors\n    qt6.qtsvg #qt6-svg\n    qt6.qttools #qt6-tools\n    qt6.qttranslations #qt6-translations\n    qt6.qtvirtualkeyboard #qt6-virtualkeyboard\n    qt6.qtwayland #qt6-wayland\n    kdePackages.kirigami #kirigami\n    kdePackages.kdialog #kdialog\n    kdePackages.syntax-highlighting #syntax-highlighting\n    vulkan-headers #vulkan-headers\n    libdrm #libdrm\n    cpptrace #cpptrace\n    jemalloc #jemalloc\n    mesa #mesa\n  ];\n\n  installPhase = ''\n    mkdir -p $out/bin\n    ls -l ${qs}/bin || true\n    makeWrapper ${qs}/bin/qs $out/bin/qs \\\n      --prefix XDG_DATA_DIRS : ${pkgs.gsettings-desktop-schemas}/share/gsettings-schemas/${pkgs.gsettings-desktop-schemas.name}\n    chmod +x $out/bin/qs\n  '';\n}\n"
  },
  {
    "path": "sdata/dist-nix/install-deps.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\nfunction vianix-warning(){\n  printf \"${STY_YELLOW}\"\n  printf \"Currently \\\"--via-nix\\\" will run:\\n\"\n  printf \"  home-manager switch --flake .#illogical_impulse\\n\"\n  printf \"If you are already using home-manager,\\n\"\n  printf \"it may override your current config,\\n\"\n  printf \"despite that this should be reversible.\\n\"\n  printf \"${STY_RST}\"\n  pause\n}\nfunction install_nix(){\n  # https://github.com/NixOS/experimental-nix-installer\n  local cmd=nix\n\n  x mkdir -p ${REPO_ROOT}/cache\n  x curl -JLo ${REPO_ROOT}/cache/nix-installer https://artifacts.nixos.org/experimental-installer\n  x sh ${REPO_ROOT}/cache/nix-installer install\n  try source '/nix/var/nix/profiles/default/etc/profile.d/nix-daemon.sh'\n\n  command -v $cmd && return\n  echo \"Failed in installing $cmd.\"\n  echo \"Please install it by yourself and then retry.\"\n  return 1\n}\nfunction install_home-manager(){\n  # https://nix-community.github.io/home-manager/index.xhtml#sec-install-standalone\n  local cmd=home-manager\n  # Maybe installed already, just not sourced yet\n  try source $HOME/.nix-profile/etc/profile.d/hm-session-vars.sh\n  command -v $cmd && return\n\n  x nix-channel --add https://nixos.org/channels/nixos-25.11 nixpkgs-home\n  x nix-channel --add https://github.com/nix-community/home-manager/archive/release-25.11.tar.gz home-manager\n  x nix-channel --update\n  x env NIX_PATH=\"nixpkgs=$HOME/.nix-defexpr/channels/nixpkgs-home\" nix-shell '<home-manager>' -A install\n\n  command -v $cmd && return\n  echo \"Failed in installing $cmd.\"\n  echo \"Please install it by yourself and then retry.\"\n  echo \"\"\n  echo \"Hint: It's also possible that the installation is actually successful,\"\n  echo \"but your \\\"\\$PATH\\\" is not properly set.\"\n  echo \"This can happen when you have used \\\"su user\\\" to switch user.\"\n  echo \"If this is the problem, use \\\"su - user\\\" instead.\"\n  return 1\n}\nfunction hm_deps(){\n  SETUP_HM_DIR=\"${REPO_ROOT}/sdata/dist-nix/home-manager\"\n  SETUP_USERNAME_NIXFILE=\"${SETUP_HM_DIR}/username.nix\"\n  echo \"\\\"$(whoami)\\\"\" > \"${SETUP_USERNAME_NIXFILE}\"\n  x git add \"${SETUP_USERNAME_NIXFILE}\"\n  cd $SETUP_HM_DIR\n  x home-manager switch --flake .#illogical_impulse \\\n    --extra-experimental-features nix-command \\\n    --extra-experimental-features flakes\n  x sudo /nix/store/*-non-nixos-gpu/bin/non-nixos-gpu-setup\n  cd $REPO_ROOT\n  x git rm -f \"${SETUP_USERNAME_NIXFILE}\"\n}\n\n##################################################\n##################################################\n\nvianix-warning\n\nTEST_CMDS=(curl fish swaylock gnome-keyring)\nensure_cmds \"${TEST_CMDS[@]}\"\n\nif ! command -v nix >/dev/null 2>&1;then\n  echo -e \"${STY_YELLOW}[$0]: \\\"nix\\\" not found.${STY_RST}\"\n  showfun install_nix\n  v install_nix\nfi\nif ! command -v home-manager >/dev/null 2>&1;then\n  echo -e \"${STY_YELLOW}[$0]: \\\"home-manager\\\" not found.${STY_RST}\"\n  showfun install_home-manager\n  v install_home-manager\nfi\n\nshowfun hm_deps\nv hm_deps\n"
  },
  {
    "path": "sdata/dist-nix/outdate-detect-mode",
    "content": "WIP\n"
  },
  {
    "path": "sdata/lib/dist-determine.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\nfunction print_os_group_id(){\n    printf \"${STY_CYAN}\"\n    printf \"===INFO===\\n\"\n    printf \"Detected OS_DISTRO_ID: ${OS_DISTRO_ID}\\n\"\n    printf \"Detected OS_DISTRO_ID_LIKE: ${OS_DISTRO_ID_LIKE}\\n\"\n    printf \"Determined OS_GROUP_ID: ${OS_GROUP_ID}\\n\"\n    printf \"==========\\n\\n\"\n    printf \"${STY_RST}\"\n}\nfunction print_os_group_id_alike(){\n    printf \"${STY_YELLOW}\"\n    printf \"===WARNING===\\n\"\n    printf \"Your OS_GROUP_ID has been determined by \\\"alike\\\" match.\\n\"\n    printf \"Ideally, it should also work for your distro.\\n\"\n    printf \"Still, there is a chance that it not works as expected or even fails.\\n\"\n    printf \"Proceed only at your own risk.\\n\"\n    printf \"=============\\n\\n\"\n    printf \"${STY_RST}\"\n}\nfunction print_os_group_id_unofficial(){\n    printf \"${STY_PURPLE}\"\n    printf \"===NOTICE===\\n\"\n    printf \"The support for your distro is provided by the community.\\n\"\n    printf \"It is not officially supported by github:end-4/dots-hyprland .\\n\"\n    printf \"${STY_BOLD}\"\n    printf \"If you find out any problems about it, PR is welcomed if you are able to address it.\\n\"\n    printf \"Or, create a discussion about it, but please do not submit issue, \\n\"\n    printf \"because the developers do not use this distro, therefore they cannot help.${STY_RST}\\n\"\n    printf \"${STY_PURPLE}\"\n    printf \"Proceed only at your own risk.\\n\"\n    printf \"============\\n\\n\"\n    printf \"${STY_RST}\"\n}\nfunction print_os_group_id_unsupported(){\n    printf \"${STY_RED}\"\n    printf \"===CAUTION===\\n\"\n    printf \"\\\"--via-nix\\\" is forcely specified \\n\"\n    printf \"as the only way to try to install on your distro.\\n\"\n    printf \"It is still experimental.\\n\"\n    printf \"Some functionalities are missing.\\n\"\n    printf \"It may also behave unexpectedly.\\n\"\n    printf \"Proceed only at your own risk.\\n\"\n    printf \"=============\\n\\n\"\n    printf \"${STY_RST}\"\n    sleep 3\n}\nfunction print_os_group_id_fallback(){\n    printf \"${STY_RED}\"\n    printf \"===CAUTION===\\n\"\n    printf \"Distro not recognized, determined as fallback.\\n\"\n    printf \"=============\\n\\n\"\n    printf \"${STY_RST}\"\n}\nfunction print_os_group_id_architecture(){\n    printf \"${STY_RED}\"\n    printf \"===CAUTION===\\n\"\n    printf \"Detected machine architecture: ${MACHINE_ARCH}\\n\"\n    printf \"This script only supports x86_64.\\n\"\n    printf \"It is very likely to fail when installing dependencies on your machine.\\n\"\n    printf \"=============\\n\\n\"\n    printf \"${STY_RST}\"\n}\n#####################################################################################\n\n####################\n# Detect distro\n# Helpful link(s):\n# http://stackoverflow.com/questions/29581754\n# https://github.com/which-distro/os-release\nOS_RELEASE_FILE_CUSTOM=\"${REPO_ROOT}/os-release\"\nif test -f \"${OS_RELEASE_FILE_CUSTOM}\"; then\n  printf \"${STY_YELLOW}Warning: using custom os-release file \\\"${OS_RELEASE_FILE_CUSTOM}\\\".${STY_RST}\\n\"\n  OS_RELEASE_FILE=\"${OS_RELEASE_FILE_CUSTOM}\"\nelif test -f /etc/os-release; then\n  OS_RELEASE_FILE=/etc/os-release\nelse\n  printf \"${STY_RED}/etc/os-release does not exist, aborting...${STY_RST}\\n\" ; exit 1\nfi\nexport OS_DISTRO_ID=$(awk -F'=' '/^ID=/ { gsub(/[\"\\x27]/,\"\",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null)\nexport OS_DISTRO_ID_LIKE=$(awk -F'=' '/^ID_LIKE=/ { gsub(/[\"\\x27]/,\"\",$2); print tolower($2) }' ${OS_RELEASE_FILE} 2> /dev/null)\n\n\n####################\n# Determine distro ID\n\nif [[ \"$OS_DISTRO_ID\" =~ ^(arch|endeavouros|cachyos)$ ]]; then\n  OS_GROUP_ID=\"arch\"\n  print_os_group_id_functions=(print_os_group_id)\nelif [[ \"$OS_DISTRO_ID_LIKE\" == \"arch\" ]]; then\n  OS_GROUP_ID=\"arch\"\n  print_os_group_id_functions=(print_os_group_id{,_alike})\nelif [[ \"$OS_DISTRO_ID\" == \"gentoo\" ]]; then\n  OS_GROUP_ID=\"gentoo\"\n  print_os_group_id_functions=(print_os_group_id{,_unofficial})\nelif [[ \"$OS_DISTRO_ID_LIKE\" == \"gentoo\" ]]; then\n  OS_GROUP_ID=\"gentoo\"\n  print_os_group_id_functions=(print_os_group_id{,_alike,_unofficial})\nelif [[ \"$OS_DISTRO_ID\" == \"fedora\" ]]; then\n  OS_GROUP_ID=\"fedora\"\n  print_os_group_id_functions=(print_os_group_id{,_unofficial})\nelif [[ \"$OS_DISTRO_ID_LIKE\" == \"fedora\" ]]; then\n  OS_GROUP_ID=\"fedora\"\n  print_os_group_id_functions=(print_os_group_id{,_alike,_unofficial})\nelif [[ \"$OS_DISTRO_ID\" =~ ^(opensuse-leap|opensuse-tumbleweed)$ ]] || [[ \"$OS_DISTRO_ID_LIKE\" =~ ^(opensuse|suse)(\\ (opensuse|suse))?$ ]]; then\n  OS_GROUP_ID=\"suse\"\n  INSTALL_VIA_NIX=true\n  print_os_group_id_functions=(print_os_group_id{,_unsupported})\nelif [[ \"$OS_DISTRO_ID\" == \"debian\" || \"$OS_DISTRO_ID_LIKE\" == \"debian\" ]]; then\n  OS_GROUP_ID=\"debian\"\n  INSTALL_VIA_NIX=true\n  print_os_group_id_functions=(print_os_group_id{,_unsupported})\nelse\n  OS_GROUP_ID=\"fallback\"\n  INSTALL_VIA_NIX=true\n  print_os_group_id_functions=(print_os_group_id{,_fallback,_unsupported})\nfi\n\n####################\n# Detect architecture\n# Helpful link(s):\n# http://stackoverflow.com/questions/45125516\nexport MACHINE_ARCH=$(uname -m)\ncase \"${MACHINE_ARCH}\" in\n  \"x86_64\") sleep 0;;\n  *) print_os_group_id_functions+=(print_os_group_id_architecture);;\nesac\n"
  },
  {
    "path": "sdata/lib/environment-variables.sh",
    "content": "# This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang.\nXDG_BIN_HOME=${XDG_BIN_HOME:-$HOME/.local/bin}\nXDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}\nXDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}\nXDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}\nXDG_STATE_HOME=${XDG_STATE_HOME:-$HOME/.local/state}\n\nSTY_RED='\\e[31m'\nSTY_GREEN='\\e[32m'\nSTY_YELLOW='\\e[33m'\nSTY_BLUE='\\e[34m'\nSTY_PURPLE='\\e[35m'\nSTY_CYAN='\\e[36m'\n\nSTY_BOLD='\\e[1m'\nSTY_FAINT='\\e[2m'\nSTY_SLANT='\\e[3m'\nSTY_UNDERLINE='\\e[4m'\nSTY_BLINK='\\e[5m'\nSTY_INVERT='\\e[7m'\nSTY_RST='\\e[00m'\n\n# Used by register_temp_file()\ndeclare -a TEMP_FILES_TO_CLEANUP=()\n\n# Used by install script\nBACKUP_DIR=\"${BACKUP_DIR:-$HOME/ii-original-dots-backup}\"\nDOTS_CORE_CONFDIR=\"${XDG_CONFIG_HOME}/illogical-impulse\"\nINSTALLED_LISTFILE=\"${DOTS_CORE_CONFDIR}/installed_listfile\"\nFIRSTRUN_FILE=\"${DOTS_CORE_CONFDIR}/installed_true\"\n"
  },
  {
    "path": "sdata/lib/functions.sh",
    "content": "# This is NOT a script for execution, but for loading functions, so NOT need execution permission or shebang.\n# NOTE that you NOT need to `cd ..' because the `$0' is NOT this file, but the script file which will source this file.\n\n# shellcheck shell=bash\n\nfunction try { \"$@\" || sleep 0; }\nfunction v(){\n  echo -e \"####################################################\"\n  echo -e \"${STY_BLUE}[$0]: Next command:${STY_RST}\"\n  echo -e \"${STY_GREEN}$*${STY_RST}\"\n  local execute=true\n  if $ask;then\n    while true;do\n      echo -e \"${STY_BLUE}Execute? ${STY_RST}\"\n      echo \"  y = Yes\"\n      echo \"  e = Exit now\"\n      echo \"  s = Skip this command (NOT recommended - your setup might not work correctly)\"\n      echo \"  yesforall = Yes and don't ask again; NOT recommended unless you really sure\"\n      local p; read -p \"====> \" p\n      case $p in\n        [yY]) echo -e \"${STY_BLUE}OK, executing...${STY_RST}\" ;break ;;\n        [eE]) echo -e \"${STY_BLUE}Exiting...${STY_RST}\" ;exit ;break ;;\n        [sS]) echo -e \"${STY_BLUE}Alright, skipping this one...${STY_RST}\" ;execute=false ;break ;;\n        \"yesforall\") echo -e \"${STY_BLUE}Alright, won't ask again. Executing...${STY_RST}\"; ask=false ;break ;;\n        *) echo -e \"${STY_RED}Please enter [y/e/s/yesforall].${STY_RST}\";;\n      esac\n    done\n  fi\n  if $execute;then x \"$@\";else\n    echo -e \"${STY_YELLOW}[$0]: Skipped \\\"$*\\\"${STY_RST}\"\n  fi\n}\n# When use v() for a defined function, use x() INSIDE its definition to catch errors.\nfunction x(){\n  if \"$@\";then local cmdstatus=0;else local cmdstatus=1;fi # 0=normal; 1=failed; 2=failed but ignored\n  while [ $cmdstatus == 1 ] ;do\n    echo -e \"${STY_RED}[$0]: Command \\\"${STY_GREEN}$*${STY_RED}\\\" has failed.\"\n    echo -e \"You may need to resolve the problem manually BEFORE repeating this command.\"\n    echo -e \"[Tip] If a certain package is failing to install, try installing it separately in another terminal.${STY_RST}\"\n    echo \"  r = Repeat this command (DEFAULT)\"\n    echo \"  e = Exit now\"\n    echo \"  i = Ignore this error and continue (your setup might not work correctly)\"\n    local p; read -p \" [R/e/i]: \" p\n    case $p in\n      [iI]) echo -e \"${STY_BLUE}Alright, ignore and continue...${STY_RST}\";cmdstatus=2;;\n      [eE]) echo -e \"${STY_BLUE}Alright, will exit.${STY_RST}\";break;;\n      *) echo -e \"${STY_BLUE}OK, repeating...${STY_RST}\"\n         if \"$@\";then cmdstatus=0;else cmdstatus=1;fi\n         ;;\n    esac\n  done\n  case $cmdstatus in\n    0) echo -e \"${STY_BLUE}[$0]: Command \\\"${STY_GREEN}$*${STY_BLUE}\\\" finished.${STY_RST}\";;\n    1) echo -e \"${STY_RED}[$0]: Command \\\"${STY_GREEN}$*${STY_RED}\\\" has failed. Exiting...${STY_RST}\";exit 1;;\n    2) echo -e \"${STY_RED}[$0]: Command \\\"${STY_GREEN}$*${STY_RED}\\\" has failed but ignored by user.${STY_RST}\";;\n  esac\n}\nfunction showfun(){\n  echo -e \"${STY_BLUE}[$0]: The definition of function \\\"$1\\\" is as follows:${STY_RST}\"\n  printf \"${STY_GREEN}\"\n  type -a \"$1\" 2>/dev/null || return 1\n  printf \"${STY_RST}\"\n}\nfunction pause(){\n  if [ ! \"$ask\" == \"false\" ];then\n    printf \"${STY_FAINT}${STY_SLANT}\"\n    local p; read -p \"(Ctrl-C to abort, Enter to proceed)\" p\n    printf \"${STY_RST}\"\n  fi\n}\nfunction remove_bashcomments_emptylines(){\n  echo \"pwd=$(pwd)\"\n  echo \"input=$1\"\n  echo \"output=$2\"\n  mkdir -p \"$(dirname \"$2\")\"\n  cat \"$1\" | sed -e 's/#.*//' -e '/^[[:space:]]*$/d' > \"$2\"\n}\nfunction prevent_sudo_or_root(){\n  case $(whoami) in\n    root) echo -e \"${STY_RED}[$0]: This script is NOT to be executed with sudo or as root. Aborting...${STY_RST}\";exit 1;;\n  esac\n}\n\n# Initialize sudo session and keep it alive in background\n# Store PID in a global variable that can be accessed by trap\ndeclare -g SUDO_KEEPALIVE_PID=\"\"\n\nfunction sudo_init_keepalive(){\n  # Check if sudo is available\n  if ! command -v sudo >/dev/null 2>&1; then\n    return 0\n  fi\n\n  # Skip if already initialized\n  if [[ -n \"$SUDO_KEEPALIVE_PID\" ]] && kill -0 \"$SUDO_KEEPALIVE_PID\" 2>/dev/null; then\n    return 0\n  fi\n\n  # Prompt for sudo password once at the beginning\n  echo -e \"${STY_CYAN}[$0]: Requesting sudo privileges for installation...${STY_RST}\"\n  if ! sudo true; then\n    echo -e \"${STY_RED}[$0]: Failed to obtain sudo privileges. Aborting...${STY_RST}\"\n    exit 1\n  fi\n\n  # Start background process to keep sudo session alive\n  # This updates the sudo timestamp every 60 seconds\n  (\n    while true; do\n      sleep 60\n      sudo true 2>/dev/null || exit 0\n    done\n  ) &\n  SUDO_KEEPALIVE_PID=$!\n\n  echo -e \"${STY_GREEN}[$0]: Sudo session initialized and will be kept alive (PID: $SUDO_KEEPALIVE_PID)${STY_RST}\"\n}\n\n# Stop the sudo keepalive background process\nfunction sudo_stop_keepalive(){\n  if [[ -n \"$SUDO_KEEPALIVE_PID\" ]] && kill -0 \"$SUDO_KEEPALIVE_PID\" 2>/dev/null; then\n    kill \"$SUDO_KEEPALIVE_PID\" 2>/dev/null || true\n    wait \"$SUDO_KEEPALIVE_PID\" 2>/dev/null || true\n    SUDO_KEEPALIVE_PID=\"\"\n  fi\n}\nfunction git_auto_unshallow(){\n# We need this function for latest_commit_hash to work properly\n  if [[ -f \"$(git rev-parse --git-dir)/shallow\" ]]; then\n    echo \"Shallow clone detected. Unshallowing...\"\n    git fetch --unshallow\n  fi\n}\nfunction latest_commit_timestamp(){\n  local target_path=\"$1\"\n  local result=$(git log -1 --format=\"%ct\" -- \"$target_path\" 2>/dev/null)\n  if [[ -z \"$result\" ]]; then\n    echo \"[latest_commit_timestamp] The timestamp of \\\"$target_path\\\" is empty. Aborting...\" >&2\n    return 1\n  fi\n  echo \"$result\"\n}\n\nfunction log_info() {\n  echo -e \"${STY_BLUE}[INFO]${STY_RST} $1\"\n}\nfunction log_success() {\n  echo -e \"${STY_GREEN}[SUCCESS]${STY_RST} $1\"\n}\nfunction log_warning() {\n  echo -e \"${STY_YELLOW}[WARNING]${STY_RST} $1\"\n}\nfunction log_error() {\n  echo -e \"${STY_RED}[ERROR]${STY_RST} $1\" >&2\n}\nfunction log_header() {\n  echo -e \"\\n${STY_PURPLE}=== $1 ===${STY_RST}\"\n}\nfunction log_die() {\n  log_error \"$1\"\n  exit 1\n}\n\n# Enhanced: Check if command exists\nfunction command_exists() {\n  command -v \"$1\" >/dev/null 2>&1\n}\n\n# Enhanced: Require a command or die\nfunction require_command() {\n  if ! command_exists \"$1\"; then\n    log_die \"Required command '$1' not found. Please install it first.\"\n  fi\n}\n\n# Enhanced: Sanitize file paths to prevent directory traversal\nfunction sanitize_path() {\n  local path=\"$1\"\n  \n  # Remove null bytes, newlines, and control characters\n  path=$(echo \"$path\" | tr -d '\\000-\\037')\n  \n  # Prevent directory traversal beyond current context\n  case \"$path\" in\n    ..|../*|*/../*|*/..|\\.\\./*)\n      log_die \"Invalid path detected (directory traversal attempt): $path\"\n      ;;\n  esac\n  \n  echo \"$path\"\n}\n\n# Enhanced: Safe file comparison that checks existence first\nfunction files_differ() {\n  local file1=\"$1\"\n  local file2=\"$2\"\n  \n  # Check if both files exist\n  if [[ ! -f \"$file1\" ]] || [[ ! -f \"$file2\" ]]; then\n    return 0  # Consider them different if either doesn't exist\n  fi\n  \n  # Quick size check first (faster than byte comparison)\n  local size1 size2\n  if command -v stat &>/dev/null; then\n    # Try both BSD and GNU stat formats\n    size1=$(stat -f%z \"$file1\" 2>/dev/null || stat -c%s \"$file1\" 2>/dev/null)\n    size2=$(stat -f%z \"$file2\" 2>/dev/null || stat -c%s \"$file2\" 2>/dev/null)\n    \n    if [[ \"$size1\" != \"$size2\" ]]; then\n      return 0  # Different sizes = different files\n    fi\n  fi\n  \n  # Then byte-by-byte comparison\n  cmp -s \"$file1\" \"$file2\" && return 1 || return 0\n}\n\n# Enhanced: Create backup of a file with timestamp\nfunction backup_file_simple() {\n  local file=\"$1\"\n  local backup_suffix=\"${2:-.bak}\"\n  \n  if [[ ! -f \"$file\" ]]; then\n    log_warning \"Cannot backup non-existent file: $file\"\n    return 1\n  fi\n  \n  local timestamp\n  timestamp=$(date +%Y%m%d-%H%M%S)\n  local backup_name=\"${file}${backup_suffix}.${timestamp}\"\n  \n  if cp -p \"$file\" \"$backup_name\" 2>/dev/null; then\n    log_info \"Backed up: $file → $backup_name\"\n    return 0\n  else\n    log_error \"Failed to backup: $file\"\n    return 1\n  fi\n}\n\n# Enhanced: Validate that a file path is within allowed directory\nfunction validate_path_in_directory() {\n  local file_path=\"$1\"\n  local allowed_dir=\"$2\"\n  \n  # Resolve to absolute paths\n  local abs_file\n  local abs_dir\n  \n  abs_file=$(cd \"$(dirname \"$file_path\")\" 2>/dev/null && pwd -P)/$(basename \"$file_path\") || return 1\n  abs_dir=$(cd \"$allowed_dir\" 2>/dev/null && pwd -P) || return 1\n  \n  # Check if file path starts with allowed directory\n  case \"$abs_file\" in\n    \"$abs_dir\"/*)\n      return 0\n      ;;\n    *)\n      log_error \"Path validation failed: $file_path is not within $allowed_dir\"\n      return 1\n      ;;\n  esac\n}\n\n# Enhanced: Check if script is running in a CI/CD environment\nfunction is_ci_environment() {\n  [[ -n \"${CI:-}\" ]] || \\\n  [[ -n \"${GITHUB_ACTIONS:-}\" ]] || \\\n  [[ -n \"${GITLAB_CI:-}\" ]] || \\\n  [[ -n \"${TRAVIS:-}\" ]] || \\\n  [[ -n \"${CIRCLECI:-}\" ]]\n}\n\n# Enhanced: Progress bar (optional, for long operations)\nfunction show_progress() {\n  local current=\"$1\"\n  local total=\"$2\"\n  local message=\"${3:-Processing}\"\n  \n  if ! command_exists tput; then\n    return\n  fi\n  \n  local percent=$((current * 100 / total))\n  local bar_length=40\n  local filled=$((bar_length * current / total))\n  local empty=$((bar_length - filled))\n  \n  printf \"\\r${message}: [\" >&2\n  printf \"%${filled}s\" | tr ' ' '=' >&2\n  printf \"%${empty}s\" | tr ' ' ' ' >&2\n  printf \"] %d%%\" \"$percent\" >&2\n  \n  if [[ $current -eq $total ]]; then\n    echo >&2\n  fi\n}\n\n\n# Enhanced: Cleanup temporary files on exit\n#declare -a TEMP_FILES_TO_CLEANUP=()\nfunction register_temp_file() {\n  local temp_file=\"$1\"\n  TEMP_FILES_TO_CLEANUP+=(\"$temp_file\")\n}\n\nfunction cleanup_temp_files() {\n  for temp_file in \"${TEMP_FILES_TO_CLEANUP[@]}\"; do\n    if [[ -f \"$temp_file\" ]]; then\n      rm -f \"$temp_file\" 2>/dev/null || true\n    fi\n  done\n  TEMP_FILES_TO_CLEANUP=()\n}\n\n# Enhanced: Check disk space before operations\nfunction check_disk_space() {\n  local path=\"${1:-.}\"\n  local required_mb=\"${2:-100}\"  # Default 100MB\n  \n  if ! command_exists df; then\n    log_warning \"df command not available, skipping disk space check\"\n    return 0\n  fi\n  \n  local available_kb\n  available_kb=$(df -k \"$path\" | awk 'NR==2 {print $4}')\n  local available_mb=$((available_kb / 1024))\n  \n  if [[ $available_mb -lt $required_mb ]]; then\n    log_warning \"Low disk space: ${available_mb}MB available, ${required_mb}MB recommended\"\n    return 1\n  fi\n  \n  return 0\n}\n\nfunction auto_update_git_submodule(){\n  if git submodule status --recursive | grep -E '^[+-U]';then\n    # Note: `git pull --recurse-submodules` cannot substitute `git submodule update --init --recursive` cuz it does not init a submodule when needed.\n    x git submodule update --init --recursive\n  fi\n}\n\nfunction backup_clashing_targets(){\n  # For non-recursive dirs/files under target_dir, only backup those which clashes with the ones under source_dir\n  # However, ignore the ones listed in ignored_list\n\n  # Deal with arguments\n  local source_dir=\"$1\"\n  local target_dir=\"$2\"\n  local backup_dir=\"$3\"\n  local -a ignored_list=(\"${@:4}\")\n\n  # Find clash dirs/files, save as clash_list\n  local clash_list=()\n  local source_list=($(ls -A \"$source_dir\"))\n  local target_list=($(ls -A \"$target_dir\"))\n  local -A target_map\n  for i in \"${target_list[@]}\"; do\n    target_map[\"$i\"]=1\n  done\n  for i in \"${source_list[@]}\"; do\n    if [[ -n \"${target_map[$i]}\" ]]; then\n      clash_list+=(\"$i\")\n    fi\n  done\n  local -A delk\n  for del in \"${ignored_list[@]}\" ; do delk[$del]=1 ; done\n  for k in \"${!clash_list[@]}\" ; do\n    [ \"${delk[${clash_list[$k]}]-}\" ] && unset 'clash_list[k]'\n  done\n  clash_list=(\"${clash_list[@]}\")\n\n  # Construct args_includes for rsync\n  local args_includes=()\n  for i in \"${clash_list[@]}\"; do\n    if [[ -d \"$target_dir/$i\" ]]; then\n      args_includes+=(--include=\"/$i/\")\n      args_includes+=(--include=\"/$i/**\")\n    else\n      args_includes+=(--include=\"/$i\")\n    fi\n  done\n  args_includes+=(--exclude='*')\n\n  x mkdir -p $backup_dir\n  x rsync -av --progress \"${args_includes[@]}\" \"$target_dir/\" \"$backup_dir/\"\n}\n\nfunction install_cmds(){\n  case $OS_GROUP_ID in\n    \"arch\")\n      local pkgs=()\n      for cmd in \"$@\";do\n        # For package name which is not cmd name, use \"case\" syntax to replace\n        case $cmd in\n          ip) pkgs+=(iproute2);;\n          *) pkgs+=($cmd) ;;\n        esac\n      done\n      v sudo pacman -Syu\n      v sudo pacman -S --noconfirm --needed \"${pkgs[@]}\"\n      ;;\n    \"debian\")\n      local pkgs=()\n      for cmd in \"$@\";do\n        # For package name which is not cmd name, use \"case\" syntax to replace\n        case $cmd in\n          ip) pkgs+=(iproute2);;\n          *) pkgs+=($cmd) ;;\n        esac\n      done\n      v sudo apt update -y\n      v sudo apt install -y \"${pkgs[@]}\"\n      ;;\n    \"fedora\")\n      local pkgs=()\n      for cmd in \"$@\";do\n        # For package name which is not cmd name, use \"case\" syntax to replace\n        case $cmd in\n          ip) pkgs+=(iproute);;\n          *) pkgs+=($cmd) ;;\n        esac\n      done\n      v sudo dnf install -y \"${pkgs[@]}\"\n      ;;\n    \"suse\")\n      local pkgs=()\n      for cmd in \"$@\";do\n        # For package name which is not cmd name, use \"case\" syntax to replace\n        case $cmd in\n          ip) pkgs+=(iproute2);;\n          *) pkgs+=($cmd) ;;\n        esac\n      done\n      v sudo zypper refresh\n      v sudo zypper -n install \"${pkgs[@]}\"\n      ;;\n    *)\n      printf \"WARNING\\n\"\n      printf \"No method found to install package providing the commands:\\n\"\n      printf \"  $@\\n\"\n      printf \"Please install by yourself.\\n\"\n      ;;\n  esac\n}\n\nfunction ensure_cmds(){\n  local not_found_cmds=()\n  for cmd in \"$@\"; do\n    if ! command -v $cmd >/dev/null 2>&1;then\n      not_found_cmds+=($cmd)\n    fi\n  done\n  if [[ ${#not_found_cmds[@]} -gt 0 ]]; then\n    echo -e \"${STY_YELLOW}[$0]: Not found: ${not_found_cmds[*]}.${STY_RST}\"\n    install_cmds \"${not_found_cmds[@]}\"\n  fi\n}\n\nfunction dedup_and_sort_listfile(){\n  if ! test -f \"$1\"; then\n    echo \"File not found: $1\" >&2; return 2\n  else\n    temp=\"$(mktemp)\"\n    sort -u -- \"$1\" > \"$temp\"\n    mv -f -- \"$temp\" \"$2\"\n  fi\n}\n"
  },
  {
    "path": "sdata/lib/package-installers.sh",
    "content": "# This script depends on `functions.sh' .\n# This script is not for direct execution, instead it should be sourced by other script. It does not need execution permission or shebang.\n\n# shellcheck shell=bash\n\n# This file is provided for any distros, mainly non-Arch(based) distros.\n\ninstall-Rubik(){\n  x mkdir -p $REPO_ROOT/cache/Rubik\n  x cd $REPO_ROOT/cache/Rubik\n  try git init -b main\n  try git remote add origin https://github.com/googlefonts/rubik.git\n  x git pull origin main && git submodule update --init --recursive\n\tx sudo mkdir -p /usr/local/share/fonts/TTF/\n\tx sudo cp fonts/variable/Rubik*.ttf /usr/local/share/fonts/TTF/\n\tx sudo mkdir -p /usr/local/share/licenses/ttf-rubik/\n\tx sudo cp OFL.txt /usr/local/share/licenses/ttf-rubik/LICENSE\n  x fc-cache -fv\n  x cd $REPO_ROOT\n}\n\ninstall-Gabarito(){\n  x mkdir -p $REPO_ROOT/cache/Gabarito\n  x cd $REPO_ROOT/cache/Gabarito\n  try git init -b main\n  try git remote add origin https://github.com/naipefoundry/gabarito.git\n  x git pull origin main && git submodule update --init --recursive\n\tx sudo mkdir -p /usr/local/share/fonts/TTF/\n\tx sudo cp fonts/ttf/Gabarito*.ttf /usr/local/share/fonts/TTF/\n\tx sudo mkdir -p /usr/local/share/licenses/ttf-gabarito/\n\tx sudo cp OFL.txt /usr/local/share/licenses/ttf-gabarito/LICENSE\n  x fc-cache -fv\n  x cd $REPO_ROOT\n}\n\ninstall-bibata(){\n  x mkdir -p $REPO_ROOT/cache/bibata-cursor\n  x cd $REPO_ROOT/cache/bibata-cursor\n  name=\"Bibata-Modern-Classic\"\n  file=\"$name.tar.xz\"\n  try rm $file\n  x curl -JLO https://github.com/ful1e5/Bibata_Cursor/releases/latest/download/$file\n  tar -xf $file\n  x sudo mkdir -p /usr/local/share/icons\n  x sudo cp -r $name /usr/local/share/icons\n  x cd $REPO_ROOT\n}\n\ninstall-MicroTeX(){\n  x mkdir -p $REPO_ROOT/cache/MicroTeX\n  x cd $REPO_ROOT/cache/MicroTeX\n  try git init -b master\n  try git remote add origin https://github.com/NanoMichael/MicroTeX.git\n  x git pull origin master && git submodule update --init --recursive\n  x mkdir -p build\n  x cd build\n  x cmake ..\n  x make -j32\n\tx sudo mkdir -p /opt/MicroTeX\n  x sudo cp ./LaTeX /opt/MicroTeX/\n  x sudo cp -r ./res /opt/MicroTeX/\n  x cd $REPO_ROOT\n}\n\ninstall-uv(){\n  x bash <(curl -LJs \"https://astral.sh/uv/install.sh\")\n}\n\ninstall-python-packages(){\n  UV_NO_MODIFY_PATH=1\n  ILLOGICAL_IMPULSE_VIRTUAL_ENV=$XDG_STATE_HOME/quickshell/.venv\n  x mkdir -p $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)\n  # we need python 3.12 https://github.com/python-pillow/Pillow/issues/8089\n  try uv venv --prompt .venv $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV) -p 3.12\n  x source $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n  if [[ \"$INSTALL_VIA_NIX\" = true ]]; then\n    x nix-shell ${REPO_ROOT}/sdata/uv/shell.nix --run \"uv pip install -r ${REPO_ROOT}/sdata/uv/requirements.txt\"\n  else\n    x uv pip install -r ${REPO_ROOT}/sdata/uv/requirements.txt\n  fi\n  x deactivate\n}\n"
  },
  {
    "path": "sdata/subcmd-checkdeps/0.run.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\n# Check whether pkgs exist in AUR or repos of Arch.\n# Do NOT abuse this since it consumes extra bandwidth from AUR server.\n\npkglistfile=$(mktemp)\npkglistfile_orig=${LIST_FILE_PATH}\npkglistfile_orig_s=${REPO_ROOT}/cache/dependencies_stripped.conf\nfor cmd in curl gzip pacman;do\n  if ! command -v $cmd;then\n    echo \"Please install $cmd first.\";exit 1\n  fi\ndone\nremove_bashcomments_emptylines $pkglistfile_orig $pkglistfile_orig_s\n\ncat $pkglistfile_orig_s | sed \"s_\\ _\\n_g\" > $pkglistfile\n\necho \"The non-existent pkgs in $pkglistfile_orig are listed as follows.\"\n# Borrowed from https://bbs.archlinux.org/viewtopic.php?pid=1490795#p1490795\ncomm -23 <(sort -u $pkglistfile) <(sort -u <(curl https://aur.archlinux.org/packages.gz | gzip -cd | sort) <(pacman -Ssq))\necho \"End of list. If nothing appears, then all pkgs exist.\"\nrm $pkglistfile\n"
  },
  {
    "path": "sdata/subcmd-checkdeps/options.sh",
    "content": "# Handle args for subcmd: checkdeps\n# shellcheck shell=bash\n\nshowhelp(){\necho -e \"Syntax: $0 checkdeps [OPTIONS] <LIST_FILE_PATH>...\n\nCheck whether pkgs listed in <LIST_FILE_PATH> exist in AUR or repos of Arch.\n\nOptions:\n  -h, --help       Show this help message\n\"\n}\n# `man getopt` to see more\npara=$(getopt \\\n  -o h \\\n  -l help \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\necho $para\n#####################################################################################\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    --) shift;break ;;\n    *) echo -e \"$0: Wrong parameters.\";exit 1;;\n  esac\ndone\n\nif [[ -f \"$1\" ]]; then\n  echo \"Using list file \\\"$1\\\".\";LIST_FILE_PATH=\"$1\";shift 1\nelse\n  echo \"Wrong path \\\"$1\\\" of list file.\";exit 1\nfi\n"
  },
  {
    "path": "sdata/subcmd-exp-merge/0.run.sh",
    "content": "# shellcheck shell=bash\n\nset -euo pipefail\n\nMERGE_BRANCH=\"exp-merge-branch\"\nBACKUP_DIR=\"${REPO_ROOT}/.exp-merge-backups\"\n\ncleanup_on_exit() {\n  local exit_code=$?\n  if [[ $exit_code -ne 0 ]]; then\n    echo\n    log_warning \"Script interrupted or failed\"\n    if git status 2>/dev/null | grep -q \"rebase in progress\"; then\n      echo\n      echo -e \"${STY_YELLOW}Rebase is still in progress${STY_RST}\"\n      echo \"Continue: git rebase --continue\"\n      echo \"Abort:    git rebase --abort\"\n    fi\n  fi\n}\n\ntrap cleanup_on_exit EXIT INT TERM\n\ncheck_preconditions() {\n  log_header \"Checking Preconditions\"\n\n  cd \"$REPO_ROOT\" || log_die \"Failed to change to repository directory\"\n\n  if ! git rev-parse --is-inside-work-tree &>/dev/null; then\n    log_die \"Not in a git repository\"\n  fi\n\n  if ! git diff --quiet || ! git diff --cached --quiet; then\n    log_error \"You have uncommitted changes in the repository:\"\n    git status --short\n    log_die \"Please commit or stash your changes before running exp-merge\"\n  fi\n\n  if ! git remote get-url upstream &>/dev/null; then\n    log_die \"No remote 'upstream' configured\"\n  fi\n\n  log_success \"Precondition checks passed\"\n}\n\nfetch_upstream() {\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would fetch from upstream\"\n    return\n  fi\n\n  if [[ \"$SKIP_FETCH\" == false ]]; then\n    log_info \"Fetching from upstream...\"\n    git fetch upstream || log_die \"Failed to fetch from upstream\"\n    log_success \"Fetched from upstream\"\n  else\n    log_info \"Skipping fetch (--skip-fetch flag set)\"\n  fi\n}\n\nupdate_main_with_upstream() {\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would update main from upstream\"\n    return\n  fi\n\n  log_info \"Updating main with upstream...\"\n  git checkout main\n  git merge --ff-only upstream/main || log_die \"Main has diverged from upstream, cannot fast-forward\"\n  log_success \"Main updated\"\n}\n\nswitch_to_merge_branch() {\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would switch to merge branch\"\n    return\n  fi\n\n  # check if branch exists\n  if git show-ref --verify --quiet \"refs/heads/${MERGE_BRANCH}\"; then\n    log_info \"Switching to existing merge branch...\"\n    git checkout \"${MERGE_BRANCH}\"\n  else\n    log_info \"Creating new merge branch from main...\"\n    git checkout -b \"${MERGE_BRANCH}\"\n  fi\n  log_success \"On branch ${MERGE_BRANCH}\"\n}\n\ncopy_and_commit_user_config() {\n  local user_quickshell=\"${HOME}/.config/quickshell\"\n  local repo_quickshell=\"${REPO_ROOT}/dots/.config/quickshell\"\n\n  if [[ ! -d \"${user_quickshell}\" ]]; then\n    log_warning \"Quickshell config not found at: ${user_quickshell}\"\n    log_info \"Skipping\"\n    return 1\n  fi\n\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would copy and commit user config\"\n    return\n  fi\n\n  # chekc for rebase in progress\n  if git status | grep -q \"rebase in progress\"; then\n    log_error \"Rebase already in progress, resolve it first\"\n    return 1\n  fi\n\n  log_info \"Copying user config...\"\n  rm -rf \"${repo_quickshell}\"\n  cp -r \"${user_quickshell}\" \"${repo_quickshell}\"\n  find \"${repo_quickshell}\" \\( -name '.git' -o -name '.gitmodules' \\) -exec rm -rf {} + 2>/dev/null || true\n\n  git add .\n  if git diff --cached --quiet; then\n    log_info \"No changes to commit\"\n  else\n    git commit -m \"user changes\"\n    log_success \"Committed user changes\"\n  fi\n}\n\nrebase_onto_main() {\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would rebase onto main\"\n    return\n  fi\n\n  log_info \"Rebasing onto main...\"\n  if git rebase main; then\n    log_success \"Rebase completed\"\n  else\n    log_error \"Rebase encountered conflicts\"\n    echo\n    echo -e \"${STY_YELLOW}Conflicted files:${STY_RST}\"\n    git diff --name-only --diff-filter=U\n    echo\n    echo -e \"${STY_CYAN}To resolve:${STY_RST}\"\n    echo \"  1. Edit conflicted files\"\n    echo \"  2. git add <files>\"\n    echo \"  3. git rebase --continue\"\n    echo \"  4. Run this script again\"\n    echo\n    echo -e \"${STY_CYAN}To abort:${STY_RST}\"\n    echo \"  git rebase --abort\"\n    echo\n    return 1\n  fi\n}\n\napply_quickshell_config() {\n  log_header \"Apply Quickshell Config\"\n\n  local user_quickshell=\"${HOME}/.config/quickshell\"\n  local repo_quickshell=\"${REPO_ROOT}/dots/.config/quickshell\"\n  local timestamp\n  timestamp=$(date +%Y%m%d-%H%M%S)\n\n  echo\n  echo -e \"${STY_CYAN}Your quickshell config has been merged with upstream.${STY_RST}\"\n  echo \"What to do with merged config:\"\n  echo\n  echo \"1) Replace current with merged version\"\n  echo \"2) Backup current, then replace\"\n  echo \"3) Save merged as quickshell.new\"\n  echo \"4) Skip\"\n  echo\n\n  local choice\n  read -p \"Choice (1-4): \" choice\n\n  case $choice in\n  1)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would replace config\"\n    else\n      rm -rf \"${user_quickshell}\"\n      cp -r \"${repo_quickshell}\" \"${user_quickshell}\"\n      log_success \"Config replaced\"\n    fi\n    ;;\n  2)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would backup and replace\"\n    else\n      mkdir -p \"${BACKUP_DIR}\"\n      local backup_name=\"quickshell.${timestamp}.bak\"\n      cp -r \"${user_quickshell}\" \"${BACKUP_DIR}/${backup_name}\"\n      log_success \"Backup: ${backup_name}\"\n      rm -rf \"${user_quickshell}\"\n      cp -r \"${repo_quickshell}\" \"${user_quickshell}\"\n      log_success \"Config replaced\"\n    fi\n    ;;\n  3)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would save as quickshell.new\"\n    else\n      local new_config=\"${HOME}/.config/quickshell.new\"\n      rm -rf \"${new_config}\"\n      cp -r \"${repo_quickshell}\" \"${new_config}\"\n      log_success \"Saved as quickshell.new\"\n      log_info \"Current config unchanged\"\n    fi\n    ;;\n  4)\n    log_info \"Skipped\"\n    ;;\n  *)\n    log_warning \"Invalid choice\"\n    ;;\n  esac\n}\n\nupdate_hypr_config() {\n  log_header \"Update Hyprland Config\"\n\n  local user_hypr=\"${HOME}/.config/hypr\"\n  local repo_hypr=\"${REPO_ROOT}/dots/.config/hypr\"\n  local timestamp\n  timestamp=$(date +%Y%m%d-%H%M%S)\n\n  if [[ ! -d \"${user_hypr}\" ]] || [[ ! -d \"${repo_hypr}\" ]]; then\n    log_info \"Hypr config not found, skipping\"\n    return\n  fi\n\n  echo\n  echo -e \"${STY_CYAN}Update hyprland config?${STY_RST}\"\n  echo -e \"${STY_YELLOW}Note: /custom/ directory will be preserved${STY_RST}\"\n  echo\n  echo \"1) Update now\"\n  echo \"2) Backup, then update\"\n  echo \"3) Skip\"\n  echo\n\n  local choice\n  read -p \"Choice (1-3): \" choice\n\n  case $choice in\n  1)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would update hypr\"\n    else\n      local temp_custom=\"/tmp/hypr-custom-${timestamp}\"\n      [[ -d \"${user_hypr}/custom\" ]] && cp -r \"${user_hypr}/custom\" \"${temp_custom}\"\n      rm -rf \"${user_hypr}\"\n      cp -r \"${repo_hypr}\" \"${user_hypr}\"\n      if [[ -d \"${temp_custom}\" ]]; then\n        rm -rf \"${user_hypr}/custom\"\n        cp -r \"${temp_custom}\" \"${user_hypr}/custom\"\n        rm -rf \"${temp_custom}\"\n      fi\n      log_success \"Hypr updated\"\n    fi\n    ;;\n  2)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would backup and update\"\n    else\n      mkdir -p \"${BACKUP_DIR}\"\n      local backup_name=\"hypr.${timestamp}.bak\"\n      cp -r \"${user_hypr}\" \"${BACKUP_DIR}/${backup_name}\"\n      log_success \"Backup: ${backup_name}\"\n\n      local temp_custom=\"/tmp/hypr-custom-${timestamp}\"\n      [[ -d \"${user_hypr}/custom\" ]] && cp -r \"${user_hypr}/custom\" \"${temp_custom}\"\n      rm -rf \"${user_hypr}\"\n      cp -r \"${repo_hypr}\" \"${user_hypr}\"\n      if [[ -d \"${temp_custom}\" ]]; then\n        rm -rf \"${user_hypr}/custom\"\n        cp -r \"${temp_custom}\" \"${user_hypr}/custom\"\n        rm -rf \"${temp_custom}\"\n      fi\n      log_success \"Hypr updated\"\n    fi\n    ;;\n  3)\n    log_info \"Skipped\"\n    ;;\n  *)\n    log_warning \"Invalid choice\"\n    ;;\n  esac\n}\n\nlog_header \"Experimental Config Merge\"\n\nif [[ \"$SKIP_NOTICE\" == false ]]; then\n  log_warning \"THIS SCRIPT IS EXPERIMENTAL, ONLY CONTINUE AT YOUR OWN RISK!\"\n  log_warning \"It might be safer if you want to preserve your modifications and not delete added files,\"\n  log_warning \"  but this can cause partial updates and therefore unexpected behavior.\"\n  log_warning \"In general, prefer \\\"./setup install\\\" for updates if available.\"\n  read -p \"Continue? (y/N): \" response\n\n  if [[ ! \"$response\" =~ ^[Yy]$ ]]; then\n    log_error \"Merge aborted by user\"\n    exit 1\n  fi\nfi\n\ncheck_preconditions\n\nfetch_upstream\n\nupdate_main_with_upstream\n\nlog_header \"Merging Quickshell Config\"\n\nswitch_to_merge_branch\n\nif copy_and_commit_user_config; then\n  if rebase_onto_main; then\n    apply_quickshell_config\n  fi\nfi\n\nupdate_hypr_config\n\n# back to main\nif [[ \"$DRY_RUN\" != true ]]; then\n  log_info \"Switching back to main...\"\n  git checkout main\nfi\n\nlog_header \"Merge Complete\"\n\nif [[ \"$DRY_RUN\" == true ]]; then\n  log_warning \"DRY-RUN: No changes made\"\nelse\n  log_success \"Done\"\nfi\n\n[[ -d \"${BACKUP_DIR}\" ]] && log_info \"Backups in: ${BACKUP_DIR}/\"\n\necho\n"
  },
  {
    "path": "sdata/subcmd-exp-merge/options.sh",
    "content": "# Handle args for subcmd: exp-merge\n# shellcheck shell=bash\n\nshowhelp(){\necho -e \"Syntax: $0 exp-merge [OPTIONS]...\n\nExperimental config merging using git rebase.\nMerges upstream changes with your quickshell config.\n\nOptions:\n  -n, --dry-run      Show what would be done\n  -h, --help         Show this help\n  -s, --skip-notice  Skip notice about script being experimental\n  --skip-fetch       Skip fetching from remote\n\nHow it works:\n  1. Fetch from upstream\n  2. Update main branch\n  3. Switch to exp-merge-branch (persistent)\n  4. Copy your ~/.config/quickshell and commit\n  5. Rebase onto main (3-way merge with history)\n  6. Prompt to apply merged config\n  7. Optionally update hypr config (preserves cstom folder)\n  8. Switch back to main\n\"\n}\n\npara=$(getopt \\\n  -o hns \\\n  -l help,dry-run,skip-notice,skip-fetch \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\n\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    --) break ;;\n    *) shift ;;\n  esac\ndone\n\nDRY_RUN=false\nSKIP_FETCH=false\nSKIP_NOTICE=false\n\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -n|--dry-run) DRY_RUN=true;shift\n      log_info \"Dry-run mode enabled - no changes will be made\"\n      ;;\n    -s|--skip-notice) SKIP_NOTICE=true;shift\n      log_warning \"Skipping notice about script being experimental\"\n      ;;\n    --skip-fetch) SKIP_FETCH=true;shift\n      log_info \"Skipping fetch from remote\"\n      ;;\n\n    --) break ;;\n    *) echo -e \"$0: Wrong parameters.\";exit 1;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/subcmd-exp-update/0.run.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\n#####################################################################################\n# Notes by @clsty:\n#\n# I'm not the one who developed this script (see issue#2284 which discussed about the history).\n# However it contains many unnecessary logics. This is typically what AI will do.\n# I don't really care if it's AI-generated or not, it's just an extra option in addition to ./setup install, so as long as the users say it works, it should be fine.\n# However, it's not easy to maintain something like this.\n# The redundant logic should be cleaned up someday.\n#\n# This also applies for exp-update.tester.sh, TBH I don't think that file is really needed, and it also looks like AI-generated. Just guessing though.\n#####################################################################################\n#\n# exp-update.sh - Enhanced dotfiles update script\n#\n# Features:\n# - Auto-detect repository structure (dots/ prefix or direct config)\n# - Pull latest commits from remote\n# - Rebuild packages if PKGBUILD files changed (user choice)\n# - Handle config file conflicts with user choices\n# - Respect .updateignore file for exclusions with flexible pattern matching:\n#   - Exact matches (e.g., \"path/to/file\")\n#   - Directory patterns (e.g., \"path/to/dir/\")\n#   - Wildcards (e.g., \"*.log\", \"path/*/file\")\n#   - Root-relative patterns (e.g., \"/.config\")\n#   - Substring matching (prefix with \"**\", e.g., \"**temp\" matches any path containing \"temp\")\n#\nset -euo pipefail\n\n# Note: The detect_repo_structure function below auto-detects the folder layout\n# Try to find the packages directory (different names in different versions)\nif which pacman &>/dev/null; then\n  if [[ -d \"${REPO_ROOT}/dist-arch\" ]]; then\n    ARCH_PACKAGES_DIR=\"${REPO_ROOT}/dist-arch\"\n  elif [[ -d \"${REPO_ROOT}/arch-packages\" ]]; then\n    ARCH_PACKAGES_DIR=\"${REPO_ROOT}/arch-packages\"\n  elif [[ -d \"${REPO_ROOT}/sdata/dist-arch\" ]]; then\n    ARCH_PACKAGES_DIR=\"${REPO_ROOT}/sdata/dist-arch\"\n  else\n    ARCH_PACKAGES_DIR=\"${REPO_ROOT}/dist-arch\"  # Default fallback\n  fi\nfi\nUPDATE_IGNORE_FILE=\"${REPO_ROOT}/.updateignore\"\nXDG_UPDATE_IGNORE_FILE=\"${XDG_CONFIG_HOME:-$HOME/.config}/illogical-impulse/updateignore\"\n#TODO: remove in future and add script to migrate to XDG path\nHOME_UPDATE_IGNORE_FILE=\"${HOME}/.updateignore\" # Legacy support \n\n# Global arrays for cached ignore patterns (performance optimization)\ndeclare -a IGNORE_PATTERNS=()\ndeclare -a IGNORE_SUBSTRING_PATTERNS=()\n\n# Track created directories to avoid redundant mkdir calls\ndeclare -A CREATED_DIRS\n\n# Auto-detect repository structure\ndetect_repo_structure() {\n  local found_dirs=()\n  \n  # Check for dots/ prefixed structure\n  if [[ -d \"${REPO_ROOT}/dots/.config\" ]]; then\n    found_dirs+=(\"dots/.config\")\n    [[ -d \"${REPO_ROOT}/dots/.local/bin\" ]] && found_dirs+=(\"dots/.local/bin\")\n    [[ -d \"${REPO_ROOT}/dots/.local/share\" ]] && found_dirs+=(\"dots/.local/share\")\n  # Check for flat structure\n  elif [[ -d \"${REPO_ROOT}/.config\" ]]; then\n    found_dirs+=(\".config\")\n    [[ -d \"${REPO_ROOT}/.local/bin\" ]] && found_dirs+=(\".local/bin\")\n    [[ -d \"${REPO_ROOT}/.local/share\" ]] && found_dirs+=(\".local/share\")\n  else\n    # Manual detection of common directories\n    for candidate in \"dots/.config\" \".config\" \"dots/.local/bin\" \".local/bin\" \"dots/.local/share\" \".local/share\"; do\n      if [[ -d \"${REPO_ROOT}/${candidate}\" ]]; then\n        # Avoid duplicates\n        if [[ ! \" ${found_dirs[*]} \" =~ \" ${candidate} \" ]]; then\n          found_dirs+=(\"${candidate}\")\n        fi\n      fi\n    done\n  fi\n  \n  if [[ ${#found_dirs[@]} -eq 0 ]]; then\n    echo \"ERROR: Could not detect repository structure\" >&2\n    return 1\n  fi\n  \n  echo \"${found_dirs[@]}\"\n}\n\n# Directories to monitor for changes (will be auto-detected)\nMONITOR_DIRS=()\n\n# Enhanced safe_read with better terminal handling\nsafe_read() {\n  local prompt=\"$1\"\n  local varname=\"$2\"\n  local default=\"${3:-}\"\n  local input_value=\"\"\n\n  # In non-interactive mode, use default immediately\n  if [[ \"$NON_INTERACTIVE\" == true ]]; then\n    if [[ -n \"$default\" ]]; then\n      printf -v \"$varname\" '%s' \"$default\"\n      return 0\n    else\n      log_error \"Non-interactive mode requires default value for: $prompt\"\n      return 1\n    fi\n  fi\n\n  echo -n \"$prompt\"\n  \n  # First, try reading from stdin (supports piped input like \"yes 1 |\")\n  if read -r -t 0.1 input_value 2>/dev/null; then\n    # Successfully read from stdin (piped input)\n    if [[ -n \"$input_value\" ]]; then\n      printf -v \"$varname\" '%s' \"$input_value\"\n      return 0\n    fi\n  fi\n  \n  # If stdin had no data, try interactive terminal\n  if [[ -t 0 ]]; then\n    # stdin is a terminal\n    read -r input_value\n  elif [[ -r /dev/tty ]]; then\n    # Try reading from tty\n    if read -r input_value </dev/tty 2>/dev/null; then\n      : # Success\n    else\n      input_value=\"\"\n    fi\n  else\n    # No interactive terminal available\n    if [[ -n \"$default\" ]]; then\n      echo\n      log_warning \"No terminal available. Using default: $default\"\n      printf -v \"$varname\" '%s' \"$default\"\n      return 0\n    else\n      echo\n      log_error \"No terminal available and no default provided\"\n      return 1\n    fi\n  fi\n\n  if [[ -n \"$input_value\" ]]; then\n    printf -v \"$varname\" '%s' \"$input_value\"\n    return 0\n  elif [[ -n \"$default\" ]]; then\n    echo\n    log_warning \"Empty input. Using default: $default\"\n    printf -v \"$varname\" '%s' \"$default\"\n    return 0\n  else\n    echo\n    log_error \"Input required but not provided\"\n    return 1\n  fi\n}\n\n# Load and cache ignore patterns for performance\nload_ignore_patterns() {\n  IGNORE_PATTERNS=()\n  IGNORE_SUBSTRING_PATTERNS=()\n  \n  for ignore_file in \"$UPDATE_IGNORE_FILE\" \"$XDG_UPDATE_IGNORE_FILE\" \"$HOME_UPDATE_IGNORE_FILE\"; do\n    [[ ! -f \"$ignore_file\" ]] && continue\n    \n    while IFS= read -r pattern || [[ -n \"$pattern\" ]]; do\n      # Skip empty lines and comments\n      [[ -z \"$pattern\" || \"$pattern\" =~ ^[[:space:]]*# ]] && continue\n      # Remove whitespace\n      pattern=$(echo \"$pattern\" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')\n      [[ -z \"$pattern\" ]] && continue\n      \n      # Separate substring patterns from regular patterns\n      if [[ \"${pattern:0:2}\" == \"**\" ]]; then\n        local cleaned_pattern=\"${pattern#\\*\\*}\"\n        # Strip trailing asterisks\n        while [[ \"$cleaned_pattern\" == *\"*\" ]] && [[ \"${cleaned_pattern: -1}\" == \"*\" ]]; do\n          cleaned_pattern=\"${cleaned_pattern%\\*}\"\n        done\n        # Ensure we have a non-empty pattern\n        if [[ -n \"$cleaned_pattern\" ]]; then\n          IGNORE_SUBSTRING_PATTERNS+=(\"$cleaned_pattern\")\n        fi\n      else\n        IGNORE_PATTERNS+=(\"$pattern\")\n      fi\n    done < \"$ignore_file\"\n  done\n  \n  if [[ \"$VERBOSE\" == true ]]; then\n    log_info \"Loaded ${#IGNORE_PATTERNS[@]} ignore patterns and ${#IGNORE_SUBSTRING_PATTERNS[@]} substring patterns\"\n  fi\n}\n\n# Optimized should_ignore using cached patterns\nshould_ignore() {\n  local file_path=\"$1\"\n  local relative_path=\"${file_path#$HOME/}\"\n  local repo_relative=\"\"\n  \n  if [[ \"$file_path\" == \"$REPO_ROOT\"* ]]; then\n    repo_relative=\"${file_path#$REPO_ROOT/}\"\n  fi\n\n  # Check regular patterns\n  for pattern in \"${IGNORE_PATTERNS[@]}\"; do\n    # Exact match\n    if [[ \"$relative_path\" == \"$pattern\" ]] || [[ \"$repo_relative\" == \"$pattern\" ]]; then\n      return 0\n    fi\n\n    # Wildcard patterns (basic glob matching)\n    if [[ \"$relative_path\" == $pattern ]] || [[ \"$repo_relative\" == $pattern ]]; then\n      return 0\n    fi\n\n    # Directory patterns (ending with /)\n    if [[ \"$pattern\" == */ ]]; then\n      local dir_pattern=\"${pattern%/}\"\n      if [[ \"$relative_path\" == \"$dir_pattern\"/* ]] || [[ \"$repo_relative\" == \"$dir_pattern\"/* ]]; then\n        return 0\n      fi\n    fi\n\n    # Root-relative patterns (starting with /)\n    if [[ \"$pattern\" == /* ]]; then\n      local root_pattern=\"${pattern#/}\"\n      if [[ \"$relative_path\" == \"$root_pattern\" ]] || [[ \"$relative_path\" == \"$root_pattern\"/* ]] ||\n         [[ \"$repo_relative\" == \"$root_pattern\" ]] || [[ \"$repo_relative\" == \"$root_pattern\"/* ]]; then\n        return 0\n      fi\n    fi\n\n    # Patterns with wildcards - check parent directories\n    if [[ \"$pattern\" == *\"*\"* ]]; then\n      local temp_path=\"$relative_path\"\n      while [[ \"$temp_path\" == */* ]]; do\n        temp_path=\"${temp_path%/*}\"\n        if [[ \"$temp_path\" == $pattern ]]; then\n          return 0\n        fi\n      done\n    fi\n  done\n\n  # Check substring patterns\n  for substring in \"${IGNORE_SUBSTRING_PATTERNS[@]}\"; do\n    if [[ -n \"$substring\" && (\"$file_path\" == *\"$substring\"* || \"$relative_path\" == *\"$substring\"*) ]]; then\n      return 0\n    fi\n  done\n\n  return 1\n}\n\n# Efficient directory creation with caching\nensure_directory() {\n  local dir=\"$1\"\n  \n  # Check if already created in this run\n  if [[ -n \"${CREATED_DIRS[$dir]:-}\" ]]; then\n    return 0\n  fi\n  \n  if [[ \"$DRY_RUN\" != true ]]; then\n    if [[ ! -d \"$dir\" ]]; then\n      if mkdir -p \"$dir\" 2>/dev/null; then\n        CREATED_DIRS[$dir]=1\n        if [[ \"$VERBOSE\" == true ]]; then\n          log_info \"Created directory: $dir\"\n        fi\n      else\n        log_error \"Failed to create directory: $dir\"\n        return 1\n      fi\n    else\n      CREATED_DIRS[$dir]=1\n    fi\n  else\n    if [[ \"$VERBOSE\" == true ]] || [[ -z \"${CREATED_DIRS[$dir]:-}\" ]]; then\n      log_info \"[DRY-RUN] Would create directory: $dir\"\n    fi\n    CREATED_DIRS[$dir]=1\n  fi\n  return 0\n}\n\n# Function to show file diff\nshow_diff() {\n  local file1=\"$1\"\n  local file2=\"$2\"\n\n  echo -e \"\\n${STY_CYAN}Showing differences:${STY_RST}\"\n  echo -e \"${STY_CYAN}Old file: $file1${STY_RST}\"\n  echo -e \"${STY_CYAN}New file: $file2${STY_RST}\"\n  echo \"----------------------------------------\"\n\n  if command -v diff &>/dev/null; then\n    diff -u \"$file1\" \"$file2\" || true\n  else\n    echo \"diff command not available\"\n  fi\n  echo \"----------------------------------------\"\n}\n\n# Backup file before replacing\nbackup_file() {\n  local file=\"$1\"\n  local backup_dir=\"${REPO_ROOT}/.update-backups\"\n  local timestamp\n  timestamp=$(date +%Y%m%d-%H%M%S)\n  \n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would backup: $file\"\n    return 0\n  fi\n  \n  if [[ ! -f \"$file\" ]]; then\n    log_warning \"File does not exist, cannot backup: $file\"\n    return 1\n  fi\n  \n  ensure_directory \"$backup_dir\" || return 1\n  \n  local backup_name\n  local relative_name=\"${file#$HOME/}\"\n  backup_name=\"${relative_name//\\//_}.${timestamp}.bak\"\n  \n  if cp -p \"$file\" \"${backup_dir}/${backup_name}\" 2>/dev/null; then\n    log_info \"Backed up to: .update-backups/${backup_name}\"\n    return 0\n  else\n    log_error \"Failed to create backup\"\n    return 1\n  fi\n}\n\n# Function to handle file conflicts\nhandle_file_conflict() {\n  local repo_file=\"$1\"\n  local home_file=\"$2\"\n  local filename=$(basename \"$home_file\")\n  local dirname=$(dirname \"$home_file\")\n  local choice=\"\"\n  local default_val=\"${DEFAULT_CHOICE:-6}\"  # Use DEFAULT_CHOICE or 6 (skip) as fallback\n\n  # In non-interactive mode, use default directly (acts like pressing Enter)\n  if [[ \"$NON_INTERACTIVE\" == true ]]; then\n    choice=\"$default_val\"\n    log_info \"Using choice $choice for: $home_file\"\n  else\n    echo -e \"\\n${STY_YELLOW}Conflict detected:${STY_RST} $home_file\"\n    echo \"Repository version differs from your local version.\"\n    echo\n    echo \"Choose an action:\"\n    echo \"1) Replace local file with repository version\"\n    echo \"2) Keep local file unchanged\"\n    echo \"3) Backup local file as ${filename}.old, use repository version\"\n    echo \"4) Save repository version as ${filename}.new, keep local file\"\n    echo \"5) Show diff and decide\"\n    echo \"6) Skip this file\"\n    echo \"7) Add to ignore and skip\"\n    echo \"8) Backup to .update-backups/ and replace with repository version\"\n    echo\n\n    while true; do\n      if ! safe_read \"Enter your choice (1-8 or name) [${default_val}]: \" choice \"$default_val\"; then\n        echo\n        log_warning \"Failed to read input. Skipping file.\"\n        return\n      fi\n\n      # Validate choice\n      if [[ \"$choice\" =~ ^[1-8]$ ]] || [[ \"$choice\" =~ ^(replace|keep|old|new|diff|skip|ignore|backup)$ ]]; then\n        break\n      else\n        echo \"Invalid choice. Please enter 1-8 or a valid name (replace, keep, old ...).\"\n      fi\n    done\n  fi\n\n  case $choice in\n  1|replace)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would replace $home_file with repository version\"\n    else\n      cp -p \"$repo_file\" \"$home_file\"\n      log_success \"Replaced $home_file with repository version\"\n    fi\n    ;;\n  2|keep)\n    log_info \"Keeping local version of $home_file\"\n    ;;\n  3|old)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would backup local file to ${filename}.old and update with repository version\"\n    else\n      mv \"$home_file\" \"${dirname}/${filename}.old\"\n      cp -p \"$repo_file\" \"$home_file\"\n      log_success \"Backed up local file to ${filename}.old and updated with repository version\"\n    fi\n    ;;\n  4|new)\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would save repository version as ${filename}.new, keep local file\"\n    else\n      cp -p \"$repo_file\" \"${dirname}/${filename}.new\"\n      log_success \"Saved repository version as ${filename}.new, kept local file\"\n    fi\n    ;;\n  5|diff)\n    show_diff \"$home_file\" \"$repo_file\"\n    echo\n    echo \"After reviewing the diff, choose:\"\n    echo \"r) Replace with repository version\"\n    echo \"k) Keep local version\"\n    echo \"b) Backup local and use repository version\"\n    echo \"n) Save repository version as .new\"\n    echo \"s) Skip this file\"\n    echo \"i) Add to ignore and skip\"\n    echo \"B) Backup to .update-backups/ and replace\"\n\n    if ! safe_read \"Enter your choice (r/k/b/n/s/i/B): \" subchoice \"s\"; then\n      echo\n      log_warning \"Failed to read input. Skipping file.\"\n      return\n    fi\n\n    case $subchoice in\n    r)\n      if [[ \"$DRY_RUN\" == true ]]; then\n        log_info \"[DRY-RUN] Would replace $home_file with repository version\"\n      else\n        cp -p \"$repo_file\" \"$home_file\"\n        log_success \"Replaced $home_file with repository version\"\n      fi\n      ;;\n    k)\n      log_info \"Keeping local version of $home_file\"\n      ;;\n    b)\n      if [[ \"$DRY_RUN\" == true ]]; then\n        log_info \"[DRY-RUN] Would backup local file to ${filename}.old and update\"\n      else\n        mv \"$home_file\" \"${dirname}/${filename}.old\"\n        cp -p \"$repo_file\" \"$home_file\"\n        log_success \"Backed up local file to ${filename}.old and updated\"\n      fi\n      ;;\n    n)\n      if [[ \"$DRY_RUN\" == true ]]; then\n        log_info \"[DRY-RUN] Would save repository version as ${filename}.new\"\n      else\n        cp -p \"$repo_file\" \"${dirname}/${filename}.new\"\n        log_success \"Saved repository version as ${filename}.new\"\n      fi\n      ;;\n    s)\n      log_info \"Skipping $home_file\"\n      ;;\n    i)\n      local relative_path_to_home=\"${home_file#$HOME/}\"\n      if [[ \"$DRY_RUN\" == true ]]; then\n        log_info \"[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE\"\n      else\n        echo \"$relative_path_to_home\" >>\"$XDG_UPDATE_IGNORE_FILE\"\n        log_success \"Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped.\"\n      fi\n      ;;\n    B)\n      if backup_file \"$home_file\"; then\n        if [[ \"$DRY_RUN\" != true ]]; then\n          cp -p \"$repo_file\" \"$home_file\"\n          log_success \"Replaced $home_file with repository version\"\n        fi\n      fi\n      ;;\n    *)\n      log_info \"Skipping $home_file\"\n      ;;\n    esac\n    ;;\n  6|skip)\n    log_info \"Skipping $home_file\"\n    ;;\n  7|ignore)\n    local relative_path_to_home=\"${home_file#$HOME/}\"\n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would add '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE\"\n    else\n      echo \"$relative_path_to_home\" >>\"$XDG_UPDATE_IGNORE_FILE\"\n      log_success \"Added '$relative_path_to_home' to $XDG_UPDATE_IGNORE_FILE and skipped.\"\n    fi\n    ;;\n  8|backup)\n    if backup_file \"$home_file\"; then\n      if [[ \"$DRY_RUN\" != true ]]; then\n        cp -p \"$repo_file\" \"$home_file\"\n        log_success \"Replaced $home_file with repository version\"\n      fi\n    fi\n    ;;\n  esac\n}\n\n# Function to check if PKGBUILD has changed\ncheck_pkgbuild_changed() {\n  local pkg_dir=\"$1\"\n  local pkgbuild_path=\"${pkg_dir}/PKGBUILD\"\n\n  [[ ! -f \"$pkgbuild_path\" ]] && return 1\n\n  local relative_path=\"${pkgbuild_path#$REPO_ROOT/}\"\n\n  if [[ \"$FORCE_CHECK\" == true ]]; then\n    return 0\n  fi\n\n  # Check if HEAD@{1} exists before trying to use it\n  if ! git rev-parse --verify HEAD@{1} &>/dev/null; then\n    # Fresh clone, assume all PKGBUILDs need checking\n    return 0\n  fi\n\n  if git diff --name-only HEAD@{1} HEAD 2>/dev/null | grep -q \"^${relative_path}$\"; then\n    return 0\n  fi\n\n  return 1\n}\n\n# Function to list available packages\nlist_packages() {\n  local available_packages=()\n  local changed_packages=()\n\n  if [[ ! -d \"$ARCH_PACKAGES_DIR\" ]]; then\n    log_warning \"No package directory found\"\n    return 1\n  fi\n\n  for pkg_dir in \"$ARCH_PACKAGES_DIR\"/*/; do\n    if [[ -f \"${pkg_dir}/PKGBUILD\" ]]; then\n      local pkg_name=$(basename \"$pkg_dir\")\n      available_packages+=(\"$pkg_name\")\n\n      if check_pkgbuild_changed \"$pkg_dir\"; then\n        changed_packages+=(\"$pkg_name\")\n      fi\n    fi\n  done\n\n  if [[ ${#available_packages[@]} -eq 0 ]]; then\n    log_info \"No packages found in package directory\"\n    return 1\n  fi\n\n  echo -e \"\\n${STY_CYAN}Available packages:${STY_RST}\"\n  for pkg in \"${available_packages[@]}\"; do\n    if [[ \" ${changed_packages[*]} \" =~ \" ${pkg} \" ]]; then\n      echo -e \"  ${STY_GREEN}● ${pkg}${STY_RST} (PKGBUILD changed)\"\n    else\n      echo -e \"  ○ ${pkg}\"\n    fi\n  done\n\n  if [[ ${#changed_packages[@]} -gt 0 ]]; then\n    echo -e \"\\n${STY_YELLOW}Packages with changed PKGBUILDs: ${changed_packages[*]}${STY_RST}\"\n  fi\n\n  return 0\n}\n\n# Function to build selected packages\nbuild_packages() {\n  local build_mode=\"$1\"\n  local packages_to_build=()\n\n  case \"$build_mode\" in\n  \"changed\")\n    for pkg_dir in \"$ARCH_PACKAGES_DIR\"/*/; do\n      if [[ -f \"${pkg_dir}/PKGBUILD\" ]]; then\n        local pkg_name=$(basename \"$pkg_dir\")\n        if check_pkgbuild_changed \"$pkg_dir\"; then\n          packages_to_build+=(\"$pkg_name\")\n        fi\n      fi\n    done\n    ;;\n  \"all\")\n    for pkg_dir in \"$ARCH_PACKAGES_DIR\"/*/; do\n      if [[ -f \"${pkg_dir}/PKGBUILD\" ]]; then\n        local pkg_name=$(basename \"$pkg_dir\")\n        packages_to_build+=(\"$pkg_name\")\n      fi\n    done\n    ;;\n  \"select\")\n    echo -e \"\\nEnter package names separated by spaces (or 'all' for all packages):\"\n    if ! safe_read \"Packages to build: \" user_selection \"\"; then\n      log_warning \"Failed to read input. Skipping package builds.\"\n      return\n    fi\n\n    if [[ \"$user_selection\" == \"all\" ]]; then\n      for pkg_dir in \"$ARCH_PACKAGES_DIR\"/*/; do\n        if [[ -f \"${pkg_dir}/PKGBUILD\" ]]; then\n          local pkg_name=$(basename \"$pkg_dir\")\n          packages_to_build+=(\"$pkg_name\")\n        fi\n      done\n    else\n      read -ra packages_to_build <<<\"$user_selection\"\n    fi\n    ;;\n  esac\n\n  if [[ ${#packages_to_build[@]} -eq 0 ]]; then\n    log_info \"No packages selected for building\"\n    return\n  fi\n\n  echo -e \"\\n${STY_CYAN}Packages to build: ${packages_to_build[*]}${STY_RST}\"\n\n  if ! safe_read \"Proceed with building these packages? (Y/n): \" confirm \"Y\"; then\n    log_warning \"Failed to read input. Skipping package builds.\"\n    return\n  fi\n\n  if [[ \"$confirm\" =~ ^[Nn]$ ]]; then\n    log_info \"Package building cancelled by user\"\n    return\n  fi\n  \n  for pkg_name in \"${packages_to_build[@]}\"; do\n    pkg_dir=\"${ARCH_PACKAGES_DIR}/${pkg_name}\"\n\n    if [[ ! -d \"$pkg_dir\" || ! -f \"${pkg_dir}/PKGBUILD\" ]]; then\n      log_error \"Package not found or missing PKGBUILD: $pkg_name\"\n      continue\n    fi\n\n    log_info \"Building package: $pkg_name\"\n    \n    if [[ \"$DRY_RUN\" == true ]]; then\n      log_info \"[DRY-RUN] Would build package in temp directory and clean up after\"\n      continue\n    fi\n\n    # Create temp build directory to avoid polluting the repo\n    local build_tmp_dir\n    build_tmp_dir=$(mktemp -d \"/tmp/pkgbuild-${pkg_name}-XXXXXX\")\n    \n    # Copy package files to temp directory (using /. to include hidden files)\n    cp -r \"$pkg_dir\"/. \"$build_tmp_dir/\" || {\n      log_error \"Failed to copy package files to temp directory\"\n      rm -rf \"$build_tmp_dir\"\n      continue\n    }\n\n    cd \"$build_tmp_dir\" || {\n      log_error \"Failed to change to temp build directory: $build_tmp_dir\"\n      rm -rf \"$build_tmp_dir\"\n      continue\n    }\n\n    if makepkg -sCi --noconfirm; then\n      log_success \"Successfully built and installed $pkg_name\"\n      ((rebuilt_packages++)) || true\n    else\n      log_error \"Failed to build package $pkg_name\"\n    fi\n\n    # Clean up temp build directory\n    cd \"$REPO_ROOT\" || log_die \"Failed to return to repository directory\"\n    rm -rf \"$build_tmp_dir\"\n    log_info \"Cleaned up temp build directory\"\n    \n    # Also clean any old build artifacts in the original package directory\n    rm -rf \"${pkg_dir}/src\" \"${pkg_dir}/pkg\" \"${pkg_dir}\"/*.pkg.tar.* 2>/dev/null || true\n  done\n\n  if [[ $rebuilt_packages -eq 0 ]]; then\n    log_warning \"No packages were successfully built\"\n  else\n    log_success \"Successfully rebuilt $rebuilt_packages package(s)\"\n  fi\n}\n\n# Optimized function to get list of changed files\nget_changed_files() {\n  local dir_path=\"$1\"\n\n  if [[ \"$FORCE_CHECK\" == true ]]; then\n    find \"$dir_path\" -type f -print0 2>/dev/null\n    return\n  fi\n  \n  # Try git-based detection first\n  if git rev-parse --verify HEAD@{1} &>/dev/null 2>&1; then\n    local temp_file\n    temp_file=$(mktemp)\n    \n    # Get changed files with specific filters (Added, Copied, Modified, Renamed)\n    git diff --name-only --diff-filter=ACMR HEAD@{1} HEAD 2>/dev/null | \\\n      while IFS= read -r file; do\n        local full_path=\"${REPO_ROOT}/${file}\"\n        if [[ \"$full_path\" == \"$dir_path\"/* ]] && [[ -f \"$full_path\" ]]; then\n          echo \"$full_path\"\n        fi\n      done > \"$temp_file\"\n    \n    if [[ -s \"$temp_file\" ]]; then\n      # Found changes via git\n      tr '\\n' '\\0' < \"$temp_file\"\n      rm -f \"$temp_file\"\n      return\n    fi\n    rm -f \"$temp_file\"\n  fi\n  \n  # Fallback: check all files\n  find \"$dir_path\" -type f -print0 2>/dev/null\n}\n\n# Function to check if we have new commits\nhas_new_commits() {\n  if git rev-parse --verify HEAD@{1} &>/dev/null; then\n    [[ \"$(git rev-parse HEAD)\" != \"$(git rev-parse HEAD@{1})\" ]]\n  else\n    # Fresh clone or no reflog - assume we want to process files\n    return 0\n  fi\n}\n\n# Cleanup function for signal handling\ncleanup_on_exit() {\n  local exit_code=$?\n  \n  # Remove lock file\n  rm -f \"${REPO_ROOT}/.update-lock\" 2>/dev/null || true\n  \n  if [[ $exit_code -ne 0 ]] && [[ \"$DRY_RUN\" != true ]]; then\n    echo\n    log_warning \"Update interrupted or failed (exit code: $exit_code)\"\n    log_info \"System may be in an inconsistent state\"\n    log_info \"Run the update again to complete the process\"\n  fi\n}\n\n# Set up signal handling and lock file\nif [[ \"${SOURCE_ONLY:-false}\" != true ]]; then\ntrap cleanup_on_exit EXIT INT TERM\n\n# Check for concurrent runs\nif [[ -f \"${REPO_ROOT}/.update-lock\" ]]; then\n  # Check if the process is still running\n  if kill -0 \"$(cat \"${REPO_ROOT}/.update-lock\" 2>/dev/null)\" 2>/dev/null; then\n    log_die \"Another update is already running (PID: $(cat \"${REPO_ROOT}/.update-lock\"))\"\n  else\n    log_warning \"Found stale lock file, removing...\"\n    rm -f \"${REPO_ROOT}/.update-lock\"\n  fi\nfi\n\n# Create lock file with current PID\nif [[ \"$DRY_RUN\" != true ]]; then\n  echo $$ > \"${REPO_ROOT}/.update-lock\"\nfi\n\n# Main script starts here\nlog_header \"Dotfiles Update Script\"\n\nif [[ \"$SKIP_NOTICE\" == false ]]; then\n  log_warning \"THIS SCRIPT IS NOT FULLY TESTED AND MAY CAUSE ISSUES!\"\n  log_warning \"It might be safer if you want to preserve your modifications and not delete added files,\"\n  log_warning \"  but this can cause partial updates and therefore unexpected behavior like in #1856.\"\n  log_warning \"In general, prefer \\\"./setup install\\\" for updates if available.\"\n  safe_read \"Continue? (y/N): \" response \"N\"\n\n  if [[ ! \"$response\" =~ ^[Yy]$ ]]; then\n    log_error \"Update aborted by user\"\n    exit 1\n  fi\nfi\n\n# Check if we're in a git repository\ncd \"$REPO_ROOT\" || log_die \"Failed to change to repository directory\"\n\nif git rev-parse --is-inside-work-tree &>/dev/null; then\n  log_info \"Running in git repository: $(git rev-parse --show-toplevel)\"\nelse\n  log_error \"Not in a git repository. Please run this script from your dotfiles repository.\"\n  exit 1\nfi\n\n# Auto-detect repository structure\nlog_header \"Detecting Repository Structure\"\nif detected_dirs=$(detect_repo_structure); then\n  read -ra MONITOR_DIRS <<<\"$detected_dirs\"\n  log_success \"Detected repository structure:\"\n  for dir in \"${MONITOR_DIRS[@]}\"; do\n    if [[ -d \"${REPO_ROOT}/${dir}\" ]]; then\n      log_info \"  ✓ ${REPO_ROOT}/${dir}\"\n    else\n      log_warning \"  ✗ ${REPO_ROOT}/${dir} (not found, will skip)\"\n    fi\n  done\nelse\n  log_die \"Failed to detect repository structure. Make sure you're in the correct directory.\"\nfi\n\n# Load ignore patterns once at startup (performance optimization)\nload_ignore_patterns\n\n# Step 1: Pull latest commits\nlog_header \"Pulling Latest Changes\"\n\ncurrent_branch=$(git branch --show-current)\nif [[ -z \"$current_branch\" ]]; then\n  log_warning \"In detached HEAD state. Checking out main/master branch...\"\n  if git show-ref --verify --quiet refs/heads/main; then\n    git checkout main\n    current_branch=\"main\"\n  elif git show-ref --verify --quiet refs/heads/master; then\n    git checkout master\n    current_branch=\"master\"\n  else\n    log_die \"Could not find main or master branch\"\n  fi\nfi\n\nlog_info \"Current branch: $current_branch\"\n\nif ! git diff --quiet || ! git diff --cached --quiet; then\n  log_warning \"You have uncommitted changes:\"\n  git status --short\n  echo\n\n  if ! safe_read \"Do you want to continue? This will stash your changes. (y/N): \" response \"N\"; then\n    echo\n    log_error \"Failed to read input. Aborting.\"\n    exit 1\n  fi\n\n  if [[ ! \"$response\" =~ ^[Yy]$ ]]; then\n    log_die \"Aborted by user\"\n  fi\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would stash changes\"\n  else\n    git stash push -m \"Auto-stash before update $(date)\"\n    log_info \"Changes stashed\"\n  fi\nfi\n\nif git remote get-url origin &>/dev/null; then\n  log_info \"Pulling changes from origin/$current_branch...\"\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would run: git pull --ff-only\"\n  else\n    if git pull --ff-only; then\n      log_success \"Successfully pulled latest changes\"\n      git submodule update --init --recursive\n      # Verify we actually got new commits\n      if git rev-parse --verify HEAD@{1} &>/dev/null; then\n        if [[ \"$(git rev-parse HEAD)\" == \"$(git rev-parse HEAD@{1})\" ]]; then\n          log_info \"Already up to date with remote\"\n        fi\n      fi\n    else\n      log_warning \"Failed to pull changes from remote.\"\n      log_warning \"This could be due to:\"\n      log_warning \"  - Network issues\"\n      log_warning \"  - Uncommitted local changes (use 'git stash' first)\"\n      log_warning \"  - Diverged history (may need 'git pull --rebase')\"\n      log_info \"Continuing with local repository state...\"\n    fi\n  fi\nelse\n  log_warning \"No remote 'origin' configured. Skipping pull operation.\"\n  log_info \"This appears to be a local-only repository.\"\nfi\n\n# Step 2: Handle package building\nrebuilt_packages=0\n\nif [[ \"$CHECK_PACKAGES\" == true ]]; then\n  log_header \"Package Management\"\n\n  # Check if required Arch Linux tools are available\n  if ! command -v pacman &>/dev/null || ! command -v makepkg &>/dev/null; then\n    log_warning \"Arch Linux package management tools (pacman/makepkg) not found.\"\n    log_warning \"Skipping package management as this appears to be a non-Arch Linux system.\"\n    log_warning \"Use -p/--packages flag only on Arch Linux systems.\"\n    PKG_TOOLS_AVAILABLE=false\n  else\n    PKG_TOOLS_AVAILABLE=true\n  fi\n\n  if [[ \"$PKG_TOOLS_AVAILABLE\" == true ]]; then\n    if [[ ! -d \"$ARCH_PACKAGES_DIR\" ]]; then\n      log_warning \"No packages directory found (tried: dist-arch, arch-packages, sdata/dist-arch).\"\n      log_warning \"Skipping package management.\"\n    else\n      # Scan for changed PKGBUILDs\n      changed_pkgbuilds=()\n      for pkg_dir in \"$ARCH_PACKAGES_DIR\"/*/; do\n        if [[ -f \"${pkg_dir}/PKGBUILD\" ]]; then\n          pkg_name=$(basename \"$pkg_dir\")\n          if check_pkgbuild_changed \"$pkg_dir\"; then\n            changed_pkgbuilds+=(\"$pkg_name\")\n          fi\n        fi\n      done\n\n      if [[ ${#changed_pkgbuilds[@]} -gt 0 ]]; then\n        log_info \"Found ${#changed_pkgbuilds[@]} package(s) with changed PKGBUILDs: ${changed_pkgbuilds[*]}\"\n        echo\n        echo \"Package build options:\"\n        echo \"1) Build only packages with changed PKGBUILDs\"\n        echo \"2) List all packages and select which to build\"\n        echo \"3) Build all packages\"\n        echo \"4) Skip package building\"\n        echo\n\n        if [[ \"$NON_INTERACTIVE\" == true ]]; then\n          pkg_choice=\"1\"\n          log_info \"Non-interactive mode: Using default package option: $pkg_choice\"\n        elif safe_read \"Choose an option (1-4): \" pkg_choice \"1\"; then\n          if [[ \"$VERBOSE\" == true ]]; then\n            log_info \"User selected package option: $pkg_choice\"\n          fi\n        else\n          log_warning \"Failed to read input. Skipping package building.\"\n          pkg_choice=\"\"\n        fi\n\n        if [[ -n \"$pkg_choice\" ]]; then\n          case $pkg_choice in\n            1) build_packages \"changed\" ;;\n            2)\n              if list_packages; then\n                build_packages \"select\"\n              fi\n              ;;\n            3) build_packages \"all\" ;;\n            4|*) log_info \"Skipping package building\" ;;\n          esac\n        fi\n      else\n        log_info \"No PKGBUILDs have changed since last update.\"\n        echo\n        if [[ \"$NON_INTERACTIVE\" == true ]]; then\n          check_anyway=\"N\"\n          log_info \"Non-interactive mode: Using default for check packages anyway: $check_anyway\"\n        elif safe_read \"Do you want to check and build packages anyway? (y/N): \" check_anyway \"N\"; then\n          if [[ \"$VERBOSE\" == true ]]; then\n            log_info \"User chose to check packages anyway: $check_anyway\"\n          fi\n        else\n          log_warning \"Failed to read input. Skipping package management.\"\n          check_anyway=\"\"\n        fi\n\n        if [[ -n \"$check_anyway\" && \"$check_anyway\" =~ ^[Yy]$ ]]; then\n          if list_packages; then\n            echo\n            echo \"Package build options:\"\n            echo \"1) Select specific packages to build\"\n            echo \"2) Build all packages\"\n            echo \"3) Skip package building\"\n\n            if safe_read \"Choose an option (1-3): \" build_choice \"3\"; then\n              case $build_choice in\n                1) build_packages \"select\" ;;\n                2) build_packages \"all\" ;;\n                3|*) log_info \"Skipping package building\" ;;\n              esac\n            else\n              log_info \"Skipping package building\"\n            fi\n          fi\n        else\n          log_info \"Skipping package management\"\n        fi\n      fi\n    fi\n  fi\nelse\n  log_header \"Package Management\"\n  log_info \"Package checking disabled. Use -p or --packages flag to enable package management.\"\nfi\n\n# Step 3: Update configuration files\nlog_header \"Updating Configuration Files\"\n\nprocess_files=false\nif [[ \"$FORCE_CHECK\" == true ]]; then\n  process_files=true\n  log_info \"Force mode: checking all configuration files\"\nelif has_new_commits; then\n  process_files=true\n  log_info \"New commits detected: checking changed configuration files\"\nelse\n  log_info \"No new commits found and force mode not enabled: skipping file updates\"\n  process_files=false\nfi\n\nif [[ \"$process_files\" == true ]]; then\n  files_processed=0\n  files_updated=0\n  files_created=0\n  \n  # Count total files for progress indication (optional)\n  total_files=0\n  if [[ \"$VERBOSE\" == false ]] && command -v tput &>/dev/null 2>&1; then\n    for dir_name in \"${MONITOR_DIRS[@]}\"; do\n      repo_dir_path=\"${REPO_ROOT}/${dir_name}\"\n      [[ ! -d \"$repo_dir_path\" ]] && continue\n      total_files=$((total_files + $(find \"$repo_dir_path\" -type f 2>/dev/null | wc -l)))\n    done\n  fi\n\n  for dir_name in \"${MONITOR_DIRS[@]}\"; do\n    repo_dir_path=\"${REPO_ROOT}/${dir_name}\"\n    \n    if [[ ! -d \"$repo_dir_path\" ]]; then\n      if [[ \"$VERBOSE\" == true ]]; then\n        log_warning \"Skipping non-existent directory: $repo_dir_path\"\n      fi\n      continue\n    fi\n    \n    # FIX: Properly handle dots/ prefix mapping\n    if [[ \"$dir_name\" == dots/* ]]; then\n      # Strip \"dots/\" prefix for home directory mapping\n      home_subdir=\"${dir_name#dots/}\"\n      home_dir_path=\"${HOME}/${home_subdir}\"\n    else\n      # Direct structure\n      home_dir_path=\"${HOME}/${dir_name}\"\n    fi\n\n    log_info \"Processing directory: $dir_name → ${home_dir_path}\"\n\n    ensure_directory \"$home_dir_path\" || continue\n\n    while IFS= read -r -d '' -u 9 repo_file; do\n      # Calculate relative path from the repo source directory\n      rel_path=\"${repo_file#$repo_dir_path/}\"\n      home_file=\"${home_dir_path}/${rel_path}\"\n\n      if should_ignore \"$home_file\"; then\n        if [[ \"$VERBOSE\" == true ]]; then\n          log_info \"Ignored: $rel_path (matches ignore pattern)\"\n        fi\n        continue\n      fi\n\n      if [[ \"$VERBOSE\" == true ]]; then\n        log_info \"Processing: $rel_path\"\n      fi\n\n      ((files_processed++))\n      \n      # Show progress for non-verbose mode\n      if [[ \"$VERBOSE\" == false ]] && command -v tput &>/dev/null 2>&1 && [[ $total_files -gt 0 ]]; then\n        printf \"\\r[INFO] Processing files: %d/%d\" \"$files_processed\" \"$total_files\" >&2\n      fi\n\n      ensure_directory \"$(dirname \"$home_file\")\" || continue\n\n      if [[ -f \"$home_file\" ]]; then\n        if ! cmp -s \"$repo_file\" \"$home_file\"; then\n          # Clear progress line if showing\n          if [[ \"$VERBOSE\" == false ]] && command -v tput &>/dev/null 2>&1 && [[ $total_files -gt 0 ]]; then\n            printf \"\\r%*s\\r\" \"80\" \"\" >&2\n          fi\n          \n          log_info \"Found difference in: $rel_path\"\n          if [[ \"$DRY_RUN\" == true ]]; then\n            log_warning \"[DRY-RUN] Conflict detected (would prompt): $home_file\"\n            ((files_updated++))\n          else\n            handle_file_conflict \"$repo_file\" \"$home_file\"\n            ((files_updated++))\n          fi\n        fi\n      else\n        if [[ \"$DRY_RUN\" == true ]]; then\n          if [[ \"$VERBOSE\" == true ]]; then\n            log_info \"[DRY-RUN] Would create new file: $home_file\"\n          fi\n        else\n          cp -p \"$repo_file\" \"$home_file\"\n          if [[ \"$VERBOSE\" == true ]]; then\n            log_success \"Created new file: $home_file\"\n          fi\n        fi\n        ((files_created++))\n      fi\n    done 9< <(get_changed_files \"$repo_dir_path\") || true\n    echo\n  done\n\n  # Clear progress line if it was shown\n  if [[ \"$VERBOSE\" == false ]] && command -v tput &>/dev/null 2>&1 && [[ $total_files -gt 0 ]]; then\n    printf \"\\r%*s\\r\" \"80\" \"\" >&2\n  fi\n\n  echo\n  log_info \"File processing summary:\"\n  log_info \"- Files processed: $files_processed\"\n  log_info \"- Files with conflicts: $files_updated\"\n  log_info \"- New files created: $files_created\"\nelse\n  log_info \"Skipping file updates (no changes detected and not in force mode)\"\nfi\n\n# Step 4: Update script permissions\nlog_header \"Updating Script Permissions\"\n\nif [[ -d \"${HOME}/.local/bin\" ]]; then\n  if [[ \"$DRY_RUN\" == true ]]; then\n    log_info \"[DRY-RUN] Would update script permissions in ~/.local/bin\"\n  else\n    find \"${HOME}/.local/bin\" -type f -exec chmod +x {} \\; 2>/dev/null || true\n    log_success \"Updated ~/.local/bin script permissions\"\n  fi\nfi\n\nlog_header \"Update Complete\"\nif [[ \"$DRY_RUN\" == true ]]; then\n  log_warning \"DRY-RUN MODE: No changes were actually made\"\n  log_info \"Run without -n/--dry-run to apply changes\"\nelse\n  log_success \"Dotfiles update completed successfully!\"\nfi\n\necho\necho -e \"${STY_CYAN}Summary:${STY_RST}\"\nif command -v git >/dev/null && git rev-parse --git-dir >/dev/null 2>&1; then\n  echo \"- Repository: $(git log -1 --pretty=format:'%h - %s (%cr)' 2>/dev/null || echo 'Unknown')\"\nelse\n  echo \"- Repository: Unknown (git not available)\"\nfi\necho \"- Branch: ${current_branch:-Unknown}\"\necho \"- Structure: ${MONITOR_DIRS[*]}\"\necho \"- Mode: $([ \"$FORCE_CHECK\" == true ] && echo \"Force check\" || echo \"Normal\")\"\necho \"- Package checking: $([ \"$CHECK_PACKAGES\" == true ] && echo \"Enabled\" || echo \"Disabled\")\"\n\nif [[ $rebuilt_packages -gt 0 ]]; then\n  echo \"- Packages rebuilt: $rebuilt_packages\"\nfi\n\nif [[ \"$process_files\" == true ]]; then\n  echo \"- Files processed: $files_processed\"\n  echo \"- Files updated/conflicted: $files_updated\"\n  echo \"- New files created: $files_created\"\nfi\n\nif [[ ! -f \"$XDG_UPDATE_IGNORE_FILE\" && ! -f \"$UPDATE_IGNORE_FILE\" ]]; then\n  echo\n  log_info \"Tip: Create ignore files to exclude files from updates:\"\n  echo \"  - Repository ignore: ${REPO_ROOT}/.updateignore\"\n  echo \"  - User ignore: ${XDG_UPDATE_IGNORE_FILE}\"\n  echo\n  echo \"Example patterns:\"\n  echo \"  *.log                 # Ignore all .log files\"\n  echo \"  .config/personal/     # Ignore entire directory\"\n  echo \"  secret-config.conf    # Ignore specific file\"\n  echo \"  /temp-file            # Ignore from root only\"\n  echo \"  **secret**            # Ignore files containing 'secret'\"\nfi\n\n# Show backup directory if any backups were created\nif [[ -d \"${REPO_ROOT}/.update-backups\" ]] && [[ \"$DRY_RUN\" != true ]]; then\n  echo\n  log_info \"Backups stored in: ${REPO_ROOT}/.update-backups/\"\nfi\n\nfi\n\necho\n"
  },
  {
    "path": "sdata/subcmd-exp-update/exp-update-tester.sh",
    "content": "#!/usr/bin/env bash\n#\n# exp-update-tester.sh - Test suite for exp-update\n#\nset -euo pipefail\n\n# Colors\nRED='\\033[0;31m'\nGREEN='\\033[0;32m'\nBLUE='\\033[0;34m'\nNC='\\033[0m'\n\nTESTS_PASSED=0\nTESTS_FAILED=0\nTEST_DIR=\"\"\nORIGINAL_DIR=\"$PWD\"\n\n# Helper functions\nlog_test() {\n  echo -e \"${BLUE}[TEST]${NC} $1\"\n}\n\nlog_pass() {\n  echo -e \"${GREEN}[PASS]${NC} $1\"\n  ((TESTS_PASSED++))\n}\n\nlog_fail() {\n  echo -e \"${RED}[FAIL]${NC} $1\"\n  ((TESTS_FAILED++))\n}\n\nlog_error() {\n  echo -e \"${RED}[ERROR]${NC} $1\"\n}\n\n# Setup test environment\nsetup_test_env() {\n  local temp_dir\n  temp_dir=$(mktemp -d -t dotfiles-test.XXXXXX)\n\n  cd \"$temp_dir\" || { echo \"Failed to cd to test directory\"; return 1; }\n  git init -q\n  git config user.email \"test@example.com\"\n  git config user.name \"Test User\"\n\n  git commit --allow-empty -m \"Initial commit\" -q\n\n  echo \"$temp_dir\"\n}\n\n# Cleanup test environment\ncleanup_test_env() {\n  if [[ -n \"${TEST_DIR:-}\" && -d \"$TEST_DIR\" ]]; then\n    rm -rf \"$TEST_DIR\"\n    TEST_DIR=\"\"\n  fi\n}\n\n# Run a test and handle cleanup\nrun_test() {\n  local test_name=\"$1\"\n  local test_func=\"$2\"\n\n  # Cleanup before test\n  cleanup_test_env\n\n  # Run the test\n  if $test_func; then\n    echo \"✓ $test_name passed\"\n    return 0\n  else\n    echo \"✗ $test_name failed\"\n    return 1\n  fi\n}\n\n# Test 2: Script has no syntax errors\ntest_syntax() {\n  log_test \"Checking script syntax\"\n\n  if bash -n setup; then\n    log_pass \"No syntax errors found\"\n    return 0\n  else\n    log_fail \"Syntax errors detected\"\n    return 1\n  fi\n}\n\n# Test 3: Help option works\ntest_help_option() {\n  log_test \"Testing --help option\"\n\n  if ./setup exp-update --help 2>&1 | grep -qiE \"(Syntax|Options|exp-update)\"; then\n    log_pass \"Help option works\"\n    return 0\n  else\n    log_fail \"Help option failed\"\n    return 1\n  fi\n}\n\n# Test 4: Test repository structure detection (dots/ prefix)\ntest_dots_structure() {\n  log_test \"Testing dots/ prefix structure detection\"\n\n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n\n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n\n  mkdir -p dots/.config/test-app\n  mkdir -p dots/.local/bin\n  echo \"test config\" > dots/.config/test-app/config.conf\n\n  git add .\n  git commit -m \"Add dots structure\" -q\n\n  cat > test_detection.sh << EOF\n#!/bin/bash\n# Mock logging and style functions/variables\nlog_info() { :; }\nlog_warning() { :; }\nlog_error() { :; }\nlog_success() { :; }\nlog_header() { :; }\nlog_die() { echo \"ERROR: \\$1\"; exit 1; }\nSTY_CYAN=\"\" STY_RST=\"\" STY_YELLOW=\"\"\n\n# Set required environment variables for exp-update/0.run.sh\nSKIP_NOTICE=true\nREPO_ROOT=\"\\$1\"\nCHECK_PACKAGES=false\nDRY_RUN=false\nFORCE_CHECK=false\nVERBOSE=false\nNON_INTERACTIVE=true\nSOURCE_ONLY=true\n\nsource \"$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh\"\ndetected_dirs=\\$(detect_repo_structure)\nif [[ -n \"\\$detected_dirs\" ]]; then\n  read -ra MONITOR_DIRS <<<\"\\$detected_dirs\"\nfi\necho \"Structure: \\${MONITOR_DIRS[*]}\"\nEOF\n\n  chmod +x test_detection.sh\n  result=$(./test_detection.sh \"$test_repo\")\n\n  if [[ \"$result\" == *\"dots/.config\"* ]]; then\n    log_pass \"Dots structure detected correctly\"\n    cd \"$ORIGINAL_DIR\"\n    return 0\n  else\n    log_fail \"Failed to detect dots structure. Got: $result\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  fi\n}\n\n# Test 5: Test flat structure detection\ntest_flat_structure() {\n  log_test \"Testing flat structure detection\"\n\n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n\n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n\n  mkdir -p .config/test-app\n  mkdir -p .local/bin\n  echo \"test config\" > .config/test-app/config.conf\n\n  git add .\n  git commit -m \"Add flat structure\" -q\n\n  cat > test_detection.sh << EOF\n#!/bin/bash\n# Mock logging and style functions/variables\nsource \"$ORIGINAL_DIR/sdata/lib/environment-variables.sh\"\nsource \"$ORIGINAL_DIR/sdata/lib/functions.sh\"\nlog_info() { :; }\nlog_warning() { :; }\nlog_error() { :; }\nlog_success() { :; }\nlog_header() { :; }\nlog_die() { echo \"ERROR: \\$1\"; exit 1; }\n\n# Set required environment variables for exp-update\nSKIP_NOTICE=true\nREPO_ROOT=\"\\$1\"\nCHECK_PACKAGES=false\nDRY_RUN=false\nFORCE_CHECK=false\nVERBOSE=false\nNON_INTERACTIVE=true\nSOURCE_ONLY=true\n\nsource \"$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh\"\ndetected_dirs=\\$(detect_repo_structure)\nif [[ -n \"\\$detected_dirs\" ]]; then\n  read -ra MONITOR_DIRS <<<\"\\$detected_dirs\"\nfi\necho \"Structure: \\${MONITOR_DIRS[*]}\"\nEOF\n\n  chmod +x test_detection.sh\n  result=$(./test_detection.sh \"$test_repo\")\n\n  if [[ \"$result\" == *\".config\"* ]] && [[ \"$result\" != *\"dots/\"* ]]; then\n    log_pass \"Flat structure detected correctly\"\n    cd \"$ORIGINAL_DIR\"\n    return 0\n  else\n    log_fail \"Failed to detect flat structure. Got: $result\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  fi\n}\n\n# Test 6: Test dots prefix mapping to home directory\ntest_dots_mapping() {\n  log_test \"Testing dots/ prefix home directory mapping\"\n  \n  dir_name=\"dots/.config\"\n  if [[ \"$dir_name\" == dots/* ]]; then\n    home_subdir=\"${dir_name#dots/}\"\n    home_dir_path=\"${HOME}/${home_subdir}\"\n  else\n    home_dir_path=\"${HOME}/${dir_name}\"\n  fi\n  \n  expected_path=\"${HOME}/.config\"\n  if [[ \"$home_dir_path\" == \"$expected_path\" ]]; then\n    log_pass \"Dots prefix mapping correct: $dir_name → $home_dir_path\"\n    return 0\n  else\n    log_fail \"Dots prefix mapping failed: $dir_name → $home_dir_path (expected: $expected_path)\"\n    return 1\n  fi\n}\n\n# Test 7: Test ignore file patterns - FIXED\ntest_ignore_patterns() {\n  log_test \"Testing ignore file pattern matching\"\n  \n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n  \n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n  \n  cat > .updateignore << 'EOF'\n*.log\nsecrets/\n.config/private*\n*backup*\nEOF\n  \n  mkdir -p .config\n  mkdir -p secrets\n  \n  cat > test_ignore.sh << EOF\n#!/bin/bash\n# Suppress all output from sourced script\nsource \"$ORIGINAL_DIR/sdata/lib/environment-variables.sh\"\nsource \"$ORIGINAL_DIR/sdata/lib/functions.sh\"\nlog_info() { :; }\nlog_warning() { :; }\nlog_error() { :; }\nlog_success() { :; }\nlog_header() { :; }\nlog_die() { echo \"ERROR: \\$1\" >&2; exit 1; }\n\n# FIXED: Set REPO_ROOT before sourcing exp-update\nREPO_ROOT=\"\\$1\"\nexport REPO_ROOT\n\n# Set other required environment variables\nSKIP_NOTICE=true\nCHECK_PACKAGES=false\nDRY_RUN=false\nFORCE_CHECK=false\nVERBOSE=false\nNON_INTERACTIVE=true\n\nUPDATE_IGNORE_FILE=\"\\${REPO_ROOT}/.updateignore\"\nHOME_UPDATE_IGNORE_FILE=\"/dev/null\"\n\n# Source the production script to use the real should_ignore function\n# Redirect all unwanted output to stderr, then to /dev/null\nsource \"$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh\" 2>/dev/null\n\ntest_cases=(\n  \"\\$REPO_ROOT/app.log:0\"\n  \"\\$REPO_ROOT/secrets/key.txt:0\" \n  \"\\$REPO_ROOT/.config/private-config:0\"\n  \"\\$REPO_ROOT/.config/backup-file:0\"\n  \"\\$REPO_ROOT/normal-config:1\"\n)\n\nall_passed=true\nfor test_case in \"\\${test_cases[@]}\"; do\n  IFS=\":\" read -r file expected <<< \"\\$test_case\"\n  mkdir -p \"\\$(dirname \"\\$file\")\"\n  touch \"\\$file\"\n  \n  if should_ignore \"\\$file\"; then\n    result=0\n  else\n    result=1\n  fi\n  \n  if [[ \\$result -ne \\$expected ]]; then\n    echo \"FAIL: \\$file (expected: \\$expected, got: \\$result)\"\n    all_passed=false\n  fi\ndone\n\nif [[ \"\\$all_passed\" == true ]]; then\n  echo \"PASS\"\nelse\n  echo \"FAIL\"\nfi\nEOF\n  \n  chmod +x test_ignore.sh\n  result=$(./test_ignore.sh \"$test_repo\" 2>&1 | grep -E \"^(PASS|FAIL)\")\n  \n  if [[ \"$result\" == \"PASS\" ]]; then\n    log_pass \"All ignore pattern tests passed\"\n    cd \"$ORIGINAL_DIR\"\n    return 0\n  else\n    log_fail \"Some ignore pattern tests failed\"\n    echo \"$result\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  fi\n}\n\n# Test 8: Test safe_read security - FIXED\ntest_safe_read_security() {\n  log_test \"Testing safe_read uses secure assignment (printf -v)\"\n\n  local safe_read_function\n  safe_read_function=$(awk '/^safe_read\\(\\) \\{/,/^\\}/' \"$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh\")\n\n  if [[ -z \"$safe_read_function\" ]]; then\n    log_fail \"Could not find safe_read function\"\n    return 1\n  fi\n\n  # FIXED: Remove comments before checking for eval\n  # The function has a comment mentioning eval, which shouldn't count\n  local function_without_comments\n  function_without_comments=$(echo \"$safe_read_function\" | sed 's/#.*$//')\n  \n  local has_printf_v=false\n  local has_eval=false\n  \n  if echo \"$safe_read_function\" | grep -F 'printf -v' > /dev/null; then\n    has_printf_v=true\n  fi\n  \n  # Check for eval in actual code (not comments)\n  if echo \"$function_without_comments\" | grep -w 'eval' > /dev/null; then\n    has_eval=true\n  fi\n\n  if [[ \"$has_printf_v\" == true ]] && [[ \"$has_eval\" == false ]]; then\n    log_pass \"safe_read uses secure printf -v assignment and no eval\"\n    return 0\n  else\n    log_fail \"safe_read does not use secure assignment or contains eval (has_printf_v=$has_printf_v, has_eval=$has_eval)\"\n    echo \"Function content:\"\n    echo \"$safe_read_function\"\n    return 1\n  fi\n}\n\n# Test 9: Test dry-run mode - FIXED\ntest_dry_run() {\n  log_test \"Testing dry-run mode\"\n\n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n\n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n\n  # Copy necessary files for setup to run\n  cp \"$ORIGINAL_DIR/setup\" .\n  cp -r \"$ORIGINAL_DIR/sdata\" .\n  cp -r \"$ORIGINAL_DIR/dots\" .\n  chmod +x setup\n\n  # Create a test config file in repo\n  mkdir -p dots/.config/test-app\n  echo \"test config\" > dots/.config/test-app/config.conf\n\n  git add .\n  git commit -m \"Add test config\" -q\n\n  # FIXED: Clean up any existing test files before running test\n  rm -rf \"${HOME}/.config/test-app\" 2>/dev/null || true\n\n  # Use non-interactive mode and check for DRY-RUN marker\n  ./setup exp-update -n --skip-notice --non-interactive 2>&1 | tee dry_run_output.txt\n\n  if grep -q \"DRY-RUN\" dry_run_output.txt; then\n    log_pass \"Dry-run mode detected in output\"\n  else\n    log_fail \"Dry-run mode not properly indicated\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  fi\n\n  # FIXED: Check if files were created (they shouldn't be in dry-run)\n  if [[ -f \"${HOME}/.config/test-app/config.conf\" ]]; then\n    log_fail \"Files were created in home during dry-run\"\n    rm -rf \"${HOME}/.config/test-app\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  else\n    log_pass \"No files created in home during dry-run\"\n  fi\n\n  cd \"$ORIGINAL_DIR\"\n  return 0\n}\n\n# Test 10: Test command-line flags\ntest_flags() {\n  log_test \"Testing command-line flags\"\n\n  # Only test non-interactive flags\n  local flags=(\"-h\" \"--help\")\n  local all_passed=true\n\n  for flag in \"${flags[@]}\"; do\n    if ./setup exp-update \"$flag\" 2>&1 | grep -qiE \"(Syntax|Options|exp-update)\"; then\n      log_test \"  ✓ $flag recognized\"\n    else\n      log_test \"  ✗ $flag not recognized\"\n      all_passed=false\n    fi\n  done\n\n  if [[ \"$all_passed\" == true ]]; then\n    log_pass \"Help flags recognized correctly\"\n    return 0\n  else\n    log_fail \"Some flags not recognized properly\"\n    return 1\n  fi\n}\n\n# Test 11: Check for shellcheck\ntest_shellcheck() {\n  log_test \"Running shellcheck (if available)\"\n  \n  if ! command -v shellcheck &>/dev/null; then\n    log_test \"shellcheck not found, skipping static analysis\"\n    return 0\n  fi\n  \n  if shellcheck -e SC1090,SC1091,SC2148,SC2034,SC2155,SC2164 setup; then\n    log_pass \"shellcheck passed\"\n    return 0\n  else\n    log_fail \"shellcheck found issues\"\n    return 1\n  fi\n}\n\n# Test 12: Test lock file mechanism\ntest_lock_file() {\n  log_test \"Testing lock file mechanism\"\n  \n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n  \n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n  \n  # Copy necessary files\n  cp \"$ORIGINAL_DIR/setup\" .\n  cp -r \"$ORIGINAL_DIR/sdata\" .\n  mkdir -p dots/.config\n  chmod +x setup\n  \n  git add .\n  git commit -m \"Add files\" -q\n  \n  # Create a fake lock file\n  echo \"99999\" > .update-lock\n  \n  # Try to run update - should fail due to lock\n  if ./setup exp-update --skip-notice --non-interactive > lock_test_output.txt 2>&1; then\n    if grep -q \"stale lock\" lock_test_output.txt; then\n      log_pass \"Lock file mechanism works (detected stale lock)\"\n      cd \"$ORIGINAL_DIR\"\n      return 0\n    fi\n  fi\n  log_fail \"Lock file mechanism did not work as expected\"\n  cat lock_test_output.txt  # Show output for debugging\n  cd \"$ORIGINAL_DIR\"\n  return 1\n}\n\n# Test 13: Test ** substring ignore patterns - FIXED\ntest_substring_ignore_patterns() {\n  log_test \"Testing ** substring ignore pattern matching\"\n\n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n\n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n\n  cat > .updateignore << 'EOF'\n**temp**\n**backup**\n**testfile**\nEOF\n\n  mkdir -p .config/test-app\n  mkdir -p temp-backup-dir\n  mkdir -p .local/share/test-temp\n  mkdir -p .config/temp-file\n\n  cat > test_substring_ignore.sh << EOF\n#!/bin/bash\n# Suppress all output from sourced script\nsource \"$ORIGINAL_DIR/sdata/lib/environment-variables.sh\"\nsource \"$ORIGINAL_DIR/sdata/lib/functions.sh\"\nlog_info() { :; }\nlog_warning() { :; }\nlog_error() { :; }\nlog_success() { :; }\nlog_header() { :; }\nlog_die() { echo \"ERROR: \\$1\" >&2; exit 1; }\n\n# FIXED: Set REPO_ROOT before sourcing exp-update\nREPO_ROOT=\"\\$1\"\nexport REPO_ROOT\n\n# Set other required environment variables\nSKIP_NOTICE=true\nCHECK_PACKAGES=false\nDRY_RUN=false\nFORCE_CHECK=false\nVERBOSE=false\nNON_INTERACTIVE=true\n\nUPDATE_IGNORE_FILE=\"\\${REPO_ROOT}/.updateignore\"\nHOME_UPDATE_IGNORE_FILE=\"/dev/null\"\n\n# Source the production script to use the real should_ignore function\nsource \"$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh\" 2>/dev/null\n\n# Load patterns into cache\nload_ignore_patterns\n\ntest_cases=(\n  \"\\$REPO_ROOT/temp-backup-dir/file:0\"\n  \"\\$REPO_ROOT/.config/test-app/temp.conf:0\"\n  \"\\$REPO_ROOT/.local/share/test-temp/data:0\"\n  \"\\$REPO_ROOT/.config/temp-file/config:0\"\n  \"\\$REPO_ROOT/normal-config:1\"\n  \"\\$REPO_ROOT/.config/my-testfile.conf:0\"\n)\n\nall_passed=true\nfor test_case in \"\\${test_cases[@]}\"; do\n  IFS=\":\" read -r file expected <<< \"\\$test_case\"\n  mkdir -p \"\\$(dirname \"\\$file\")\"\n  touch \"\\$file\"\n\n  if should_ignore \"\\$file\"; then\n    result=0\n  else\n    result=1\n  fi\n\n  if [[ \\$result -ne \\$expected ]]; then\n    echo \"FAIL: \\$file (expected: \\$expected, got: \\$result)\"\n    all_passed=false\n  fi\ndone\n\nif [[ \"\\$all_passed\" == true ]]; then\n  echo \"PASS\"\nelse\n  echo \"FAIL\"\nfi\nEOF\n\n  chmod +x test_substring_ignore.sh\n  result=$(./test_substring_ignore.sh \"$test_repo\" 2>&1 | grep -E \"^(PASS|FAIL)\")\n\n  if [[ \"$result\" == \"PASS\" ]]; then\n    log_pass \"** substring ignore patterns work correctly\"\n    cd \"$ORIGINAL_DIR\"\n    return 0\n  else\n    log_fail \"** substring ignore patterns failed\"\n    echo \"$result\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  fi\n}\n\n# Test 14: Test ensure_directory caching\ntest_directory_caching() {\n  log_test \"Testing directory creation caching\"\n  \n  local test_repo\n  test_repo=$(setup_test_env)\n  TEST_DIR=\"$test_repo\"\n  \n  cd \"$test_repo\" || { log_fail \"Failed to cd to test directory\"; return 1; }\n  \n  cat > test_dir_cache.sh << EOF\n#!/bin/bash\nsource \"$ORIGINAL_DIR/sdata/lib/environment-variables.sh\"\nsource \"$ORIGINAL_DIR/sdata/lib/functions.sh\"\nlog_info() { :; }\nlog_warning() { :; }\nlog_error() { :; }\nlog_success() { :; }\nlog_header() { :; }\nlog_die() { echo \"ERROR: \\$1\" >&2; exit 1; }\n\nREPO_ROOT=\"\\$1\"\nexport REPO_ROOT\n\nSKIP_NOTICE=true\nCHECK_PACKAGES=false\nDRY_RUN=false\nFORCE_CHECK=false\nVERBOSE=false\nNON_INTERACTIVE=true\nSOURCE_ONLY=true\n\nsource \"$ORIGINAL_DIR/sdata/subcmd-exp-update/0.run.sh\" 2>/dev/null\n\ntest_dir=\"/tmp/test-ensure-dir-\\$\\$\"\n\n# First call should create\nensure_directory \"\\$test_dir\"\nresult1=\\$?\n\n# Second call should use cache\nensure_directory \"\\$test_dir\"\nresult2=\\$?\n\n# Check if CREATED_DIRS has the entry\nif [[ -n \"\\${CREATED_DIRS[\\$test_dir]:-}\" ]] && [[ \\$result1 -eq 0 ]] && [[ \\$result2 -eq 0 ]]; then\n  echo \"PASS\"\n  rm -rf \"\\$test_dir\"\nelse\n  echo \"FAIL\"\nfi\nEOF\n  \n  chmod +x test_dir_cache.sh\n  result=$(./test_dir_cache.sh \"$test_repo\" 2>&1 | grep -E \"^(PASS|FAIL)\")\n  \n  if [[ \"$result\" == \"PASS\" ]]; then\n    log_pass \"Directory creation caching works\"\n    cd \"$ORIGINAL_DIR\"\n    return 0\n  else\n    log_fail \"Directory creation caching failed\"\n    cd \"$ORIGINAL_DIR\"\n    return 1\n  fi\n}\n\n# Test 15: Test enhanced safe_read with non-interactive mode\ntest_safe_read_noninteractive() {\n  log_test \"Testing safe_read in non-interactive mode\"\n  \n  cat > test_safe_read.sh << 'EOF'\n#!/bin/bash\nsource \"$ORIGINAL_DIR/sdata/lib/environment-variables.sh\"\nsource \"$ORIGINAL_DIR/sdata/lib/functions.sh\"\nlog_warning() { :; }\nlog_error() { :; }\n\n# Simulate the enhanced safe_read function\nsafe_read() {\n  local prompt=\"$1\"\n  local varname=\"$2\"\n  local default=\"${3:-}\"\n  local input_value=\"\"\n\n  # In non-interactive mode, use default immediately\n  if [[ \"$NON_INTERACTIVE\" == true ]]; then\n    if [[ -n \"$default\" ]]; then\n      printf -v \"$varname\" '%s' \"$default\"\n      return 0\n    else\n      log_error \"Non-interactive mode requires default value for: $prompt\"\n      return 1\n    fi\n  fi\n  \n  # Regular read logic...\n  printf -v \"$varname\" '%s' \"$default\"\n  return 0\n}\n\n# Test 1: With default in non-interactive mode\nNON_INTERACTIVE=true\nif safe_read \"Test: \" result \"default_value\"; then\n  if [[ \"$result\" == \"default_value\" ]]; then\n    echo \"TEST1: PASS\"\n  else\n    echo \"TEST1: FAIL - got '$result'\"\n  fi\nelse\n  echo \"TEST1: FAIL - returned error\"\nfi\n\n# Test 2: Without default in non-interactive mode (should fail)\nif safe_read \"Test: \" result \"\"; then\n  echo \"TEST2: FAIL - should have failed\"\nelse\n  echo \"TEST2: PASS - correctly failed\"\nfi\nEOF\n  \n  chmod +x test_safe_read.sh\n  result=$(./test_safe_read.sh 2>&1)\n  \n  if echo \"$result\" | grep -q \"TEST1: PASS\" && echo \"$result\" | grep -q \"TEST2: PASS\"; then\n    log_pass \"Enhanced safe_read handles non-interactive mode correctly\"\n    rm -f test_safe_read.sh\n    return 0\n  else\n    log_fail \"Enhanced safe_read non-interactive mode failed\"\n    echo \"$result\"\n    rm -f test_safe_read.sh\n    return 1\n  fi\n}\n\n# Main test runner\nmain() {\n  echo -e \"${BLUE}================================${NC}\"\n  echo -e \"${BLUE}  Update.sh Test Suite (Enhanced)${NC}\"\n  echo -e \"${BLUE}================================${NC}\\n\"\n\n  if [[ ! -f \"setup\" ]]; then\n    log_error \"Please run this test from the directory containing setup\"\n    exit 1\n  fi\n\n  chmod +x setup 2>/dev/null || true\n\n  # Define tests\n  tests=(\n    \"test_syntax\"\n    \"test_help_option\"\n    \"test_dots_structure\"\n    \"test_flat_structure\"\n    \"test_dots_mapping\"\n    \"test_ignore_patterns\"\n    \"test_substring_ignore_patterns\"\n    \"test_safe_read_security\"\n    \"test_dry_run\"\n    \"test_flags\"\n    \"test_shellcheck\"\n    \"test_lock_file\"\n    \"test_directory_caching\"\n    \"test_safe_read_noninteractive\"\n  )\n\n  # Run tests\n  for test in \"${tests[@]}\"; do\n    if $test; then\n      echo \"✓ $test passed\"\n    else\n      echo \"✗ $test failed\"\n    fi\n    echo\n  done\n\n  # Summary\n  echo -e \"${BLUE}================================${NC}\"\n  echo -e \"${BLUE}  Test Summary${NC}\"\n  echo -e \"${BLUE}================================${NC}\"\n  echo -e \"${GREEN}Passed: $TESTS_PASSED${NC}\"\n  echo -e \"${RED}Failed: $TESTS_FAILED${NC}\"\n  echo -e \"${BLUE}Total:  ${#tests[@]}${NC}\\n\"\n\n  if [[ $TESTS_FAILED -eq 0 ]]; then\n    echo -e \"${GREEN}All tests passed! 🎉${NC}\\n\"\n    exit 0\n  else\n    echo -e \"${RED}Some tests failed! ❌${NC}\\n\"\n    exit 1\n  fi\n}\n\n# Global cleanup\ncleanup() {\n  echo \"Cleaning up test files...\"\n  cleanup_test_env\n  rm -f test_detection.sh test_ignore.sh test_safe_read.sh test_fresh_clone.sh test_substring_ignore.sh dry_run_output.txt 2>/dev/null || true\n  rm -f test_caching.sh test_dir_cache.sh 2>/dev/null || true\n  rm -f lock_test_output.txt 2>/dev/null || true\n  rm -rf \"${HOME}/.config/test-app\" 2>/dev/null || true\n}\n\ntrap cleanup EXIT INT TERM\n\nif [[ \"${BASH_SOURCE[0]}\" == \"${0}\" ]]; then\n  main \"$@\"\nfi\n"
  },
  {
    "path": "sdata/subcmd-exp-update/options.sh",
    "content": "# Handle args for subcmd: exp-update\n# shellcheck shell=bash\n\nshowhelp(){\necho -e \"Syntax: $0 exp-update [OPTIONS]...\n\nExperimental updating without full reinstall.\nUpdates dotfiles by syncing configuration files to home directory.\n\nOptions:\n  -f, --force        Force check all files even if no new commits\n  -p, --packages     Enable package checking and building\n  -n, --dry-run      Show what would be done without making changes\n  -v, --verbose      Enable verbose output\n  -h, --help         Show this help message\n  -s, --skip-notice  Skip notice about script being untested\n      --non-interactive\n                     Set default choice for file conflicts\n                        replace: Replace local     keep: Keep local         old:  Backup as .old\n                        new:     Save as .new      diff: Show diff          skip: Skip\n                        ignore:  Add to ignore     backup: Backup and replace\n\nThis script updates your dotfiles by:\n  1. Auto-detecting repository structure (dots/ prefix or direct)\n  2. Pulling latest changes from git remote\n  3. Optionally rebuilding packages (if -p flag is used)\n  4. Syncing configuration files to home directory\n  5. Updating script permissions\n\nIgnore file patterns support:\n  - Exact matches (e.g., 'path/to/file')\n  - Directory patterns (e.g., 'path/to/dir/')\n  - Wildcards (e.g., '*.log', 'path/*/file')\n  - Root-relative patterns (e.g., '/.config')\n  - Substring matching (prefix with '**', e.g., '**temp' matches any path containing 'temp')\n\"\n}\n# `man getopt` to see more\npara=$(getopt \\\n  -o hfpnvs \\\n  -l help,force,packages,dry-run,verbose,skip-notice,non-interactive,default-choice: \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\n#####################################################################################\n## getopt Phase 1\n# ignore parameter's order, execute options below first\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    --) break ;;\n    *) shift ;;\n  esac\ndone\n#####################################################################################\n## getopt Phase 2\n\nFORCE_CHECK=false\nCHECK_PACKAGES=false\nDRY_RUN=false\nVERBOSE=false\nSKIP_NOTICE=false\nNON_INTERACTIVE=false\nDEFAULT_CHOICE=\"\"\n\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    ## Ones without parameter\n    -f|--force) FORCE_CHECK=true;shift\n      log_info \"Force check mode enabled - will check all files regardless of git changes\"\n      ;;\n    -p|--packages) CHECK_PACKAGES=true;shift\n      log_info \"Package checking enabled\"\n      ;;\n    -n|--dry-run) DRY_RUN=true;shift\n      log_info \"Dry-run mode enabled - no changes will be made\"\n      ;;\n    -v|--verbose) VERBOSE=true;shift\n      log_info \"Verbose mode enabled\"\n      ;;\n    -s|--skip-notice) SKIP_NOTICE=true;shift\n      log_warning \"Skipping notice about script being untested\"\n      ;;\n    --non-interactive) NON_INTERACTIVE=true;shift\n      log_info \"Non-interactive mode enabled\"\n      ;;\n    --default-choice)\n      case \"$2\" in\n        replace) DEFAULT_CHOICE=\"1\" ;;\n        keep)    DEFAULT_CHOICE=\"2\" ;;\n        old)     DEFAULT_CHOICE=\"3\" ;;\n        new)     DEFAULT_CHOICE=\"4\" ;;\n        diff)    DEFAULT_CHOICE=\"5\" ;;\n        skip)    DEFAULT_CHOICE=\"6\" ;;\n        ignore)  DEFAULT_CHOICE=\"7\" ;;\n        backup)  DEFAULT_CHOICE=\"8\" ;;\n        *)\n          log_error \"Invalid --default-choice value: $2\"\n          log_error \"Valid values: replace, keep, old, new, diff, skip, ignore, backup\"\n          exit 1\n          ;;\n      esac\n      shift 2\n      log_info \"Default conflict choice set to: $DEFAULT_CHOICE\"\n      ;;\n    \n    ## Ending\n    --) break ;;\n    *) echo -e \"$0: Wrong parameters.\";exit 1;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/subcmd-install/0.greeting.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\n#####################################################################################\n\nprintf \"${STY_CYAN}[$0]: Hi there! Before we start:${STY_RST}\\n\"\nprintf \"\\n\"\nprintf \"${STY_PURPLE}${STY_BOLD}[NEW] illogical-impulse is now powered by Quickshell.${STY_RST}\\n\"\nprintf \"${STY_PURPLE}\"\nprintf '# NOTE: illogical-impulse on AGS is no longer supported.\\n'\nprintf '# If you were using the old version with AGS and would like to keep it, do not run this script.\\n'\nprintf \"\\n\"\npause\nprintf \"${STY_CYAN}${STY_BOLD}Quick overview about what this script does:${STY_RST}\\n\"\nprintf \"${STY_CYAN}\"\nprintf \"  1. Install dependencies.\\n\"\nprintf \"  2. Setup permissions/services etc.\\n\"\nprintf \"  3. Copying config files.${STY_RST}\\n\"\npause\nprintf \"${STY_CYAN}${STY_BOLD}Tips:${STY_RST}\\n\"\nprintf \"${STY_CYAN}\"\nprintf \"  a) It has been designed to be idempotent which means you can run it multiple times.\\n\"\nprintf \"  b) Use ${STY_INVERT} --help ${STY_RST}${STY_CYAN} for more options.${STY_RST}\\n\"\nprintf \"${STY_YELLOW}${STY_BOLD}Note: ${STY_RST}\"\nprintf \"${STY_YELLOW}\"\nprintf \"It does not handle system-level/hardware stuff like Nvidia drivers. Please do it by yourself.\\n\"\nprintf \"${STY_RST}\"\nprintf \"\\n\"\npause\n\ncase $ask in\n  false) sleep 0 ;;\n  *) \n    printf \"${STY_BLUE}\"\n    printf \"${STY_BOLD}Do you want to confirm every time before a command executes?${STY_RST}\\n\"\n    printf \"${STY_BLUE}\"\n    printf \"  y = Yes, ask me before executing each of them. (DEFAULT)\\n\"\n    printf \"  n = No, I know everything this script will do, just execute them automatically.\\n\"\n    printf \"  a = Abort.\\n\"\n    read -p \"===> [Y/n/a]: \" p\n    case $p in\n      n) ask=false ;;\n      a) exit 1 ;;\n      *) ask=true ;;\n    esac\n    printf \"${STY_RST}\"\n    ;;\nesac\n"
  },
  {
    "path": "sdata/subcmd-install/1.deps-router.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\nprintf \"${STY_CYAN}[$0]: 1. Install dependencies\\n${STY_RST}\"\n\nfunction outdate_detect(){\n  # Shallow clone prevent latest_commit_timestamp() from working.\n  x git_auto_unshallow 2>&1>/dev/null\n\n  local source_path=\"$1\"\n  local target_path=\"$2\"\n  local source_timestamp=\"$(latest_commit_timestamp $source_path 2>/dev/null)\"\n  local target_timestamp=\"$(latest_commit_timestamp $target_path 2>/dev/null)\"\n  local outdate_detect_mode=\"$(cat ${target_path}/outdate-detect-mode)\"\n\n  # outdate-detect-mode possible modes:\n  # - WIP: Work in progress (should be taken as outdated)\n  # - FORCE_OUTDATED: forcely taken as outdated\n  # - FORCE_UPDATED: forcely taken as updated\n  # - AUTO: Let the script decide automatically\n  #\n  # outdate status possible values:\n  # - WIP,FORCE_OUTDATED,FORCE_UPDATED: Inherited directly from outdate-detect-mode\n  # - EMPTY_SOURCE: source path has empty timestamp, maybe not tracked by git (should be taken as outdated)\n  # - EMPTY_TARGET: target path has empty timestamp, maybe not tracked by git (should be taken as outdated)\n  # - OUTDATED: target path is older than source path.\n  # - UPDATED: target path is not older than source path.\n\n  # Does target path have an outdate-detect-mode file which content is special?\n  if [[ \"${outdate_detect_mode}\" =~ ^(WIP|FORCE_OUTDATED|FORCE_UPDATED)$ ]]; then\n    echo \"${outdate_detect_mode}\"\n  # Does source path has an empty timestamp?\n  elif [ -z \"$source_timestamp\" ]; then\n    echo \"EMPTY_SOURCE\"\n  # Does target path has an empty timestamp?\n  elif [ -z \"$target_timestamp\" ]; then\n    echo \"EMPTY_TARGET\"\n  # If target path is older than source path, it's outdated.\n  elif [[ \"$target_timestamp\" -lt \"$source_timestamp\" ]]; then\n    echo \"OUTDATED\"\n  else\n    echo \"UPDATED\"\n  fi\n}\n#####################################################################################\n\nif [[ \"$INSTALL_VIA_NIX\" == \"true\" ]]; then\n\n  TARGET_ID=nix\n  printf \"${STY_YELLOW}\"\n  printf \"===WARNING===\\n\"\n  printf \"./sdata/dist-${TARGET_ID}/install-deps.sh will be used.\\n\"\n  printf \"The process is still WIP.\\n\"\n  printf \"Proceed only at your own risk.\\n\"\n  printf \"\\n\"\n  printf \"${STY_RST}\"\n  pause\n  source ./sdata/dist-${TARGET_ID}/install-deps.sh\n\nelif [[ \"$OS_GROUP_ID\" =~ ^(arch|gentoo|fedora)$ ]]; then\n\n  TARGET_ID=$OS_GROUP_ID\n  if ! [[ \"${TARGET_ID}\" = \"arch\" ]]; then\n    tmp_update_status=\"$(outdate_detect sdata/dist-arch sdata/dist-${TARGET_ID})\"\n    if [[ \"${tmp_update_status}\" =~ ^(OUTDATED|EMPTY_TARGET|EMPTY_SOURCE|FORCE_OUTDATED|WIP)$ ]]; then\n      printf \"${STY_RED}${STY_BOLD}===URGENT===${STY_RST}\\n\"\n      printf \"${STY_RED}\"\n      printf \"Status code: ${tmp_update_status}\\n\"\n      printf \"The community provided ./sdata/dist-${TARGET_ID}/ seems to be outdated,\\n\"\n      printf \"which means it probably does not reflect all latest changes of ./sdata/dist-arch/ .\\n\"\n      printf \"In such case it may work unexpectedly.${STY_RST}\\n\"\n      printf \"\\n\"\n      printf \"${STY_RED}It's highly recommended to check the following links before continue.${STY_RST}\\n\"\n      printf \"${STY_RED}1. Normally just check discussion#2140 to see if there's any valid update notice.${STY_RST}\\n\"\n      printf \"   ${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/discussions/2140${STY_RST}\\n\"\n      printf \"   ${STY_RED}Note that the timeliness relies on manual maintenance.${STY_RST}\\n\"\n      printf \"${STY_RED}2. For details please compare the two lists of commit history:${STY_RST}\\n\"\n      printf \"   ${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-arch${STY_RST}\\n\"\n      printf \"   ${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/commits/main/sdata/dist-${TARGET_ID}${STY_RST}\\n\"\n      printf \"\\n\"\n      printf \"${STY_PURPLE}PR on ./sdata/dist-${TARGET_ID}/ to properly reflect the latest changes of ./sdata/dist-arch is welcomed.${STY_RST}\\n\"\n      printf \"${STY_PURPLE}${STY_BOLD}Again, do not create any issue,${STY_RST}\\n\"\n      printf \"${STY_PURPLE}but you can create a discussion under \\\"Extra Distros\\\" category: ${STY_RST}\\n\"\n      printf \"${STY_PURPLE}${STY_UNDERLINE}https://github.com/end-4/dots-hyprland/discussions/new?category=extra-distros${STY_RST}\\n\"\n      printf \"\\n\"\n      if [[ \"${tmp_update_status}\" = \"OUTDATED\" ]]; then\n        printf \"${STY_RED}NOTE: The conclusion above is determined automatically by comparing latest Git commit time,\\n\"\n        printf \"however sometimes the changes on \\\"dist-arch\\\" are actually not needed for \\\"dist-${TARGET_ID}\\\",\\n\"\n        printf \"in such case you should just ignore it and continue.\\n\"\n        printf \"${STY_RST}\\n\"\n      fi\n      printf \"\\n\"\n      if ! [[ \"$IGNORE_OUTDATE_CHECK\" = \"true\" ]]; then\n        if [ \"$ask\" = \"false\" ]; then\n          printf \"${STY_RED}Urgent problem encountered, aborting...${STY_RST}\\n\";exit 1\n        else\n          printf \"${STY_RED}Still proceed?${STY_RST}\\n\"\n          read -p \"[y/N]: \" p\n          case \"$p\" in\n            [yY])sleep 0;;\n            *)echo \"Aborting...\";exit 1;;\n          esac\n        fi\n      fi\n    fi\n  fi\n  printf \"./sdata/dist-${TARGET_ID}/install-deps.sh will be used.\\n\"\n  source ./sdata/dist-${TARGET_ID}/install-deps.sh\nfi\n"
  },
  {
    "path": "sdata/subcmd-install/2.setups.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\nfunction prepare_systemd_user_service(){\n  if [[ ! -e \"/usr/lib/systemd/user/ydotool.service\" ]]; then\n    x sudo ln -s /usr/lib/systemd/{system,user}/ydotool.service\n  fi\n}\n\nfunction setup_user_group(){\n  if [[ -z $(getent group i2c) ]] && [[ \"$OS_GROUP_ID\" != \"fedora\" ]]; then\n    # On Fedora this is not needed. Tested with desktop computer with NVIDIA video card.\n    x sudo groupadd i2c\n  fi\n\n  if [[ \"$OS_GROUP_ID\" == \"fedora\" ]]; then\n    x sudo usermod -aG video,input \"$(whoami)\"\n  else\n    x sudo usermod -aG video,i2c,input \"$(whoami)\"\n  fi\n}\n#####################################################################################\n# These python packages are installed using uv into the venv (virtual environment). Once the folder of the venv gets deleted, they are all gone cleanly. So it's considered as setups, not dependencies.\nshowfun install-python-packages\nv install-python-packages\n\nshowfun setup_user_group\nv setup_user_group\n\nif [[ ! -z $(systemctl --version) ]]; then\n  # For Fedora, uinput is required for the virtual keyboard to function, and udev rules enable input group users to utilize it.\n  if [[ \"$OS_GROUP_ID\" == \"fedora\" ]]; then\n    v bash -c \"echo uinput | sudo tee /etc/modules-load.d/uinput.conf\"\n    v bash -c 'echo SUBSYSTEM==\\\"misc\\\", KERNEL==\\\"uinput\\\", MODE=\\\"0660\\\", GROUP=\\\"input\\\" | sudo tee /etc/udev/rules.d/99-uinput.rules'\n  else\n    v bash -c \"echo i2c-dev | sudo tee /etc/modules-load.d/i2c-dev.conf\"\n  fi\n  # TODO: find a proper way for enable Nix installed ydotool. When running `systemctl --user enable ydotool, it errors \"Failed to enable unit: Unit ydotool.service does not exist\".\n  if [[ ! \"${INSTALL_VIA_NIX}\" == true ]]; then\n    if [[ \"$OS_GROUP_ID\" == \"fedora\" ]]; then\n      v prepare_systemd_user_service\n    fi\n    # When $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR are empty, it commonly means that the current user has been logged in with `su - user` or `ssh user@hostname`. In such case `systemctl --user enable <service>` is not usable. It should be `sudo systemctl --machine=$(whoami)@.host --user enable <service>` instead.\n    if [[ ! -z \"${DBUS_SESSION_BUS_ADDRESS}\" ]]; then\n      v systemctl --user enable ydotool --now\n    else\n      v sudo systemctl --machine=$(whoami)@.host --user enable ydotool --now\n    fi\n  fi\n  v sudo systemctl enable bluetooth --now\nelif [[ ! -z $(openrc --version) ]]; then\n  v bash -c \"echo 'modules=i2c-dev' | sudo tee -a /etc/conf.d/modules\"\n  v sudo rc-update add modules boot\n  v sudo rc-update add ydotool default\n  v sudo rc-update add bluetooth default\n\n  x sudo rc-service ydotool start\n  x sudo rc-service bluetooth start\nelse\n  printf \"${STY_RED}\"\n  printf \"====================INIT SYSTEM NOT FOUND====================\\n\"\n  printf \"${STY_RST}\"\n  pause\nfi\n\nif [[ \"$OS_GROUP_ID\" == \"gentoo\" ]]; then\n  v sudo chown -R $(whoami):$(whoami) ~/.local/\nfi\n\nv gsettings set org.gnome.desktop.interface font-name 'Google Sans Flex Medium 11 @opsz=11,wght=500'\nv gsettings set org.gnome.desktop.interface color-scheme 'prefer-dark'\nv kwriteconfig6 --file kdeglobals --group KDE --key widgetStyle Darkly\n"
  },
  {
    "path": "sdata/subcmd-install/3.files-exp.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# See https://github.com/end-4/dots-hyprland/issues/2137\n#\n# Stage 1 todos:\n# TODO: Properly handle hyprland config, ~/.config/hypr/hyprland.conf should be overwritten only when firstrun\n# TODO: add --exp-files-path <path>   Use <path> instead of the default yaml config\n# TODO: add --exp-files-regen         Force copy the default config to ${EXP_FILE_PATH} (auto do this when not existed)\n# TODO: Implement versioning, i.e. when user-defined yaml config file has version number mismatch with the default one, produce error. If only minor version number is not the same, the error can be ommitted via --exp-file-no-strict .\n# TODO: add --exp-files-no-strict     Ignore error when minor version number is not the same\n# TODO: When --via-nix is specified, use dots-extra/vianix/hypridle.conf instead\n#\n# Stage 2 todos:\n# TODO: Implement bool key symlink (both read-write and read-only), when the value of `symlink` is true, then instead using `rsync` or `cp`, use `ln`.\n# TODO: add --exp-file-reset-symlink  Try to remove all symlink in .config and .local, which point to the local repo\n# TODO: Update help and doc about `--exp-files` and the yaml config, including the possible values of mode.\n#\n# Stage 3 todos:\n# TODO: Implement user-define yaml with merging (override) ability for user who only wants little customization and is satisfied with most of the defaults. User can use `./install-files.yaml` as custom config. When `./install-files.yaml` exists and have correct major version number, merge it together with `sdata/step/3.install-files.yaml` to generate a `cache/install-files.final.yaml` to determine how to copy files. About how to merge two yaml files, I know some software such as rime input method and docker supports a override yaml config, which we may reference from. See also https://github.com/mikefarah/yq/discussions/1437\n# TODO: Implement variants like keybindings, terminals, etc under user_preferences.\n\n# Configuration file\nCONFIG_FILE=\"sdata/subcmd-install/3.files-exp.yaml\"\n\n# =============================================================================\nwizard_update_preferences() {\n  echo -e \"${STY_CYAN}=== Dotfiles Customization ===${STY_RESET}\"\n\n    # Get current preferences\n    current_shell=$(yq '.user_preferences.shell // \"fish\"' \"$CONFIG_FILE\")\n    current_terminal=$(yq '.user_preferences.terminal // \"kitty\"' \"$CONFIG_FILE\")\n    current_keybindings=$(yq '.user_preferences.keybindings // \"default\"' \"$CONFIG_FILE\")\n\n    echo \"Current preferences:\"\n    echo \"  Shell: $current_shell\"\n    echo \"  Terminal: $current_terminal\"\n    echo \"  Keybindings: $current_keybindings\"\n    echo\n\n    # Shell selection\n    echo \"Which shell do you prefer?\"\n    echo \"1) fish (default)\"\n    echo \"2) zsh\"\n    read -p \"Enter choice [1-2]: \" shell_choice\n\n    case \"$shell_choice\" in\n      1|\"\") shell=\"fish\" ;;\n      2) shell=\"zsh\" ;;\n      *) echo \"Invalid choice, using fish\"; shell=\"fish\" ;;\n    esac\n\n    # Terminal selection\n    echo\n    echo \"Which terminal do you prefer?\"\n    echo \"1) kitty (default)\"\n    echo \"2) foot\"\n    read -p \"Enter choice [1-2]: \" terminal_choice\n\n    case \"$terminal_choice\" in\n      1|\"\") terminal=\"kitty\" ;;\n      2) terminal=\"foot\" ;;\n      *) echo \"Invalid choice, using kitty\"; terminal=\"kitty\" ;;\n    esac\n\n    # Keybindings selection\n    echo\n    echo \"Which keybinding style do you prefer?\"\n    echo \"1) default (arrow keys)\"\n    echo \"2) vim (H/J/K/L)\"\n    read -p \"Enter choice [1-2]: \" keybind_choice\n\n    case \"$keybind_choice\" in\n      1|\"\") keybindings=\"default\" ;;\n      2) keybindings=\"vim\" ;;\n      *) echo \"Invalid choice, using default\"; keybindings=\"default\" ;;\n    esac\n\n    # Update YAML in-place\n    yq -i \".user_preferences.shell = \\\"$shell\\\"\" \"$CONFIG_FILE\"\n    yq -i \".user_preferences.terminal = \\\"$terminal\\\"\" \"$CONFIG_FILE\"\n    yq -i \".user_preferences.keybindings = \\\"$keybindings\\\"\" \"$CONFIG_FILE\"\n\n    echo\n    echo \"Preferences updated!\"\n  }\n\n# Get user preference\nget_pref() {\n  yq -r \".user_preferences.$1\" \"$CONFIG_FILE\"\n}\n\n# Check if pattern should be processed based on user preferences\nshould_process_pattern() {\n  local pattern=\"$1\"\n  local condition=$(echo \"$pattern\" | yq '.condition // \"true\"')\n\n    # If no condition or condition is \"true\", always process\n    if [[ \"$condition\" == \"true\" ]]; then\n      return 0\n    fi\n\n    # Extract the preference type and value from condition\n    local type=$(echo \"$condition\" | yq '.type')\n    local value=$(echo \"$condition\" | yq '.value')\n\n    [[ \"$(get_pref \"$type\")\" == \"$value\" ]]\n\n  }\n\n# Compare hashes of files/directories, return true if they are the same, false otherwise\nfiles_are_same() {\n  local path1=\"$1\"\n  local path2=\"$2\"\n\n    # Check if paths exist\n    if [[ ! -e \"$path1\" || ! -e \"$path2\" ]]; then\n      return 1\n    fi\n\n    # For directories, use find + md5sum to compare recursively\n    # For files, use md5sum directly\n    if [[ -d \"$path1\" && -d \"$path2\" ]]; then\n      # Compare directory contents using find and md5sum\n      local hash1=$(find \"$path1\" -type f -exec md5sum {} \\; | sort -k 2 | md5sum | awk '{print $1}')\n      local hash2=$(find \"$path2\" -type f -exec md5sum {} \\; | sort -k 2 | md5sum | awk '{print $1}')\n      [[ \"$hash1\" == \"$hash2\" ]]\n    elif [[ -f \"$path1\" && -f \"$path2\" ]]; then\n      # Compare file hashes\n      local hash1=$(md5sum \"$path1\" | awk '{print $1}')\n      local hash2=$(md5sum \"$path2\" | awk '{print $1}')\n      [[ \"$hash1\" == \"$hash2\" ]]\n    else\n      # One is a file, one is a directory - different types\n      return 1\n    fi\n  }\n\n# Find next backup number\nget_next_backup_number() {\n  local base_path=\"$1\"\n  local counter=1\n\n  while [[ -e \"${base_path}.old.${counter}\" ]]; do\n    ((counter++))\n  done\n\n  echo $counter\n}\n\n# =============================================================================\n# MAIN EXECUTION\n# =============================================================================\n\n# Run user preference wizard\ncase \"$ask\" in\n  false) sleep 0 ;;\n  *) wizard_update_preferences ;;\nesac\n\n# Read patterns from YAML file\nreadarray patterns < <(yq -o=j -I=0 '.patterns[]' \"$CONFIG_FILE\")\n\n# Process each pattern\nfor pattern in \"${patterns[@]}\"; do\n  from=$(echo \"$pattern\" | yq '.from' - | envsubst)\n  to=$(echo \"$pattern\" | yq '.to' - | envsubst)\n  mode=$(echo \"$pattern\" | yq '.mode' - | envsubst)\n  condition=$(echo \"$pattern\" | yq '.condition // \"true\"')\n\n  # Handle fontconfig fontset override\n  # If FONTSET_DIR_NAME is set and this is the fontconfig pattern, use the fontset instead\n  if [[ \"$from\" == \"dots/.config/fontconfig\" ]] && [[ -n \"${FONTSET_DIR_NAME:-}\" ]]; then\n    from=\"dots-extra/fontsets/${FONTSET_DIR_NAME}\"\n    echo \"Using fontset \\\"${FONTSET_DIR_NAME}\\\" for fontconfig\"\n  fi\n\n  # Check if pattern should be processed\n  if ! should_process_pattern \"$pattern\"; then\n    # Format condition message nicely\n    if [[ \"$condition\" != \"true\" ]]; then\n      cond_type=$(echo \"$condition\" | yq -r '.type // \"\"')\n      cond_value=$(echo \"$condition\" | yq -r '.value // \"\"')\n      if [[ -n \"$cond_type\" && -n \"$cond_value\" ]]; then\n        echo \"Skipping $from -> $to (condition not met: $cond_type == '$cond_value')\"\n      else\n        echo \"Skipping $from -> $to (condition not met)\"\n      fi\n    else\n      echo \"Skipping $from -> $to (condition not met)\"\n    fi\n    continue\n  fi\n\n  echo \"Processing: $from -> $to (mode: $mode)\"\n\n  # Build exclude arguments for rsync\n  excludes=()\n  if echo \"$pattern\" | yq -e '.excludes' >/dev/null 2>&1; then\n    while IFS= read -r exclude; do\n      excludes+=(--exclude \"$exclude\")\n    done < <(echo \"$pattern\" | yq -r '.excludes[]')\n  fi\n\n  # Check if source exists\n  if [[ ! -e \"$from\" ]]; then\n    echo \"Warning: Source does not exist: $from (skipping)\"\n    continue\n  fi\n\n  # Ensure destination directory exists for files\n  if [[ -f \"$from\" ]]; then\n    v mkdir -p \"$(dirname \"$to\")\"\n  fi\n\n  # Execute based on mode\n  case \"$mode\" in\n    \"sync\")\n      if [[ -d \"$from\" ]]; then\n        warning_overwrite\n        v rsync -av --delete \"${excludes[@]}\" \"$from/\" \"$to/\"\n      else\n        warning_overwrite\n        # For files, don't use trailing slash and don't use --delete\n        v rsync -av \"${excludes[@]}\" \"$from\" \"$to\"\n      fi\n      ;;\n    \"soft\")\n      warning_overwrite\n      if [[ -d \"$from\" ]]; then\n        v rsync -av \"${excludes[@]}\" \"$from/\" \"$to/\"\n      else\n        # For files, don't use trailing slash\n        v rsync -av \"${excludes[@]}\" \"$from\" \"$to\"\n      fi\n      ;;\n    \"hard\")\n      v cp -r \"$from\" \"$to\"\n      ;;\n    \"hard-backup\")\n      if [[ -e \"$to\" ]]; then\n        if files_are_same \"$from\" \"$to\"; then\n          echo \"Files are identical, skipping backup\"\n        else\n          backup_number=$(get_next_backup_number \"$to\")\n          v mv \"$to\" \"$to.old.$backup_number\"\n          v cp -r \"$from\" \"$to\"\n        fi\n      else\n        v cp -r \"$from\" \"$to\"\n      fi\n      ;;\n    \"soft-backup\")\n      if [[ -e \"$to\" ]]; then\n        if files_are_same \"$from\" \"$to\"; then\n          echo \"Files are identical, skipping backup\"\n        else\n          v cp -r \"$from\" \"$to.new\"\n        fi\n      else\n        v cp -r \"$from\" \"$to\"\n      fi\n      ;;\n    \"skip\")\n      echo \"Skipping $from\"\n      ;;\n    \"skip-if-exists\")\n      if [[ -e \"$to\" ]]; then\n        echo \"Skipping $from (destination exists)\"\n      else\n        v cp -r \"$from\" \"$to\"\n      fi\n      ;;\n    *)\n      echo \"Unknown mode: $mode\"\n      ;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/subcmd-install/3.files-exp.yaml",
    "content": "# The possible values of `mode`: (by default `sync`)\n# - `sync`: Make the destination completely the same as the source.\n# - `soft`: Skip existing files when copying\n# - `hard`: Overwrite existing files when copying\n# - `soft-backup`: If target file exists, copy source file to `*.new`. Do not create `*.new` but skip coyping when source and target HASH values are exactly the same.\n# - `hard-backup`: If target file exists, create backup by renaming it to `*.old.<n>` where `<n>` is a number increment from 1 to prevent backup gets overwritten when running twice. (Keep in mind that this script must be idempotent.) Also must compare the actual file content by using MD5 HASH value to prevent generating lots of `*.old.<n>`s when runnng multiple times. Do not create backup but skip coyping when source and target HASH values are exactly the same.\n# - `skip`: Skip this step\n# - `skip-if-exists`: Skip this step if target exists\nversion: \"1.0\"\nuser_preferences:\n  shell: \"fish\" # fish | zsh\n  terminal: \"foot\" # kitty | foot\n  keybindings: \"default\" # default | vim\npatterns:\n  # Always install these files\n  - from: \"dots/.config/quickshell/ii\"\n    to: \"$XDG_CONFIG_HOME/quickshell/ii\"\n    mode: \"sync\"\n  # Conditionally install these files\n  - from: \"dots/.config/fish\"\n    to: \"$XDG_CONFIG_HOME/fish\"\n    mode: \"sync\"\n    excludes: [\"conf.d\"]\n    condition:\n      type: \"shell\"\n      value: \"fish\"\n  - from: \"dots/.config/zshrc.d\"\n    to: \"$XDG_CONFIG_HOME/zshrc.d\"\n    mode: \"sync\"\n    condition:\n      type: \"shell\"\n      value: \"zsh\"\n  - from: \"dots/.config/foot\"\n    to: \"$XDG_CONFIG_HOME/foot\"\n    mode: \"sync\"\n    condition:\n      type: \"terminal\"\n      value: \"foot\"\n  - from: \"dots/.config/kitty\"\n    to: \"$XDG_CONFIG_HOME/kitty\"\n    mode: \"sync\"\n    condition:\n      type: \"terminal\"\n      value: \"kitty\"\n  # Hyprland    \n  - from: \"dots/.config/hypr\"\n    to: \"$XDG_CONFIG_HOME/hypr\"\n    mode: \"sync\"\n    excludes: [\"custom\", \"hyprlock.conf\", \"hypridle.conf\"]\n  # Hyprland special files\n  - from: \"dots/.config/hypr/hypridle.conf\"\n    to: \"$XDG_CONFIG_HOME/hypr/hypridle.conf\"\n    mode: \"soft-backup\"\n  - from: \"dots/.config/hypr/hyprlock.conf\"\n    to: \"$XDG_CONFIG_HOME/hypr/hyprlock.conf\"\n    mode: \"soft-backup\"\n  - from: \"dots/.config/hypr/custom\"\n    to: \"$XDG_CONFIG_HOME/hypr/custom\"\n    mode: \"skip-if-exists\"\n  - from: \"dots/.local/share/icons\"\n    to: \"$XDG_DATA_HOME/icons\"\n    mode: \"soft\"\n  - from: \"dots/.local/share/konsole\"\n    to: \"$XDG_DATA_HOME/konsole\"\n    mode: \"soft\"\n  # Fontconfig (default - fontsets handled separately if FONTSET_DIR_NAME is set)\n  - from: \"dots/.config/fontconfig\"\n    to: \"$XDG_CONFIG_HOME/fontconfig\"\n    mode: \"sync\"\n  # MISC config directories (other .config directories)\n  - from: \"dots/.config/fuzzel\"\n    to: \"$XDG_CONFIG_HOME/fuzzel\"\n    mode: \"sync\"\n  - from: \"dots/.config/kde-material-you-colors\"\n    to: \"$XDG_CONFIG_HOME/kde-material-you-colors\"\n    mode: \"sync\"\n  - from: \"dots/.config/Kvantum\"\n    to: \"$XDG_CONFIG_HOME/Kvantum\"\n    mode: \"sync\"\n  - from: \"dots/.config/matugen\"\n    to: \"$XDG_CONFIG_HOME/matugen\"\n    mode: \"sync\"\n  - from: \"dots/.config/mpv\"\n    to: \"$XDG_CONFIG_HOME/mpv\"\n    mode: \"sync\"\n  - from: \"dots/.config/qt5ct\"\n    to: \"$XDG_CONFIG_HOME/qt5ct\"\n    mode: \"sync\"\n  - from: \"dots/.config/qt6ct\"\n    to: \"$XDG_CONFIG_HOME/qt6ct\"\n    mode: \"sync\"\n  - from: \"dots/.config/wlogout\"\n    to: \"$XDG_CONFIG_HOME/wlogout\"\n    mode: \"sync\"\n  - from: \"dots/.config/xdg-desktop-portal\"\n    to: \"$XDG_CONFIG_HOME/xdg-desktop-portal\"\n    mode: \"sync\"\n  # MISC config files (individual files in .config)\n  - from: \"dots/.config/chrome-flags.conf\"\n    to: \"$XDG_CONFIG_HOME/chrome-flags.conf\"\n    mode: \"soft\"\n  - from: \"dots/.config/code-flags.conf\"\n    to: \"$XDG_CONFIG_HOME/code-flags.conf\"\n    mode: \"soft\"\n  - from: \"dots/.config/darklyrc\"\n    to: \"$XDG_CONFIG_HOME/darklyrc\"\n    mode: \"soft\"\n  - from: \"dots/.config/dolphinrc\"\n    to: \"$XDG_CONFIG_HOME/dolphinrc\"\n    mode: \"soft\"\n  - from: \"dots/.config/kdeglobals\"\n    to: \"$XDG_CONFIG_HOME/kdeglobals\"\n    mode: \"soft\"\n  - from: \"dots/.config/konsolerc\"\n    to: \"$XDG_CONFIG_HOME/konsolerc\"\n    mode: \"soft\"\n  - from: \"dots/.config/starship.toml\"\n    to: \"$XDG_CONFIG_HOME/starship.toml\"\n    mode: \"soft\"\n  - from: \"dots/.config/thorium-flags.conf\"\n    to: \"$XDG_CONFIG_HOME/thorium-flags.conf\"\n    mode: \"soft\"\n"
  },
  {
    "path": "sdata/subcmd-install/3.files-legacy.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\n#####################################################################################\n# MISC (For dots/.config/* but not quickshell, not fish, not Hyprland, not fontconfig)\ncase \"${SKIP_MISCCONF}\" in\n  true) sleep 0;;\n  *)\n    for i in $(find dots/.config/ -mindepth 1 -maxdepth 1 ! -name 'quickshell' ! -name 'fish' ! -name 'hypr' ! -name 'fontconfig' -exec basename {} \\;); do\n#      i=\"dots/.config/$i\"\n      echo \"[$0]: Found target: dots/.config/$i\"\n      if [ -d \"dots/.config/$i\" ];then install_dir__sync \"dots/.config/$i\" \"$XDG_CONFIG_HOME/$i\"\n      elif [ -f \"dots/.config/$i\" ];then install_file \"dots/.config/$i\" \"$XDG_CONFIG_HOME/$i\"\n      fi\n    done\n    install_dir \"dots/.local/share/konsole\" \"${XDG_DATA_HOME}\"/konsole\n    ;;\nesac\n\ncase \"${SKIP_QUICKSHELL}\" in\n  true) sleep 0;;\n  *)\n     # Should overwriting the whole directory not only ~/.config/quickshell/ii/ cuz https://github.com/end-4/dots-hyprland/issues/2294#issuecomment-3448671064\n    install_dir__sync dots/.config/quickshell \"$XDG_CONFIG_HOME\"/quickshell\n    ;;\nesac\n\ncase \"${SKIP_FISH}\" in\n  true) sleep 0;;\n  *)\n    install_dir__sync_exclude dots/.config/fish \"$XDG_CONFIG_HOME\"/fish \"conf.d\"\n    ;;\nesac\n\ncase \"${SKIP_FONTCONFIG}\" in\n  true) sleep 0;;\n  *)\n    case \"$FONTSET_DIR_NAME\" in\n      \"\") install_dir__sync dots/.config/fontconfig \"$XDG_CONFIG_HOME\"/fontconfig ;;\n      *) install_dir__sync dots-extra/fontsets/$FONTSET_DIR_NAME \"$XDG_CONFIG_HOME\"/fontconfig ;;\n    esac;;\nesac\n\n# For Hyprland\ncase \"${SKIP_HYPRLAND}\" in\n  true) sleep 0;;\n  *)\n    install_dir__sync dots/.config/hypr/hyprland \"$XDG_CONFIG_HOME\"/hypr/hyprland\n    if [ -f \"${XDG_CONFIG_HOME}/hypr/hyprland.conf\" ]; then\n      mv \"${XDG_CONFIG_HOME}/hypr/hyprland.conf\" \"${XDG_CONFIG_HOME}/hypr/hyprland.conf.old\" # disable old config\n      echo 'hyprland.conf has been renamed to hyprland.conf.old. This is to allow the new lua config to load.'\n    fi\n    for i in hyprlock.conf ; do\n      install_file__auto_backup \"dots/.config/hypr/$i\" \"${XDG_CONFIG_HOME}/hypr/$i\"\n    done\n    for i in hyprland.lua ; do\n      case \"${SKIP_HYPRLAND_ENTRY}\" in\n        true) sleep 0;;\n        *) install_file \"dots/.config/hypr/$i\" \"${XDG_CONFIG_HOME}/hypr/$i\" ;;\n      esac\n    done\n    for i in hypridle.conf ; do\n      if [[ \"${INSTALL_VIA_NIX}\" == true ]]; then\n        install_file__auto_backup \"dots-extra/via-nix/$i\" \"${XDG_CONFIG_HOME}/hypr/$i\"\n      else\n        install_file__auto_backup \"dots/.config/hypr/$i\" \"${XDG_CONFIG_HOME}/hypr/$i\"\n      fi\n    done\n    if [ \"$OS_GROUP_ID\" = \"fedora\" ];then\n      v bash -c \"printf \\\"# For fedora to setup polkit\\nexec-once = /usr/libexec/kf6/polkit-kde-authentication-agent-1\\n\\\" >> ${XDG_CONFIG_HOME}/hypr/hyprland/execs.conf\"\n    fi\n\n    install_dir__ignore_existing \"dots/.config/hypr/custom\" \"${XDG_CONFIG_HOME}/hypr/custom\"\n    ;;\nesac\n\ninstall_file \"dots/.local/share/icons/illogical-impulse.svg\" \"${XDG_DATA_HOME}\"/icons/illogical-impulse.svg\n"
  },
  {
    "path": "sdata/subcmd-install/3.files.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\nprintf \"${STY_CYAN}[$0]: 3. Copying config files\\n${STY_RST}\"\n\n# shellcheck shell=bash\n\nfunction warning_overwrite(){\n  printf \"${STY_YELLOW}\"\n  printf \"The command below overwrites the destination.\\n\"\n  printf \"${STY_RST}\"\n}\nfunction auto_backup_configs(){\n  local backup=false\n  case $ask in\n    false) if [[ ! -d \"$BACKUP_DIR\" ]]; then local backup=true;fi;;\n    *)\n      printf \"${STY_RED}\"\n      printf \"Would you like to backup clashing dirs/files to \\\"$BACKUP_DIR\\\"?\\n\"\n      printf \"${STY_RST}\"\n      while true;do\n        echo \"  y = Yes, backup\"\n        echo \"  n/s = No, skip to next\"\n        local p; read -p \"====> \" p\n        case $p in\n          [yY]) echo -e \"${STY_BLUE}OK, doing backup...${STY_RST}\"\n            local backup=true;break ;;\n          [nNsS]) echo -e \"${STY_BLUE}Alright, skipping...${STY_RST}\"\n            local backup=false;break ;;\n          *) echo -e \"${STY_RED}Please enter [y/n/s].${STY_RST}\";;\n        esac\n      done\n      ;;\n  esac\n  if $backup;then\n    backup_clashing_targets dots/.config $XDG_CONFIG_HOME \"${BACKUP_DIR}/.config\"\n    backup_clashing_targets dots/.local/share $XDG_DATA_HOME \"${BACKUP_DIR}/.local/share\"\n    printf \"${STY_BLUE}Backup into \\\"${BACKUP_DIR}\\\" finished.${STY_RST}\\n\"\n  fi\n}\nfunction gen_firstrun(){\n  x mkdir -p \"$(dirname ${FIRSTRUN_FILE})\"\n  x touch \"${FIRSTRUN_FILE}\"\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  realpath -se \"${FIRSTRUN_FILE}\" >> \"${INSTALLED_LISTFILE}\"\n}\ncp_file(){\n  # NOTE: This function is only for using in other functions\n  x mkdir -p \"$(dirname $2)\"\n  x cp -f \"$1\" \"$2\"\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  realpath -se \"$2\" >> \"${INSTALLED_LISTFILE}\"\n}\nrsync_dir(){\n  # NOTE: This function is only for using in other functions\n  x mkdir -p \"$2\"\n  local dest=\"$(realpath -se $2)\"\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  rsync -a --out-format='%i %n' \"$1\"/ \"$2\"/ | awk -v d=\"$dest\" '$1 ~ /^>/{ sub(/^[^ ]+ /,\"\"); printf d \"/\" $0 \"\\n\" }' >> \"${INSTALLED_LISTFILE}\"\n}\nrsync_dir__ignore_existing(){\n  # NOTE: This function is only for using in other functions\n  x mkdir -p \"$2\"\n  local dest=\"$(realpath -se $2)\"\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  rsync -a --ignore-existing --out-format='%i %n' \"$1\"/ \"$2\"/ | awk -v d=\"$dest\" '$1 ~ /^>/{ sub(/^[^ ]+ /,\"\"); printf d \"/\" $0 \"\\n\" }' >> \"${INSTALLED_LISTFILE}\"\n}\nrsync_dir__sync(){\n  # NOTE: This function is only for using in other functions\n  # `--delete' for rsync to make sure that\n  # original dotfiles and new ones in the SAME DIRECTORY\n  # (eg. in ~/.config/hypr) won't be mixed together\n  x mkdir -p \"$2\"\n  local dest=\"$(realpath -se $2)\"\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  rsync -a --delete --out-format='%i %n' \"$1\"/ \"$2\"/ | awk -v d=\"$dest\" '$1 ~ /^>/{ sub(/^[^ ]+ /,\"\"); printf d \"/\" $0 \"\\n\" }' >> \"${INSTALLED_LISTFILE}\"\n}\nrsync_dir__sync_exclude(){\n  # NOTE: This function is only for using in other functions\n  # Same as rsync_dir__sync but with exclude patterns support\n  # Usage: rsync_dir__sync_exclude <src> <dest> <exclude_pattern1> [<exclude_pattern2> ...]\n  local src=\"$1\"\n  local dest_dir=\"$2\"\n  shift 2\n  local excludes=()\n  for pattern in \"$@\"; do\n    excludes+=(--exclude \"$pattern\")\n  done\n  x mkdir -p \"$dest_dir\"\n  local dest=\"$(realpath -se $dest_dir)\"\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  rsync -a --delete \"${excludes[@]}\" --out-format='%i %n' \"$src\"/ \"$dest_dir\"/ | awk -v d=\"$dest\" '$1 ~ /^>/{ sub(/^[^ ]+ /,\"\"); printf d \"/\" $0 \"\\n\" }' >> \"${INSTALLED_LISTFILE}\"\n}\nfunction install_file(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  local s=$1\n  local t=$2\n  if [ -f $t ];then\n    warning_overwrite\n  fi\n  v cp_file $s $t\n}\nfunction install_file__auto_backup(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  local s=$1\n  local t=$2\n  if [ -f $t ];then\n    echo -e \"${STY_YELLOW}[$0]: \\\"$t\\\" already exists.${STY_RST}\"\n    if ${INSTALL_FIRSTRUN};then\n      echo -e \"${STY_BLUE}[$0]: It seems to be the firstrun.${STY_RST}\"\n      v mv $t $t.old\n      v cp_file $s $t\n    else\n      echo -e \"${STY_BLUE}[$0]: It seems not a firstrun.${STY_RST}\"\n      v cp_file $s $t.new\n    fi\n  else\n    echo -e \"${STY_GREEN}[$0]: \\\"$t\\\" does not exist yet.${STY_RST}\"\n    v cp_file $s $t\n  fi\n}\nfunction install_dir(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  local s=$1\n  local t=$2\n  if [ -d $t ];then\n    warning_overwrite\n  fi\n  v rsync_dir $s $t\n}\nfunction install_dir__sync(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  local s=$1\n  local t=$2\n  if [ -d $t ];then\n    warning_overwrite\n  fi\n  v rsync_dir__sync $s $t\n}\nfunction install_dir__skip_ifexist(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  local s=$1\n  local t=$2\n  if [ -d $t ];then\n    echo -e \"${STY_BLUE}[$0]: \\\"$t\\\" already exists, will not do anything.${STY_RST}\"\n  else\n    echo -e \"${STY_YELLOW}[$0]: \\\"$t\\\" does not exist yet.${STY_RST}\"\n    v rsync_dir $s $t\n  fi\n}\nfunction install_dir__ignore_existing(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  local s=$1\n  local t=$2\n  if [ -d $t ];then\n    echo -e \"${STY_BLUE}[$0]: \\\"$t\\\" already exists, will not do anything.${STY_RST}\"\n  else\n    echo -e \"${STY_YELLOW}[$0]: \\\"$t\\\" does not exist yet.${STY_RST}\"\n    v rsync_dir__ignore_existing $s $t\n  fi\n}\nfunction install_dir__sync_exclude(){\n  # NOTE: Do not add prefix `v` or `x` when using this function\n  # Sync directory with exclude patterns\n  # Usage: install_dir__sync_exclude <src> <dest> <exclude_pattern1> [<exclude_pattern2> ...]\n  local s=$1\n  local t=$2\n  shift 2\n  if [ -d $t ];then\n    warning_overwrite\n  fi\n  v rsync_dir__sync_exclude $s $t \"$@\"\n}\nfunction install_google_sans_flex(){\n  local font_name=\"Google Sans Flex\"\n  local src_name=\"google-sans-flex\"\n  local src_url=\"https://github.com/end-4/google-sans-flex\"\n  local src_dir=\"$REPO_ROOT/cache/$src_name\"\n  local target_dir=\"${XDG_DATA_HOME}/fonts/illogical-impulse-$src_name\"\n  if fc-list | grep -qi \"$font_name\"; then return; fi\n  x mkdir -p $src_dir\n  x cd $src_dir\n  try git init -b main\n  try git remote add origin $src_url\n  x git pull origin main \n  x git submodule update --init --recursive\n  warning_overwrite\n  rsync_dir \"$src_dir\" \"$target_dir\" \n  x fc-cache -fv\n  x cd $REPO_ROOT\n  x mkdir -p \"$(dirname ${INSTALLED_LISTFILE})\"\n  realpath -se \"$target_dir\" >> \"${INSTALLED_LISTFILE}\"\n}\n\n#####################################################################################\n# In case some dirs does not exists\nfor i in \"$XDG_BIN_HOME\" \"$XDG_CACHE_HOME\" \"$XDG_CONFIG_HOME\" \"$XDG_DATA_HOME\"; do\n  if ! test -e \"$i\"; then\n    v mkdir -p \"$i\"\n  fi\ndone\ncase \"${INSTALL_FIRSTRUN}\" in\n  # When specify --firstrun\n  true) sleep 0 ;;\n  # When not specify --firstrun\n  *)\n    if test -f \"${FIRSTRUN_FILE}\"; then\n      INSTALL_FIRSTRUN=false\n    else\n      INSTALL_FIRSTRUN=true\n    fi\n    ;;\nesac\n\n\nshowfun auto_update_git_submodule\nv auto_update_git_submodule\n\n# Backup\nif [[ ! \"${SKIP_BACKUP}\" == true ]]; then auto_backup_configs; fi\n\ncase \"${EXPERIMENTAL_FILES_SCRIPT}\" in\n  true)source sdata/subcmd-install/3.files-exp.sh;;\n  *)source sdata/subcmd-install/3.files-legacy.sh;;\nesac\n\nif [[ ! \"$OS_GROUP_ID\" == \"fedora\" ]]; then\n  showfun install_google_sans_flex\n  v install_google_sans_flex\nfi\n\n#####################################################################################\n\nv gen_firstrun\nv dedup_and_sort_listfile \"${INSTALLED_LISTFILE}\" \"${INSTALLED_LISTFILE}\"\n\n# Prevent hyprland from not fully loaded\nsleep 1\ntry hyprctl reload\n\n#####################################################################################\nprintf \"\\n\"\nprintf \"\\n\"\nprintf \"\\n\"\nprintf \"${STY_CYAN}[$0]: Finished${STY_RST}\\n\"\nprintf \"\\n\"\nprintf \"${STY_CYAN}When starting Hyprland from your display manager (login screen) ${STY_RED} DO NOT SELECT UWSM ${STY_RST}\\n\"\nprintf \"\\n\"\nprintf \"${STY_CYAN}If you are already running Hyprland,${STY_RST}\\n\"\nprintf \"${STY_CYAN}Press ${STY_INVERT} Ctrl+Super+T ${STY_RST}${STY_CYAN} to select a wallpaper${STY_RST}\\n\"\nprintf \"${STY_CYAN}Press ${STY_INVERT} Super+/ ${STY_RST}${STY_CYAN} for a list of keybinds${STY_RST}\\n\"\nprintf \"\\n\"\nprintf \"${STY_CYAN}For suggestions/hints after installation:${STY_RST}\\n\"\nprintf \"${STY_CYAN}${STY_UNDERLINE} https://ii.clsty.link/en/ii-qs/01setup/#post-installation ${STY_RST}\\n\"\nprintf \"\\n\"\n\nif [[ -z \"${ILLOGICAL_IMPULSE_VIRTUAL_ENV}\" ]]; then\n  printf \"\\n${STY_RED}[$0]: \\!! Important \\!! : Please ensure environment variable ${STY_RST} \\$ILLOGICAL_IMPULSE_VIRTUAL_ENV ${STY_RED} is set to proper value (by default \\\"~/.local/state/quickshell/.venv\\\"), or Quickshell config will not work. We have already provided this configuration in ~/.config/hypr/hyprland/env.conf, but you need to ensure it is included in hyprland.conf, and also a restart is needed for applying it.${STY_RST}\\n\"\nfi\n"
  },
  {
    "path": "sdata/subcmd-install/options.sh",
    "content": "# Handle args for subcmd: install\n# shellcheck shell=bash\nshowhelp(){\nprintf \"Syntax: $0 install [OPTIONS]...\n\nIdempotent installation for dotfiles.\n\nOptions for install:\n  -h, --help                Print this help message and exit\n  -f, --force               (Dangerous) Force mode without any confirm\n  -F, --fisrtrun            Act like it is the first run\n  -c, --clean               Clean the build cache first\n      --skip-allgreeting    Skip the whole process greeting\n      --skip-alldeps        Skip the whole process installing dependency\n      --skip-allsetups      Skip the whole process setting up permissions/services etc\n      --skip-allfiles       Skip the whole process copying configuration files\n      --ignore-outdate      Ignore outdate checking for community supported \\\"dist-*\\\".\n  -s, --skip-sysupdate      Skip system package upgrade e.g. \\\"sudo pacman -Syu\\\"\n      --skip-plasmaintg     Skip installing plasma-browser-integration\n      --skip-backup         Skip backup conflicting files\n      --skip-quickshell     Skip installing the config for Quickshell\n      --skip-hyprland       Skip installing the config for Hyprland\n      --skip-hyprland-entry Skip installing the entry config for Hyprland\n      --skip-fish           Skip installing the config for Fish\n      --skip-fontconfig     Skip installing the config for fontconfig\n      --skip-miscconf       Skip copying the dirs and files to \\\".configs\\\" except for\n                            Quickshell, Fish and Hyprland\n      --core                Alias of --skip-{plasmaintg,fish,miscconf,fontconfig}\n      --fontset <set>       Use a set of pre-defined font and config (currently only fontconfig).\n                            Possible values of <set>: $(ls -A ${REPO_ROOT}/dots-extra/fontsets)\n${STY_CYAN}\nNew features (experimental):\n      --exp-files             Use yaml-based config for the third step copying files.\n                              This feature is ${STY_YELLOW}still on early stage${STY_CYAN},\n                              feedback and contribution welcomed,\n                              see https://github.com/end-4/dots-hyprland/issues/2137 for details.\n      --via-nix               Use Nix and Home-manager to install dependencies.\n                              This feature is ${STY_RED}working in progress${STY_CYAN}. Contribution is welcomed,\n                              see https://github.com/end-4/dots-hyprland/issues/1061 for details.\n${STY_RST}\"\n}\n\ncleancache(){\n  rm -rf \"${REPO_ROOT}/cache\"\n}\n\n# `man getopt` to see more\npara=$(getopt \\\n  -o hfFk:cs \\\n  -l help,force,firstrun,fontset:,clean,skip-allgreeting,skip-alldeps,skip-allsetups,skip-allfiles,ignore-outdate,skip-sysupdate,skip-plasmaintg,skip-backup,skip-quickshell,skip-fish,skip-hyprland,skip-hyprland-entry,skip-fontconfig,skip-miscconf,core,exp-files,via-nix \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\n#####################################################################################\n## getopt Phase 1\n# ignore parameter's order, execute options below first\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    -c|--clean) cleancache;shift;;\n    --) shift;break ;;\n    *) shift ;;\n  esac\ndone\n#####################################################################################\n## getopt Phase 2\n\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    ## Already processed in phase 1, but not exited\n    -c|--clean) shift;;\n    ## Ones without parameter\n    -f|--force) ask=false;shift;;\n    -F|--firstrun) INSTALL_FIRSTRUN=true;shift;;\n    --skip-allgreeting) SKIP_ALLGREETING=true;shift;;\n    --skip-alldeps) SKIP_ALLDEPS=true;shift;;\n    --skip-allsetups) SKIP_ALLSETUPS=true;shift;;\n    --skip-allfiles) SKIP_ALLFILES=true;shift;;\n    -s|--skip-sysupdate) SKIP_SYSUPDATE=true;shift;;\n    --ignore-outdate) IGNORE_OUTDATE_CHECK=true;shift;;\n    --skip-plasmaintg) SKIP_PLASMAINTG=true;shift;;\n    --skip-backup) SKIP_BACKUP=true;shift;;\n    --skip-hyprland) SKIP_HYPRLAND=true;shift;;\n    --skip-hyprland-entry) SKIP_HYPRLAND_ENTRY=true;shift;;\n    --skip-fish) SKIP_FISH=true;shift;;\n    --skip-quickshell) SKIP_QUICKSHELL=true;shift;;\n    --skip-fontconfig) SKIP_FONTCONFIG=true;shift;;\n    --skip-miscconf) SKIP_MISCCONF=true;shift;;\n    --core) SKIP_PLASMAINTG=true;SKIP_FISH=true;SKIP_FONTCONFIG=true;SKIP_MISCCONF=true;shift;;\n    --exp-files) EXPERIMENTAL_FILES_SCRIPT=true;shift;;\n    --via-nix) INSTALL_VIA_NIX=true;shift;;\n    \n    ## Ones with parameter\n    --fontset)\n    if [[ -d \"${REPO_ROOT}/dots-extra/fontsets/$2\" ]];\n      then echo \"Using fontset \\\"$2\\\".\";FONTSET_DIR_NAME=\"$2\";shift 2\n      else echo \"Wrong argument for $1.\";exit 1\n    fi;;\n\n    ## Ending\n    --) shift;break ;;\n    *) echo -e \"$0: Wrong parameters.\";exit 1;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/subcmd-resetfirstrun/0.run.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\ntry rm \"${FIRSTRUN_FILE}\"\n"
  },
  {
    "path": "sdata/subcmd-resetfirstrun/options.sh",
    "content": "# Handle args for subcmd: checkdeps\n# shellcheck shell=bash\n\nshowhelp(){\necho -e \"Syntax: $0 resetfirstrun [OPTIONS]\n\nReset firstrun state.\n\nOptions:\n  -h, --help       Show this help message and exit\n\"\n}\n# `man getopt` to see more\npara=$(getopt \\\n  -o c \\\n  -l help \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\n#####################################################################################\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    --) shift;break ;;\n    *) echo -e \"$0: Wrong parameters.\";exit 1;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/subcmd-uninstall/0.run.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\nprintf \"${STY_RED}\"\nprintf \"===CAUTION===\\n\"\nprintf \"This script will try to revert changes made by \\\"./setup install\\\".\\n\"\nprintf \"However:\\n\"\nprintf \"1. It is far from enough to precisely revert all changes.\\n\"\nprintf \"2. It has not been fully tested, use at your own risk.\\n\"\nprintf \"${STY_RST}\"\npause\n##############################################################################################################################\n\n# Undo Step 3\nprintf \"${STY_CYAN}Undo install step 3...\\n${STY_RST}\"\n\nfunction view_listfile(){\n  local listfile=\"$1\"\n  if command -v less >/dev/null; then\n    less \"$listfile\"\n  else\n    cat \"$listfile\"\n  fi\n}\n\nfunction edit_listfile(){\n  local listfile=\"$1\"\n  for ed in \"$EDITOR\" nano vim nvim vi; do\n    if command -v $ed >/dev/null; then\n      x $ed \"$listfile\"\n      return\n    fi\n  done\n  printf \"Failed to find an available editor, please manually edit \\\"$listfile\\\".\\n\"\n}\n\nfunction delete_targets(){\n  local listfile=\"$1\"\n  local targets=()\n  readarray -t targets < \"$listfile\"\n  for path in \"${targets[@]}\"; do\n    if [[ ! -e \"$path\" ]]; then\n      printf \"${STY_YELLOW}Target \\\"$path\\\" inexists, skipping...${STY_RST}\\n\"\n      continue\n    elif [[ \"$path\" == \"$HOME\"* ]]; then\n      if [[ -d \"$path\" ]]; then\n\t\tx rm -r -- \"$path\"\n\telse\n\t\tx rm -- \"$path\"\n\tfi\n\n    else\n      while true; do\n        printf \"WARNING: Target \\\"$path\\\" is not under \\$HOME. Still delete it?\\ny=Yes, delete it;\\nn=No, skip this one\\n\"\n        read -n1 -p \"> \" ans < /dev/tty\n        echo\n        case \"$ans\" in\n          y|Y)\n\t    if [[ -d \"$path\" ]]; then\n\t\t    x rm -r -- \"$path\"\n\t    else\n\t\t    x rm -- \"$path\"\n\t    fi\n            break 1\n            ;;\n          n|N)\n            break 1\n            ;;\n          *)\n            ;;\n        esac\n      done\n    fi\n  done\n}\n\nfunction deletion_prompt(){\n  local listfile=\"$1\"\n  while true; do\n    printf \"Every target which path as a line inside the list \\\"$listfile\\\" will be deleted permanently.\\n\"\n    printf \"Please choose:\\nv=View the list\\ne=Edit the list\\nq=Quit\\ny=Perform deletion now\\n\"\n    read -n1 -p \"> \" choice < /dev/tty\n    echo\n    case \"$choice\" in\n      q|Q)\n        printf \"Quiting...\\n\"\n        break\n        ;;\n      y|Y)\n        delete_targets \"$listfile\"\n        break\n        ;;\n      v|V)\n        view_listfile \"$listfile\"\n        ;;\n      e|E)\n        edit_listfile \"$listfile\"\n        ;;\n      *)\n        ;;\n    esac\n  done\n}\n\ndeletion_prompt \"${INSTALLED_LISTFILE}\"\n\nempty_dir_listfile=$(mktemp)\nscan_paths=(${XDG_CONFIG_HOME} \"${XDG_DATA_HOME}\"/konsole)\nfor dir in \"${scan_paths[@]}\"; do\n  find \"$dir\" -type d -empty -print >> $empty_dir_listfile\ndone\nx dedup_and_sort_listfile \"$empty_dir_listfile\" \"$empty_dir_listfile\"\ndeletion_prompt \"$empty_dir_listfile\"\n\n##############################################################################################################################\n\nprintf \"${STY_CYAN}Undo install step 2...\\n${STY_RST}\"\nuser=$(whoami)\nwarn_undo_break_system(){\n  printf \"${STY_YELLOW}WARNING: The command below could break your system functionality. If you are unsure about it, just skip the command.${STY_RST}\\n\"\n}\nwarn_undo_break_system\nv sudo gpasswd -d \"$user\" video\nwarn_undo_break_system\nv sudo gpasswd -d \"$user\" i2c\nwarn_undo_break_system\nv sudo gpasswd -d \"$user\" input\nwarn_undo_break_system\nv sudo rm /etc/modules-load.d/i2c-dev.conf\n\n##############################################################################################################################\n\nprintf \"${STY_CYAN}Undo install step 1...\\n${STY_RST}\"\n\nif test -f sdata/dist-$OS_GROUP_ID/uninstall-deps.sh; then\n  source sdata/dist-$OS_GROUP_ID/uninstall-deps.sh\nelse\n  printf \"${STY_YELLOW}Automatic depedencies uninstallation is not yet avaible for your distro. Skipping...${STY_RST}\\n\"\nfi\n\nprintf \"${STY_CYAN}Uninstall script finished.\\n${STY_RST}\"\nprintf \"${STY_CYAN}Hint: If you had agreed to backup when you ran \\\"./setup install\\\", you should be able to find it under \\\"$BACKUP_DIR\\\".\\n${STY_RST}\"\n"
  },
  {
    "path": "sdata/subcmd-uninstall/options.sh",
    "content": "# Handle args for subcmd: uninstall\n# shellcheck shell=bash\n\nshowhelp(){\necho -e \"Syntax: $0 uninstall [OPTIONS]...\n\nUnintall dots.\n\nOptions:\n  -h, --help       Show this help message\n\"\n}\n# `man getopt` to see more\npara=$(getopt \\\n  -o h \\\n  -l help \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\n#####################################################################################\n## getopt Phase 1\n# ignore parameter's order, execute options below first\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    --) break ;;\n    *) shift ;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/subcmd-virtmon/0.run.sh",
    "content": "# This script is meant to be sourced.\n# It's not for directly running.\n\n# shellcheck shell=bash\n\nensure_cmds wayvnc lsof jq ip\n\nstart_hypr_mon_guard(){\n  if ! pgrep -x hypr_mon_guard >/dev/null 2>&1; then\n    if PATH=$PATH:${REPO_ROOT}/sdata/subcmd-virtmon command -v hypr_mon_guard ; then\n      echo \"Running hypr_mon_guard.\"\n      PATH=$PATH:${REPO_ROOT}/sdata/subcmd-virtmon setsid hypr_mon_guard > $(mktemp) 2>&1 &\n    else\n      echo \"Script hypr_mon_guard not found.\"\n      exit 1\n    fi\n  fi\n}\nif ! [[ \"${DISABLE_HYPR_MON_GUARD}\" = true ]]; then\n  start_hypr_mon_guard\nfi\n\nreadarray -t vmon_ids < <(hyprctl -j monitors all | jq -r '.[] | select(.name | test(\"^TESTER-\")) | .name | sub(\"^TESTER-\"; \"\")')\n\nif [[ \"${CLEAN_TESTER_MONITORS}\" = true ]]; then\n  echo \"Cleaning tester monitors...\"\n  for i in \"${vmon_ids[@]}\"; do\n    echo \"Removing tester monitor: TESTER-$i...\"\n    x hyprctl output remove \"TESTER-$i\"\n  done\n  echo \"Cleaning tester wayvnc sessions...\"\n  for i in /tmp/wayvncctl_tester_* ; do\n    # When no target is matched, * will not be expanded\n    [ -e \"$i\" ] || continue\n    x bash -c \"wayvncctl --socket=$i -r wayvnc-exit || rm $i\"\n  done\n  echo \"Cleaning complete, exit...\"\n  exit 0\nfi\n\n\necho \"Finding an unused port...\"\nfor port in {5900..5999}; do\n  if ! lsof -nP -iTCP:\"$port\" -sTCP:LISTEN -t >/dev/null 2>&1; then\n    vnc_port=\"$port\"\n    break\n  fi\ndone\nif [ -z \"$vnc_port\" ];then\n  echo \"No available port for vnc server, aborting...\"; exit 1\nfi\n# The name of tester_socket can be anything, the following just borrows $vnc_port as ID\ntester_socket=/tmp/wayvncctl_tester_$vnc_port\n# In case this exists for some reason\ntry rm $tester_socket\nvmon_tester=TESTER-$vnc_port\n\necho \"Creating tester monitor...\"\nx hyprctl output create headless ${vmon_tester}\n\necho \"Setting properties of tester monitor...\"\nx hyprctl keyword monitor ${vmon_tester},${VMON_RESOLUTION}@${VMON_FPS},${VMON_POSITION},${VMON_SCALE}${VMON_EXTRA}\n\ne=\"%s${STY_RST}\\n\"\nprintf \"${STY_YELLOW}=========================================$e\"\nprintf \"${STY_CYAN}The status of the virtual monitor:$e\"\nprintf \"${STY_BLUE}Resolution: ${STY_UNDERLINE}${STY_INVERT}${VMON_RESOLUTION}$e\"\nprintf \"${STY_BLUE}Frame rate: ${STY_UNDERLINE}${STY_INVERT}${VMON_FPS}$e\"\nprintf \"${STY_CYAN}Use a VNC client to connect to the virtual monitor.$e\"\nprintf \"${STY_BLUE}Port: ${STY_UNDERLINE}${STY_INVERT}$vnc_port$e\"\nprintf \"${STY_BLUE}IP: use a suitable one from below:$e\"\nprintf ${STY_PURPLE}\nLANG=C LC_ALL=C ip -o addr show up | grep -v -E 'docker|veth|virbr' | awk '{split($4,a,\"/\"); print $2\"\\t\"a[1]}'\nprintf ${STY_RST}\nprintf \"${STY_CYAN}Hint:$e\"\nprintf \"${STY_GREEN}  The VNC client will ask you about server address,$e\"\nprintf \"${STY_GREEN}  either joined as <IP>:<Port> or separately.$e\"\nprintf \"${STY_GREEN}  As for username and password, just leave them as empty.$e\"\nprintf \"${STY_YELLOW}=========================================$e\"\n\nif [ \"$RUNNING_IN_BACKGROUND\" = true ];then\n  echo \"wayvnc now running in background. Run again with --clean to cleanup.\"\n  nohup wayvnc ${WAYVNC_EX_ARGS} --socket=$tester_socket -f=${VMON_FPS} -o=${vmon_tester} --log-level=${WAYVNC_LOGLEVEL} 0.0.0.0 $vnc_port > $(mktemp) 2>&1 &\n  disown\nelse\n  echo \"wayvnc now running, press Ctrl-C to quit.\"\n  wayvnc ${WAYVNC_EX_ARGS} --socket=$tester_socket -f=${VMON_FPS} -o=${vmon_tester} --log-level=${WAYVNC_LOGLEVEL} 0.0.0.0 $vnc_port\n  echo \"wayvnc stopped. Cleaning...\"\n  hyprctl output remove \"${vmon_tester}\"\nfi\n"
  },
  {
    "path": "sdata/subcmd-virtmon/hypr_mon_guard",
    "content": "#!/usr/bin/bash\n# This script is to prevent hyprland from not responding to any input when no monitor is enabled.\n# When this script is running in background,\n# it will be safe to temporarily disable monitors using hyprctl.\n# The shebang cannot be #!/usr/bin/env bash , idk why\nwhile true; do\nreadarray -t enabled_mons < <(hyprctl -j monitors all | jq -r '.[] | select(.disabled == false) | .name')\n#if -z \"$enabled_mons\"; then\nif [ ${#enabled_mons[@]} -eq 0 ]; then\nhyprctl reload\nfi\nsleep 30\ndone\n"
  },
  {
    "path": "sdata/subcmd-virtmon/options.sh",
    "content": "# Handle args for subcmd: checkdeps\n# shellcheck shell=bash\n\nVMON_RESOLUTION=1920x1080\nVMON_FPS=60\nVMON_POSITION=auto\nVMON_SCALE=1\nVMON_EXTRA=\"\"\nWAYVNC_LOGLEVEL=${WAYVNC_LOGLEVEL:-quiet}\n\nshowhelp(){\necho -e \"Syntax: $0 virtmon [OPTIONS]\n\nCreate virtual monitor for testing multi-monitors.\n\nOptions:\n  -h, --help       Show this help message and exit\n  -c, --clean      Clean all tester monitors and wayvnc sessions and exit\n  -d, --daemon     Run in background\n      --no-guard   Disable hypr_mon_guard. Tip: this process can\n                   also be terminated using ${STY_BOLD}pkill hypr_mon_guard${STY_RST}\n\nFor the syntax of following options, see also Hyprland Wiki:\n  https://wiki.hypr.land/Configuring/Monitors\n      --res <res>  Resolution, by default ${STY_UNDERLINE}$VMON_RESOLUTION${STY_RST}\n      --fps <fps>  Refresh rate and FPS, by default ${STY_UNDERLINE}$VMON_FPS${STY_RST}\n      --pos <pos>  Position, by default ${STY_UNDERLINE}$VMON_POSITION${STY_RST}\n                   Examples: ${STY_UNDERLINE}auto-left${STY_RST}, ${STY_UNDERLINE}0x-1080${STY_RST}\n      --sca <sca>  Scale, by default ${STY_UNDERLINE}$VMON_SCALE${STY_RST}\n      --ext <ext>  Extra properties, e.g. ${STY_UNDERLINE}transform, 1${STY_RST}\n\nNote 1:\n  The virtual monitor will be served via wayvnc.\n  You need a VNC client to connect to it.\n  Recommended VNC client:\n  - Android: AVNC (https://github.com/gujjwal00/avnc)\n  - Linux X11, Windows and MacOS: TigerVNC (https://github.com/TigerVNC/tigervnc)\n  - Linux Wayland: Remmina-VNC (https://remmina.org/remmina-vnc)\n\nNote 2:\n  You can run this subcommand multiple times to create multiple virtual monitor. To do this, use ${STY_UNDERLINE}-d${STY_RST} to run wayvnc in background and repeat again, or just open extra terminal/shell and run the subcommand again.\n\nNote 3:\n  If you do not have a spare device connected to the same network, it's still possible to test multi-monitor by using VNC client on this device, in which case the <IP> should be ${STY_UNDERLINE}localhost${STY_RST} or ${STY_UNDERLINE}127.0.0.1${STY_RST}.\n\"\n}\n# `man getopt` to see more\npara=$(getopt \\\n  -o hcd \\\n  -l help,clean,daemon,no-guard,res:,fps:,pos:,sca:,ext: \\\n  -n \"$0\" -- \"$@\")\n[ $? != 0 ] && echo \"$0: Error when getopt, please recheck parameters.\" && exit 1\n#####################################################################################\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -h|--help) showhelp;exit;;\n    --) shift;break ;;\n    *) shift ;;\n  esac\ndone\n\neval set -- \"$para\"\nwhile true ; do\n  case \"$1\" in\n    -c|--clean) CLEAN_TESTER_MONITORS=true;shift;;\n    -d|--daemon) RUNNING_IN_BACKGROUND=true;shift;;\n    --no-guard) DISABLE_HYPR_MON_GUARD=true;shift;;\n    --res) VMON_RESOLUTION=\"$2\";shift 2;;\n    --fps) VMON_FPS=\"$2\";shift 2;;\n    --pos) VMON_POSITION=\"$2\";shift 2;;\n    --sca) VMON_SCALE=\"$2\";shift 2;;\n    --ext) VMON_EXTRA=\", $2\";shift 2;;\n    --) shift;break ;;\n    *) echo -e \"$0: Wrong parameters.\";exit 1;;\n  esac\ndone\n"
  },
  {
    "path": "sdata/uv/README.md",
    "content": "## Why is this important?\nInstead of installing python packages via system package manager, we should install them into virtual environment.\n\nThis is important because there has been so many complaints about the failure installing/updating python packages via system package manager, see [#1017](https://github.com/end-4/dots-hyprland/issues/1017).\n\n## How to add/remove python package?\n\n1. Edit `requirements.in`. You may refer to [PyPI](https://pypi.org/) for possible package names.\n  - If PyPI does not have the needed package, we probably need to build it manually inside the venv. In such case we need to edit the install scripts.\n2. Run `uv pip compile requirements.in -o requirements.txt` in this folder.\n\n**Notes:**\n- For reference see [uv doc](https://docs.astral.sh/uv/pip/dependencies/#using-requirementsin).\n- `requirements.txt` is included in Git. It's for locking package versions to enhance stability and reproducibility.[^1]\n\n[^1]: In fact, including package version lock file in Git is also the most common way for similar situations, for example the `package-lock.json` of Node.js projects (see also [this stackoverflow question](https://stackoverflow.com/questions/48524417/should-the-package-lock-json-file-be-added-to-gitignore)). Although there are some situations when it's not suitable to include the lock file, for example [the poetry document](https://python-poetry.org/docs/basic-usage/#committing-your-poetrylock-file-to-version-control) recommend application developers to include package version lock file in Git, but library developers should consider more, such as not including the lock file or including it but refreshing regularly.\n\n## How will the python packages get installed?\n\nFor summary:\n- They will be installed to the virtual environment `$ILLOGICAL_IMPULSE_VIRTUAL_ENV`.\n- The default value of `$ILLOGICAL_IMPULSE_VIRTUAL_ENV` is `$XDG_STATE_HOME/quickshell/.venv`.\n  - The default value of `$XDG_STATE_HOME` is `$HOME/.local/state`.\n- Currently we use `env = ILLOGICAL_IMPULSE_VIRTUAL_ENV, ~/.local/state/quickshell/.venv` in `~/.config/hypr/hyprland/env.conf` to set this environment variable.[^2]\n\nFor details: see the function `install-python-packages()` defined in `/sdata/lib/package-installers.sh`.\n\n[^2]: Hyprland seems to have weird problem dealing with recursive variable, so we can not use `$XDG_STATE_HOME/quickshell/.venv` even if we had set `$XDG_STATE_HOME` to `~/.local/state` explicitly, else `$XDG_STATE_HOME` will possibly not get expanded but get recognised as literally `$XDG_STATE_HOME`. This problem never happens for some users, but according to some issues when we were using recursive variable setting in the past, it's possible to happen for other users. Reason unknown.\n\n## How to use the python packages installed through here?\n\nBasically you'll need to activate the virtual environment first:\n```bash\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n```\n\nIt will add the python executable located in the venv to `$PATH` and give it the highest priority.\nRun `which python` and you'll understand.\n\nThis python executable will also search and use the python package inside the venv,\nwhich enables running any python script or running command provided via python package using the venv.\n\nAfter that you probably need to deactivate it:\n```bash\ndeactivate\n```\n\n### Situation 1: As a single command\n**Description:** At someplace which accept a single command,\n- run a python script,\n- or run a command provided by python package.\n\nExample: In `~/‎.config/quickshell/ii/screenshot.qml`:\n```qml\nProcess {\nid: imageDetectionProcess\n                command: [\"bash\", \"-c\", `${Directories.scriptPath}/images/find_regions.py ` \n+ `--hyprctl ` \n+ `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' ` \n+ `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} ` \n```\nIn this example, python script `find_regions.py` is called and receives some arguments.\n\n#### Solution A: shebang\n\nAdd the shebang below to the beginning of python script:\n```python\n#!/usr/bin/env -S\\_/bin/sh\\_-c\\_\"source\\_\\$(eval\\_echo\\_\\$ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate&&exec\\_python\\_-E\\_\"\\$0\"\\_\"\\$@\"\"\n```\nAnd that's it!\n\n**Note:** This is the simplest solution as it only modifies the shebang of python script.\n**However:**\n- It's only for python script, not the command provided by python package.\n- It can not deal with complex argument (e.g. filename containing spaces) passed to the python script.\n  - If we apply this solution to the example above, it may cause problem, considering that `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}'` could be a rather complex argument passed to `find_regions.py`.\n- This solution rely on shebang to activate the correct python venv, but the shebang will be ignored if the script is directly passed to the interpreter, e.g. `python3 foo.py`.\n\n#### Solution B: bash script as wrapper\n\nFirst make sure the python script is using the shebang `#!/usr/bin/env python3`, instead of `#!/usr/bin/python3` or something else.\n\nThen write a wrapper script in bash.\nLet's continue the `screenshot.qml` example, in the same directory as `find_regions.py`, write a `find-regions-venv.sh`:\n```bash\n#!/usr/bin/env bash\n\n# Specify the path of the python script.\n# The example below only applies when `find_regions.py` and this wrapper script are under the same folder.\nPY_SCRIPT=\"$(cd $(dirname \"${BASH_SOURCE[0]}\") && pwd)/find_regions.py\"\n\nsource $(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\n\"$PY_SCRIPT\" \"$@\"\ndeactivate\n```\n**Not done yet!** Do not forget to update the code calling the original python script.\nIn this example, in `~/‎.config/quickshell/ii/screenshot.qml` we should modify `find_regions.py` to the wrapper script `find-regions-venv.sh`:\n```qml\nProcess {\nid: imageDetectionProcess\n                command: [\"bash\", \"-c\", `${Directories.scriptPath}/images/find-regions-venv.sh ` \n+ `--hyprctl ` \n+ `--image '${StringUtils.shellSingleQuoteEscape(panelWindow.screenshotPath)}' ` \n+ `--max-width ${Math.round(panelWindow.screen.width * root.falsePositivePreventionRatio)} ` \n```\n\n### Situation 2: Inside a bash script\nNote: the solutions for `Situation 1: As a single command` also apply here; but **not** vice versa.\n\n**Description:**\nInside a bash script,\n- run a python script,\n- or run a command provided by python package.\n\n**Solution:**\n- Add \"activation command\" before the target line,\n- Also add \"deactivation command\" after the target line.\n\n**Example:**\n\nFor running a python script,\ntake `generate_colors_material.py` as example:\n```bash\nsource \"$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\"\npython3 \"$SCRIPT_DIR/generate_colors_material.py\" \"${generate_colors_material_args[@]}\" \\\n  > \"$STATE_DIR\"/user/generated/material_colors.scss\n\"$SCRIPT_DIR\"/applycolor.sh\n```\n\nFor running a python script provided by python package,\ntake `kde-material-you-colors` as example:\n```bash\nsource \"$(eval echo $ILLOGICAL_IMPULSE_VIRTUAL_ENV)/bin/activate\"\nkde-material-you-colors \"$mode_flag\" --color \"$color\" -sv \"$sv_num\"\ndeactivate\n```\n\n\n\n"
  },
  {
    "path": "sdata/uv/requirements.in",
    "content": "build\npillow\nsetuptools-scm\nwheel\npywayland\npsutil\nkde-material-you-colors\nmaterialyoucolor\nlibsass\nmaterial-color-utilities\nsetproctitle\nclick\nloguru\npycairo\npygobject\ntqdm\nnumpy\nopencv-contrib-python\ngoogle-auth\nrequests\n"
  },
  {
    "path": "sdata/uv/requirements.txt",
    "content": "# This file was autogenerated by uv via the following command:\n#    uv pip compile requirements.in -o requirements.txt\nbuild==1.2.2.post1\n    # via -r requirements.in\ncertifi==2026.2.25\n    # via requests\ncffi==1.17.1\n    # via\n    #   cryptography\n    #   pywayland\ncharset-normalizer==3.4.7\n    # via requests\nclick==8.2.1\n    # via -r requirements.in\ncryptography==46.0.0\n    # via google-auth\ndbus-python==1.4.0\n    # via kde-material-you-colors\ngoogle-auth==2.49.1\n    # via -r requirements.in\nidna==3.11\n    # via requests\nkde-material-you-colors==1.10.1\n    # via -r requirements.in\nlibsass==0.23.0\n    # via -r requirements.in\nloguru==0.7.3\n    # via -r requirements.in\nmaterial-color-utilities==0.2.1\n    # via -r requirements.in\nmaterialyoucolor==2.0.10\n    # via\n    #   -r requirements.in\n    #   kde-material-you-colors\nnumpy==2.2.2\n    # via\n    #   -r requirements.in\n    #   kde-material-you-colors\n    #   material-color-utilities\n    #   opencv-contrib-python\nopencv-contrib-python==4.12.0.88\n    # via -r requirements.in\npackaging==24.2\n    # via\n    #   build\n    #   setuptools-scm\npillow==11.1.0\n    # via\n    #   -r requirements.in\n    #   kde-material-you-colors\n    #   material-color-utilities\npsutil==6.1.1\n    # via -r requirements.in\npyasn1==0.6.3\n    # via pyasn1-modules\npyasn1-modules==0.4.2\n    # via google-auth\npycairo==1.28.0\n    # via\n    #   -r requirements.in\n    #   pygobject\npycparser==2.22\n    # via cffi\npygobject==3.52.3\n    # via -r requirements.in\npyproject-hooks==1.2.0\n    # via build\npywayland==0.4.18\n    # via -r requirements.in\nrequests==2.33.1\n    # via -r requirements.in\nsetproctitle==1.3.4\n    # via -r requirements.in\nsetuptools==80.9.0\n    # via setuptools-scm\nsetuptools-scm==8.1.0\n    # via -r requirements.in\ntqdm==4.67.1\n    # via -r requirements.in\nurllib3==2.6.3\n    # via requests\nwheel==0.45.1\n    # via -r requirements.in\n"
  },
  {
    "path": "sdata/uv/shell.nix",
    "content": "{ pkgs ? import <nixpkgs> {} }:\npkgs.mkShell {\n  buildInputs = with pkgs; [\n    pkg-config\n    meson\n    ninja\n    cairo\n    dbus\n    dbus-glib\n    glib\n  ];\n}\n"
  },
  {
    "path": "setup",
    "content": "#!/usr/bin/env bash\ncd \"$(dirname \"$0\")\"\n# Use REPO_ROOT instead of base - when scripts are sourced they do not need export to inherit vars\nREPO_ROOT=\"$(pwd)\"\nsource ./sdata/lib/environment-variables.sh\nsource ./sdata/lib/functions.sh\nsource ./sdata/lib/package-installers.sh\nsource ./sdata/lib/dist-determine.sh\n\nprevent_sudo_or_root\nset -e\n\n#####################################################################################\nshowhelp_global(){\nprintf \"${STY_CYAN}NOTE:\n  The old \\\"./install.sh\\\"   is now \\\"./setup install\\\"\n  The old \\\"./update.sh\\\"    is now \\\"./setup exp-update\\\"\n  The old \\\"./uninstall.sh\\\" is now \\\"./setup uninstall\\\"${STY_RST}\n\n[$0]: Handle setup for illogical-impulse.\n\nSyntax:\n  $0 <subcommand> [OPTIONS]...\n\nSubcommands:\n  install        (Re)Install/Update illogical-impulse.\n                 Note: To update to the latest, manually run \\\"git stash && git pull\\\" first.\n  install-deps   Run the install step \\\"1. Install dependencies\\\"\n  install-setups Run the install step \\\"2. Setup for permissions/services etc\\\"\n  install-files  Run the install step \\\"3. Copying config files\\\"\n  resetfirstrun  Reset firstrun state.\n  uninstall      Uninstall illogical-impulse.\n\n  exp-update     (Experimental) Update illogical-impulse without fully reinstall.\n  exp-merge      (Experimental) Merge upstream changes with local configs using git rebase.\n\n  virtmon        (For dev only) Create virtual monitors for testing multi-monitors.\n  checkdeps      (For dev only) Check whether pkgs exist in AUR or repos of Arch.\n\n  help           Show this help message.\n\nFor each <subcommand>, use -h for details:\n  $0 <subcommand> -h\n\n${STY_BOLD}${STY_CYAN}Access ${STY_UNDERLINE}https://ii.clsty.link${STY_RST} ${STY_BOLD}${STY_CYAN}for documentation about illogical-impulse.${STY_RST}\n\"\n}\ncase $1 in\n  # Global help\n  \"\"|help|--help|-h)showhelp_global;exit;;\n  # Correct subcommand\n  install|uninstall|exp-update|exp-merge|resetfirstrun|checkdeps|virtmon)\n    SUBCMD_NAME=$1\n    SUBCMD_DIR=./sdata/subcmd-$1\n    shift;;\n  # Correct subcommand but not using ./sdata/subcmd-$1\n  install-deps|install-setups|install-files)\n    SUBCMD_NAME=$1\n    SUBCMD_DIR=./sdata/subcmd-install\n    shift;;\n  # Wrong subcommand\n  *)printf \"${STY_RED}Unknown subcommand \\\"$1\\\".${STY_RST}\\n\";showhelp_global;exit 1;;\nesac\n#####################################################################################\nif [[ -f \"${SUBCMD_DIR}/options.sh\" ]];\n  then source \"${SUBCMD_DIR}/options.sh\"\nfi\ncase ${SUBCMD_NAME} in\n  install)\n    for function in ${print_os_group_id_functions[@]}; do\n      $function\n    done\n    pause\n    # Initialize sudo keepalive for the entire install process\n    sudo_init_keepalive\n    # Set trap to cleanup when this subcommand exits\n    trap sudo_stop_keepalive EXIT INT TERM\n    if [[ \"${SKIP_ALLGREETING}\" != true ]]; then\n      source ${SUBCMD_DIR}/0.greeting.sh\n    fi\n    if [[ \"${SKIP_ALLDEPS}\" != true ]]; then\n      source ${SUBCMD_DIR}/1.deps-router.sh\n    fi\n    if [[ \"${SKIP_ALLSETUPS}\" != true ]]; then\n      source ${SUBCMD_DIR}/2.setups.sh\n    fi\n    if [[ \"${SKIP_ALLFILES}\" != true ]]; then\n      source ${SUBCMD_DIR}/3.files.sh\n    fi\n    ;;\n  install-deps)\n    for function in ${print_os_group_id_functions[@]}; do\n      $function\n    done\n    pause\n    # Initialize sudo keepalive for dependency installation\n    sudo_init_keepalive\n    # Set trap to cleanup when this subcommand exits\n    trap sudo_stop_keepalive EXIT INT TERM\n    if [[ \"${SKIP_ALLDEPS}\" != true ]]; then\n      source ${SUBCMD_DIR}/1.deps-router.sh\n    fi\n    ;;\n  install-setups)\n    for function in ${print_os_group_id_functions[@]}; do\n      $function\n    done\n    pause\n    # Initialize sudo keepalive for setup steps\n    sudo_init_keepalive\n    # Set trap to cleanup when this subcommand exits\n    trap sudo_stop_keepalive EXIT INT TERM\n    if [[ \"${SKIP_ALLSETUPS}\" != true ]]; then\n      source ${SUBCMD_DIR}/2.setups.sh\n    fi\n    ;;\n  install-files)\n    for function in ${print_os_group_id_functions[@]}; do\n      $function\n    done\n    pause\n    if [[ \"${SKIP_ALLFILES}\" != true ]]; then\n      source ${SUBCMD_DIR}/3.files.sh\n    fi\n    ;;\n  *)\n    source ${SUBCMD_DIR}/0.run.sh\n    exit\n    ;;\nesac\n"
  }
]