[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "{\n  \"image\": \"mcr.microsoft.com/devcontainers/universal:2\",\n  \"features\": {\n  }\n}\n"
  },
  {
    "path": ".github/FUNDING.yml",
    "content": "github: [Alexander5015, iamharshdev]\nko-fi: alexander5015\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-bug-report-form.yml",
    "content": "---\nname: Bug Report\ndescription: File a bug report.\ntitle: '[Bug] '\nlabels:\n  - bug\n  - unconfirmed\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        Thanks for taking the time to fill out this bug report!\n  - type: textarea\n    id: what-happened\n    attributes:\n      label: What happened? What are the steps to reproduce the issue?\n      description: >-\n        Describe the bug and include the steps to replicate the issue. Issues\n        with images or videos will be resolved faster.\n      placeholder: Clearly explain the issue. Please limit each post to one issue.\n    validations:\n      required: true\n  - type: textarea\n    id: expected-behaviour\n    attributes:\n      label: What did you expect to happen?\n      description: A clear and concise description of what you expected to happen.\n    validations:\n      required: true\n  - type: dropdown\n    id: version\n    attributes:\n      label: Boring Notch Version\n      description: >-\n        What version of our software are you running? (Go to ✦ in the menu bar >\n        Settings > About)\n      options:\n        - Select a version\n        - v2.7.3\n        - v2.7.2\n        - v2.7.1\n        - v2.7\n        - v2.7-rc.3\n        - v2.6\n    validations:\n      required: true\n  - type: input\n    id: operating-system\n    attributes:\n      label: macOS Version\n      description: Go to  > About This Mac\n    validations:\n      required: true\n  - type: dropdown\n    id: music-source\n    attributes:\n      label: Music Source? (If relevant)\n      description: >-\n         If this issue is related to music, what music source did you select in Boring Notch settings?\n      options:\n        - Now Playing\n        - Apple Music\n        - Spotify\n        - YouTube Music\n    validations:\n      required: false\n  - type: input\n    id: music-app\n    attributes:\n      label: Music App (If using Now Playing)\n    validations:\n      required: false\n  - type: input\n    id: music-website\n    attributes:\n      label: Website (If using browser for music)\n    validations:\n      required: false\n  - type: textarea\n    id: logs\n    attributes:\n      label: Relevant log output\n      description: >-\n        Please copy and paste any relevant log output. This will be\n        automatically formatted into code, so no need for backticks.\n      render: shell\n  - type: checkboxes\n    id: checks\n    attributes:\n      label: Checks\n      description: \n      options:\n        - label: I haven't found any duplicates with my issue.\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/1-feature-request-form.yml",
    "content": "name: Feature Request\ndescription: Suggest a new idea or enhancement for Boring Notch\ntitle: \"[FEATURE] \"\nlabels: []\n\nbody:\n  - type: markdown\n    attributes:\n      value: |\n        ## Feature Request\n        \n        Thanks for helping make **Boring Notch** better! Please check [existing requests](https://github.com/TheBoredTeam/boring.notch/issues?q=is%3Aissue) before submitting to avoid duplicates.\n\n  - type: textarea\n    id: problem\n    attributes:\n      label: Is your feature request related to a problem?\n      description: |\n        Please provide a clear description of the problem or pain point this feature would address.\n        \n        **Example:** \"I often miss my next meeting because I work in full-screen mode and can't see the system clock or calendar notifications.\"\n      placeholder: \"I'm frustrated when... or It would be great if...\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: solution\n    attributes:\n      label: Describe the solution you'd like\n      description: |\n        How should Boring Notch handle this? Describe the behavior, UI, or interaction you imagine in detail.\n        \n        **Example:** \"When a meeting is starting in 5 minutes, the notch could expand slightly to show a countdown timer or the meeting title.\"\n      placeholder: \"I would like the notch to...\"\n    validations:\n      required: true\n\n  - type: textarea\n    id: use-cases\n    attributes:\n      label: Use cases & user scenarios\n      description: |\n        Describe specific scenarios where this feature would be valuable. Who would benefit from this and how often would it be used?\n        \n        **Example:** \"Remote workers who attend 5+ video meetings daily would use this constantly. Students could benefit during online classes.\"\n      placeholder: \"This would help users who...\"\n    validations:\n      required: false\n\n  - type: textarea\n    id: additional-context\n    attributes:\n      label: Additional context & screenshots\n      description: |\n        Add any other context, mockups, wireframes, or screenshots about the feature request here. Visuals are incredibly helpful!\n        \n        You can drag and drop images directly into this field.\n      placeholder: \"Here is a mockup of how the hover state should look...\"\n    validations:\n      required: false\n\n  - type: checkboxes\n    id: contribution\n    attributes:\n      label: Willingness to contribute\n      description: \"We love community contributions! Would you be willing to help build this?\"\n      options:\n        - label: \"Yes, I can write the code for this feature\"\n        - label: \"Yes, I can help with design/mockups\"\n\n  - type: checkboxes\n    id: checklist\n    attributes:\n      label: Pre-submission checklist\n      description: \"Please confirm the following before submitting:\"\n      options:\n        - label: \"I have searched existing issues to ensure this isn't a duplicate\"\n          required: true\n        - label: \"I have provided sufficient detail for the team to understand the request\"\n          required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "<!--- This issue template is being replaced with the updated issue form for new features -->\n---\nname: Feature request\nabout: Suggest an idea for this project\ntitle: \"[FEATURE]\"\nlabels: ''\nassignees: ''\n\n---\n\n**Is your feature request related to a problem? Please describe.**\nA clear and concise description of what the problem is. Ex. I'm always frustrated when [...]\n\n**Describe the solution you'd like**\nA clear and concise description of what you want to happen.\n\n**Additional context**\nAdd any other context or screenshots about the feature request here.\n\n**Checks**\n- [x] I haven't found any duplicates with my issue.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/old_bug_report.md",
    "content": "<!--- This issue template is deprecated, use the updated issue form for bug reports -->\n---\nname: Bug report\nabout: Create a report to help us improve\ntitle: \"[BUG]\"\nlabels: 'bug,unconfirmed'\nassignees: ''\n\n---\n\n**Describe the bug**\nA clear and concise description of what the bug is.\n\n**To Reproduce**\nSteps to reproduce the behavior:\n1. Go to '...'\n2. Click on '....'\n3. Scroll down to '....'\n4. See error\n\n**Expected behavior**\nA clear and concise description of what you expected to happen.\n\n**Screenshots or recordings**\nIf applicable, add screenshots to help explain your problem.\n\n**Additional context**\nAdd any other context about the problem here. Mentioning what version are you using.\n\n**Checks**\n- [x] I haven't found any duplicates with my issue.\n"
  },
  {
    "path": ".github/PULL_REQUEST.md",
    "content": "## Pull Request template\nPlease, go through these steps before you submit a PR.\n\n1. Make sure that your PR is not a duplicate.\n2. If not, then make sure that:\n\n    a. Your changes MUST NOT change translations. Please submit translations on [Crowdin](https://crowdin.com/project/boring-notch).\n\n    b. You have tested the code yourself to ensure it builds correctly and functions as intended.\n\n3. **After** these steps, you're ready to open a pull request.\n\n    a. Your pull request MUST NOT target the `main` branch on this repository. You probably want to target `dev` instead.\n\n    b. Give a descriptive title to your PR.\n\n    c. Describe your changes. PR should also include screen recording or screenshots to show the changes that were made.\n\n    d. Put `closes #XXXX` in your description to link your PR to the issue(s) that it fixes (if such).\n\nIMPORTANT: Please review the [CONTRIBUTING.md](../CONTRIBUTING.md) file for detailed contributing guidelines.\n\n**PLEASE REMOVE THIS TEMPLATE BEFORE SUBMITTING**\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\" # See documentation for possible values\n    directory: \"/\" # Location of package manifests\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/scripts/extract_version.py",
    "content": "#!/usr/bin/env python3\n\nfrom __future__ import annotations\n\nimport json\nimport os\nimport re\nimport semver\nimport subprocess\nimport sys\nfrom argparse import ArgumentParser\n\n\nSEMVER_RE = re.compile(r\"v?[0-9]+\\.[0-9]+(?:\\.[0-9]+)?(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?\")\n\n\ndef find_first_valid(text: str):\n    for cand in SEMVER_RE.findall(text or \"\"):\n        s = cand.lstrip(\"v\")\n        # Normalize for parsing: 2.7 -> 2.7.0, 2.7-beta -> 2.7.0-beta\n        # Regex: look for X.Y at start, not followed by .Z\n        normalized = re.sub(r\"^([0-9]+\\.[0-9]+)(?![0-9]*\\.)\", r\"\\1.0\", s)\n        try:\n            parsed = semver.VersionInfo.parse(normalized)\n            return s, parsed\n        except Exception:\n            continue\n    return None, None\n\n\ndef write_github_output(version: str | None, is_beta_flag: bool) -> None:\n    out = os.environ.get(\"GITHUB_OUTPUT\")\n    if not out:\n        return\n    try:\n        with open(out, \"a\", encoding=\"utf-8\") as f:\n            f.write(f\"version={version or ''}\\n\")\n            f.write(f\"is_beta={str(is_beta_flag).lower()}\\n\")\n    except Exception:\n        pass\n\n\ndef main(argv=None) -> int:\n    p = ArgumentParser()\n    p.add_argument(\"-c\", \"--comment\", help=\"Comment body to scan (defaults: $COMMENT or stdin)\")\n    args = p.parse_args(argv)\n\n    comment = args.comment or os.environ.get(\"COMMENT\")\n    if not comment:\n        comment = sys.stdin.read() or \"\"\n\n    version, parsed = find_first_valid(comment)\n\n    beta = getattr(parsed, \"prerelease\", None)\n\n    # Write GitHub Actions outputs if available (GITHUB_OUTPUT)\n    write_github_output(version, bool(beta))\n\n    # For CLI consumption print simple key=value lines (and a human line)\n    print(f\"version={version or ''}\")\n    print(f\"is_beta={str(bool(beta)).lower()}\")\n    print(f\"Found version: {version} (beta: {bool(beta)})\")\n    return 0\n\n\nif __name__ == \"__main__\":\n    raise SystemExit(main())\n"
  },
  {
    "path": ".github/scripts/remove_beta.py",
    "content": "#!/usr/bin/env python3\n\"\"\"\nRemove the last beta item from an appcast XML file.\nUsage: remove_beta.py path/to/appcast.xml\n\nThis script mirrors the inline Python used previously in the workflow.\n\"\"\"\nimport sys\nimport xml.etree.ElementTree as ET\nfrom pathlib import Path\n\n\ndef remove_last_beta_item(appcast_path: Path) -> int:\n    if not appcast_path.exists():\n        print(f\"Appcast file not found: {appcast_path}\")\n        return 1\n\n    try:\n        tree = ET.parse(appcast_path)\n        root = tree.getroot()\n\n        channel = root.find('channel')\n        if channel is None:\n            print('No channel found in appcast')\n            return 0\n\n        items = channel.findall('item')\n        removed = False\n        for item in reversed(items):\n            enclosure = item.find('enclosure')\n            if enclosure is not None:\n                version = enclosure.get('sparkle:version', '')\n                if 'beta' in version.lower():\n                    channel.remove(item)\n                    removed = True\n                    break\n\n        if removed:\n            tree.write(appcast_path, encoding='utf-8', xml_declaration=True)\n            print('Removed beta item from appcast')\n        else:\n            print('No beta item found in appcast')\n\n        return 0\n\n    except Exception as e:\n        print(f'Error processing appcast: {e}')\n        return 2\n\n\nif __name__ == '__main__':\n    if len(sys.argv) < 2:\n        print('Usage: remove_beta.py path/to/appcast.xml')\n        sys.exit(1)\n\n    path = Path(sys.argv[1])\n    sys.exit(remove_last_beta_item(path))\n"
  },
  {
    "path": ".github/workflows/build_reusable.yml",
    "content": "name: \"Reusable Build\"\non:\n  workflow_call:\n    inputs:\n      head_ref:\n        required: true\n        type: string\n      version:\n        required: false\n        type: string\n      build_number:\n        required: false\n        type: string\n      xcode_version:\n        required: false\n        type: string\n        default: \"16.4\"\n      code_sign_identity:\n        required: false\n        type: string\n        default: \"Apple Development\"\n    secrets:\n      BUILD_CERTIFICATE_BASE64:\n        required: true\n      P12_PASSWORD:\n        required: true\n      KEYCHAIN_PASSWORD:\n        required: true\n\njobs:\n  build:\n    runs-on: macos-latest\n    permissions:\n      contents: write\n    env:\n      PROJECT_NAME: boringNotch\n      EXPORT_METHOD: development\n      VERSION_INPUT: ${{ inputs.version }}\n      BUILD_NUMBER_INPUT: ${{ inputs.build_number }}\n      DEVELOPMENT_TEAM: ${{ vars.DEVELOPMENT_TEAM_ID }}\n      CODE_SIGN_IDENTITY: ${{ inputs.code_sign_identity }}\n      XCODE_VERSION: ${{ inputs.xcode_version }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd\n        with:\n          ref: ${{ inputs.head_ref }}\n\n      - name: Resolve Swift packages\n        run: xcodebuild -resolvePackageDependencies -project ${{ env.PROJECT_NAME }}.xcodeproj\n\n      - name: Install Apple certificate\n        env:\n          BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n          P12_PASSWORD: ${{ secrets.P12_PASSWORD }}\n          KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}\n        run: |\n          CERT_PATH=\"$RUNNER_TEMP/build_certificate.p12\"\n          KC=\"$RUNNER_TEMP/app-signing.keychain-db\"\n          echo -n \"$BUILD_CERTIFICATE_BASE64\" | base64 --decode > \"$CERT_PATH\"\n          security create-keychain -p \"$KEYCHAIN_PASSWORD\" \"$KC\"\n          security set-keychain-settings -lut 21600 \"$KC\"\n          security unlock-keychain -p \"$KEYCHAIN_PASSWORD\" \"$KC\"\n          security import \"$CERT_PATH\" -P \"$P12_PASSWORD\" -A -t cert -f pkcs12 -k \"$KC\"\n          security list-keychain -d user -s \"$KC\"\n\n      - name: Select Xcode ${{ env.XCODE_VERSION }}\n        run: |\n          XCODE_PATH=\"/Applications/Xcode_${XCODE_VERSION}.app\"\n          if [[ ! -d \"$XCODE_PATH\" ]]; then\n            echo \"::error::Xcode ${XCODE_VERSION} not found at ${XCODE_PATH}\"\n            echo \"Available Xcode versions:\"\n            ls -d /Applications/Xcode*.app 2>/dev/null || echo \"  (none found)\"\n            exit 1\n          fi\n          sudo xcode-select -s \"$XCODE_PATH\"\n          xcodebuild -version\n\n      - name: Set version and build number\n        run: |\n          PBXPROJ=\"${{ env.PROJECT_NAME }}.xcodeproj/project.pbxproj\"\n          VERSION=\"${{ env.VERSION_INPUT }}\"\n          BUILD_NUMBER_INPUT=\"${{ env.BUILD_NUMBER_INPUT }}\"\n          if [[ -z \"$BUILD_NUMBER_INPUT\" ]]; then\n            BUILD_NUMBER=\"$GITHUB_RUN_NUMBER\"\n          else\n            BUILD_NUMBER=\"$BUILD_NUMBER_INPUT\"\n          fi\n          if [[ -n \"$VERSION\" ]]; then\n            sed -i '' \"s/MARKETING_VERSION = [^;]*/MARKETING_VERSION = ${VERSION}/g\" \"$PBXPROJ\"\n          fi\n          sed -i '' \"s/CURRENT_PROJECT_VERSION = [^;]*/CURRENT_PROJECT_VERSION = ${BUILD_NUMBER}/g\" \"$PBXPROJ\"\n\n      - name: Commit version changes\n        run: |\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add \"${{ env.PROJECT_NAME }}.xcodeproj/project.pbxproj\"\n          VERSION=\"${{ env.VERSION_INPUT }}\"\n          BUILD_NUMBER_INPUT=\"${{ env.BUILD_NUMBER_INPUT }}\"\n          if [[ -z \"$BUILD_NUMBER_INPUT\" ]]; then\n            BUILD_NUMBER=\"$GITHUB_RUN_NUMBER\"\n          else\n            BUILD_NUMBER=\"$BUILD_NUMBER_INPUT\"\n          fi\n          if [[ -n \"$VERSION\" ]]; then\n            COMMIT_MSG=\"Set version to v${VERSION} (build ${BUILD_NUMBER})\"\n          else\n            COMMIT_MSG=\"Set build number to ${BUILD_NUMBER}\"\n          fi\n          git commit -m \"$COMMIT_MSG\" || true\n          git push origin \"HEAD:${{ inputs.head_ref }}\" || true\n\n      - name: Build and archive\n        run: |\n          xcodebuild clean archive \\\n            -project ${{ env.PROJECT_NAME }}.xcodeproj \\\n            -scheme ${{ env.PROJECT_NAME }} \\\n            -archivePath ${{ env.PROJECT_NAME }} \\\n            -destination \"generic/platform=macOS\" \\\n            DEVELOPMENT_TEAM=\"$DEVELOPMENT_TEAM\" \\\n            CODE_SIGN_IDENTITY=\"$CODE_SIGN_IDENTITY\" \\\n            ONLY_ACTIVE_ARCH=NO \\\n            -allowProvisioningUpdates\n\n      - name: Export app\n        run: |\n          cat > \"$RUNNER_TEMP/export_options.plist\" <<PLIST\n          <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n          <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n          <plist version=\"1.0\">\n          <dict>\n            <key>method</key>\n            <string>${{ env.EXPORT_METHOD }}</string>\n            <key>signingStyle</key>\n            <string>automatic</string>\n            <key>teamID</key>\n            <string>${DEVELOPMENT_TEAM}</string>\n          </dict>\n          </plist>\n          PLIST\n          xcodebuild -exportArchive \\\n            -archivePath \"${{ env.PROJECT_NAME }}.xcarchive\" \\\n            -exportPath Release \\\n            -exportOptionsPlist \"$RUNNER_TEMP/export_options.plist\"\n\n      - name: Verify generate_appcast exists\n        run: |\n          if [ ! -x Configuration/sparkle/generate_appcast ]; then\n            echo \"::warning title=Missing generate_appcast::Configuration/sparkle/generate_appcast is not present or not executable; skipping appcast generation verification.\"\n          fi\n\n      - name: Create DMG\n        run: |\n          VENV_DIR=\"$RUNNER_TEMP/venv\"\n          python3 -m venv \"$VENV_DIR\"\n          source \"$VENV_DIR/bin/activate\"\n          pip install --upgrade pip setuptools wheel \"dmgbuild[badge_icons]\"\n          chmod +x Configuration/dmg/create_dmg.sh\n          ./Configuration/dmg/create_dmg.sh \\\n            \"Release/${{ env.PROJECT_NAME }}.app\" \\\n            \"Release/${{ env.PROJECT_NAME }}.dmg\" \\\n            \"${{ env.PROJECT_NAME }} ${{ env.VERSION_INPUT }}\"\n\n      - name: Upload DMG\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        with:\n          name: ${{ env.PROJECT_NAME }}.dmg\n          path: Release/${{ env.PROJECT_NAME }}.dmg\n\n      - name: Upload .app\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f\n        with:\n          name: ${{ env.PROJECT_NAME }}.app\n          path: Release/${{ env.PROJECT_NAME }}.app\n"
  },
  {
    "path": ".github/workflows/cicd.yml",
    "content": "name: Build for macOS\n\non:\n  push:\n    branches:\n      - '*'\n  pull_request:\n    branches:\n      - '*'\n\n# https://stackoverflow.com/a/72408109/6942800\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}\n  cancel-in-progress: true\n\n# Least-privilege: only needs to read code for building.\npermissions:\n  contents: read\n\njobs:\n  build:\n    name: Build Boring Notch\n    strategy:\n      matrix:\n        platform:\n          - macOS\n        xcode:\n          - ^16\n        scheme:\n          - boringNotch\n    runs-on: macos-latest\n    steps:\n    - name: Code Checkout\n      # TODO: pin to immutable SHA\n      uses: actions/checkout@v6.0.2\n    - # TODO: pin to immutable SHA\n      uses: mxcl/xcodebuild@v3\n      with:\n        xcode: ${{ matrix.xcode }}\n        platform: ${{ matrix.platform }}\n        scheme: ${{ matrix.scheme }}\n        action: build\n        verbosity: xcpretty\n        upload-logs: always\n        configuration: release\n"
  },
  {
    "path": ".github/workflows/manual_build.yml",
    "content": "name: \"Manual Build\"\non:\n  workflow_dispatch:\n    inputs:\n      head_ref:\n        description: 'Branch to build'\n        required: false\n        default: main\n      version:\n        description: 'Marketing version (optional)'\n        required: false\n      build_number:\n        description: 'Build number (optional)'\n        required: false\n      xcode_version:\n        description: 'Xcode version (optional)'\n        required: false\n        default: '16.4'\n\n# Least-privilege: workflow_dispatch build only; no token access needed beyond artifacts.\npermissions:\n  contents: write\n\n\njobs:\n  build:\n    name: Build and sign app\n    uses: ./.github/workflows/build_reusable.yml\n    with:\n      head_ref: ${{ github.event.inputs.head_ref || github.ref_name || 'main' }}\n      version: ${{ github.event.inputs.version || '' }}\n      build_number: ${{ github.event.inputs.build_number || '' }}\n      xcode_version: ${{ github.event.inputs.xcode_version || '16.4' }}\n      code_sign_identity: \"Apple Development\"\n    secrets:\n      BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n      P12_PASSWORD:              ${{ secrets.P12_PASSWORD }}\n      KEYCHAIN_PASSWORD:         ${{ secrets.KEYCHAIN_PASSWORD }}\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: \"Deploy Boring Notch\"\non:\n  issue_comment:\n    types: [created]\n\nconcurrency:\n  group: release-${{ github.event.issue.number || github.run_id }}\n  cancel-in-progress: true\n\nenv:\n  PROJECT_NAME: boringNotch\n  BETA_CHANNEL_NAME: beta\n  RELEASE_COMMAND: /release\n  CODE_SIGN_IDENTITY: \"Apple Development\"\n  XCODE_VERSION: \"16.4\"\n\npermissions:\n  contents: read\n  pull-requests: write\n\njobs:\n  # helper job to test for the release command in the comment; env is safe to use here\n  check_release:\n    name: Check for release command\n    runs-on: ubuntu-latest\n    outputs:\n      is_release: ${{ steps.check.outputs.is_release }}\n    steps:\n      - id: check\n        run: |\n          if [[ \"${{ github.event.comment.body }}\" == *\"${{ env.RELEASE_COMMAND }}\"* ]]; then\n            echo \"is_release=true\" >> $GITHUB_OUTPUT\n          else\n            echo \"is_release=false\" >> $GITHUB_OUTPUT\n          fi\n\n  preparation:\n    name: Preparation\n    if: ${{ github.event.issue.pull_request && needs.check_release.outputs.is_release == 'true' }}\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n      pull-requests: write\n    outputs:\n      is_beta: ${{ steps.extract_version.outputs.is_beta }}\n      version: ${{ steps.extract_version.outputs.version }}\n      build_number: ${{ steps.extract_version.outputs.build_number }}\n      title: ${{ steps.release_notes.outputs.title }}\n      release_notes: ${{ steps.release_notes.outputs.release_notes }}\n      release_notes_github: ${{ steps.release_notes.outputs.release_notes_github }}\n      head_ref: ${{ steps.pr_info.outputs.head_ref }}\n      base_ref: ${{ steps.pr_info.outputs.base_ref }}\n    steps:\n      - name: Validate permissions and PR state\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          set -euo pipefail\n          REPO=\"${{ github.repository }}\"\n          COMMENTER=\"${{ github.event.comment.user.login }}\"\n          PR_NUMBER=\"${{ github.event.issue.number }}\"\n\n          # Acknowledge the command\n          gh api \"repos/${REPO}/issues/comments/${{ github.event.comment.id }}/reactions\" \\\n            -f content=eyes --silent\n\n          # Require admin permission\n          PERM=$(gh api \"repos/${REPO}/collaborators/${COMMENTER}/permission\" --jq '.permission')\n          if [[ \"$PERM\" != \"admin\" ]]; then\n            echo \"::error::${COMMENTER} is not an admin (permission: ${PERM})\"\n            exit 1\n          fi\n\n          # Require mergeable, non-draft PR\n          IS_DRAFT=$(gh api \"repos/${REPO}/pulls/${PR_NUMBER}\" --jq '.draft')\n          MERGEABLE=$(gh api \"repos/${REPO}/pulls/${PR_NUMBER}\" --jq '.mergeable')\n          if [[ \"$IS_DRAFT\" == \"true\" || \"$MERGEABLE\" != \"true\" ]]; then\n            echo \"::error::PR #${PR_NUMBER} is not ready to merge (draft=${IS_DRAFT}, mergeable=${MERGEABLE})\"\n            exit 1\n          fi\n\n      - name: Get PR branch info\n        id: pr_info\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          set -euo pipefail\n          PR_DATA=$(gh api \"repos/${{ github.repository }}/pulls/${{ github.event.issue.number }}\" \\\n            --jq '{head_ref: .head.ref, base_ref: .base.ref}')\n          echo \"head_ref=$(echo \"$PR_DATA\" | jq -r '.head_ref')\" >> \"$GITHUB_OUTPUT\"\n          echo \"base_ref=$(echo \"$PR_DATA\" | jq -r '.base_ref')\" >> \"$GITHUB_OUTPUT\"\n\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          ref: ${{ steps.pr_info.outputs.head_ref }}\n\n      - name: Ensure scripts directory exists\n        env:\n          BASE_REF: ${{ steps.pr_info.outputs.base_ref }}\n        run: |\n          if [ ! -f \".github/scripts/extract_version.py\" ]; then\n            echo \"Script not found in PR branch, fetching from base branch\"\n            git fetch origin \"$BASE_REF\"\n            git checkout \"origin/$BASE_REF\" -- .github/scripts/\n          fi\n\n      - name: Extract version from comment\n        id: extract_version\n        env:\n          COMMENT: ${{ github.event.comment.body }}\n        run: |\n          set -euo pipefail\n          python3 -m pip install --upgrade --no-cache-dir semver\n          export projname=\"${{ env.PROJECT_NAME }}\"\n          OUTPUT=$(python3 .github/scripts/extract_version.py -c \"$COMMENT\")\n          VERSION=$(awk -F= '/^version=/{print $2; exit}' <<<\"$OUTPUT\")\n          IS_BETA=$(awk -F= '/^is_beta=/{print $2; exit}' <<<\"$OUTPUT\")\n          BUILD_NUMBER=\"${GITHUB_RUN_NUMBER}\"\n          echo \"version=$VERSION\" >> \"$GITHUB_OUTPUT\"\n          echo \"is_beta=$IS_BETA\" >> \"$GITHUB_OUTPUT\"\n          echo \"build_number=$BUILD_NUMBER\" >> \"$GITHUB_OUTPUT\"\n          echo \"Version: $VERSION | Beta: $IS_BETA | Build: $BUILD_NUMBER\"\n\n      - name: Generate release notes from PR\n        id: release_notes\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          set -euo pipefail\n          PR_NUMBER=\"${{ github.event.issue.number }}\"\n          REPO=\"${{ github.repository }}\"\n          TITLE=$(gh api \"repos/${REPO}/pulls/${PR_NUMBER}\" --jq '.title // \"Release\"')\n          BODY=$(gh api \"repos/${REPO}/pulls/${PR_NUMBER}\" --jq '.body // \"- No release notes provided\"')\n\n          # Strip H1 headings for GitHub release notes\n          BODY_GITHUB=$(printf '%s' \"$BODY\" | sed '/^# /d' | perl -pe 's#<h1[^>]*>.*?</h1>##gi')\n\n          {\n            echo \"title=${TITLE}\"\n            echo \"release_notes<<RELEASE_NOTES_DELIM\"\n            echo \"$BODY\"\n            echo \"RELEASE_NOTES_DELIM\"\n            echo \"release_notes_github<<RELEASE_NOTES_GH_DELIM\"\n            echo \"$BODY_GITHUB\"\n            echo \"RELEASE_NOTES_GH_DELIM\"\n          } >> \"$GITHUB_OUTPUT\"\n\n      - name: Check version not already released\n        env:\n          VERSION: ${{ steps.extract_version.outputs.version }}\n        run: |\n          git fetch --tags\n          if git rev-parse -q --verify \"refs/tags/v${VERSION}\" >/dev/null; then\n            echo \"::error::Version v${VERSION} already exists as a tag\"\n            exit 1\n          fi\n\n      - name: Sync branch (stable releases only)\n        if: steps.extract_version.outputs.is_beta == 'false'\n        env:\n          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}\n          BASE_REF: ${{ steps.pr_info.outputs.base_ref }}\n          HEAD_REF: ${{ steps.pr_info.outputs.head_ref }}\n        run: |\n          set -euo pipefail\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --global credential.https://github.com.helper \\\n            '!f() { echo \"username=x-access-token\"; printf \"password=%s\\n\" \"${GITHUB_TOKEN}\"; }; f'\n          git fetch origin \"$BASE_REF\"\n          git checkout \"$HEAD_REF\"\n          git merge --no-ff \"origin/$BASE_REF\" -m \"Sync branch before release\"\n          git push origin \"$HEAD_REF\"\n\n  build:\n    name: Build and sign\n    needs: preparation\n    permissions:\n      contents: write\n    uses: ./.github/workflows/build_reusable.yml\n    with:\n      head_ref: ${{ needs.preparation.outputs.head_ref }}\n      version: ${{ needs.preparation.outputs.version }}\n      build_number: ${{ needs.preparation.outputs.build_number }}\n      xcode_version: ${{ needs.preparation.outputs.xcode_version }}\n      code_sign_identity: ${{ needs.preparation.outputs.code_sign_identity }}\n    secrets:\n      BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}\n      P12_PASSWORD:              ${{ secrets.P12_PASSWORD }}\n      KEYCHAIN_PASSWORD:         ${{ secrets.KEYCHAIN_PASSWORD }}\n\n  publish:\n    name: Publish release\n    needs: [preparation, build]\n    runs-on: macos-latest\n    permissions:\n      contents: write\n    env:\n      HEAD_REF: ${{ needs.preparation.outputs.head_ref }}\n      VERSION: ${{ needs.preparation.outputs.version }}\n      IS_BETA: ${{ needs.preparation.outputs.is_beta }}\n    steps:\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          ref: ${{ env.HEAD_REF }}\n\n      - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1\n        with:\n          name: ${{ env.PROJECT_NAME }}.dmg\n          path: Release\n\n      - name: Create embedded release notes\n        env:\n          RELEASE_NOTES: ${{ needs.preparation.outputs.release_notes }}\n        run: printf '%s' \"$RELEASE_NOTES\" > Release/boringNotch.html\n\n      - name: Generate signed appcast\n        env:\n          SPARKLE_PRIVATE_KEY: ${{ secrets.PRIVATE_SPARKLE_KEY }}\n        run: |\n          set -euo pipefail\n          test -x Configuration/sparkle/generate_appcast || {\n            echo \"::error::Configuration/sparkle/generate_appcast missing or not executable\"; exit 1;\n          }\n          CHANNEL_ARGS=()\n          if [[ \"${IS_BETA}\" == \"true\" ]]; then\n            CHANNEL_ARGS=(--channel \"${{ env.BETA_CHANNEL_NAME }}\")\n          fi\n          printf '%s' \"$SPARKLE_PRIVATE_KEY\" | ./Configuration/sparkle/generate_appcast \\\n            --ed-key-file - \\\n            --link \"https://github.com/TheBoredTeam/boring.notch/releases\" \\\n            --download-url-prefix \"https://github.com/TheBoredTeam/boring.notch/releases/download/v${VERSION}/\" \\\n            --embed-release-notes \\\n            \"${CHANNEL_ARGS[@]}\" \\\n            -o updater/appcast.xml \\\n            Release/\n\n      - name: Commit appcast\n        run: |\n          set -euo pipefail\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          if [[ \"${IS_BETA}\" == \"false\" ]]; then\n            git add updater/appcast.xml\n            git commit -m \"Update version to v${VERSION} and appcast\" || true\n            git push origin \"HEAD:${HEAD_REF}\" || true\n          else\n            # Save generated appcast, switch to main, apply and push\n            cp updater/appcast.xml \"$RUNNER_TEMP/appcast.xml\"\n            git fetch origin main\n            git checkout main\n            cp \"$RUNNER_TEMP/appcast.xml\" updater/appcast.xml\n            git add updater/appcast.xml\n            git commit -m \"Update appcast with beta release for v${VERSION}\" || true\n            git push origin main\n          fi\n\n      - name: Create GitHub release\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n          TITLE: ${{ needs.preparation.outputs.title }}\n          NOTES: ${{ needs.preparation.outputs.release_notes_github }}\n        run: |\n          RELEASE_FLAGS=()\n          if [[ \"${IS_BETA}\" == \"true\" ]]; then\n            RELEASE_FLAGS=(--prerelease)\n          fi\n          gh release create \"v${VERSION}\" Release/boringNotch.dmg \\\n            --title \"v${VERSION} - ${TITLE}\" \\\n            --notes \"$NOTES\" \\\n            \"${RELEASE_FLAGS[@]}\"\n\n  upgrade-brew:\n    name: Update Homebrew cask\n    needs: [preparation, publish]\n    runs-on: ubuntu-latest\n    env:\n      VERSION: ${{ needs.preparation.outputs.version }}\n      IS_BETA: ${{ needs.preparation.outputs.is_beta }}\n    steps:\n      - name: Generate cask files\n        run: |\n          set -euo pipefail\n          DMG_URL=\"https://github.com/TheBoredTeam/boring.notch/releases/download/v${VERSION}/boringNotch.dmg\"\n\n          # Retry SHA calculation (release may need a moment to propagate)\n          for attempt in 1 2 3; do\n            if SHA256=$(curl -sL --fail \"$DMG_URL\" | shasum -a 256 | cut -d' ' -f1); then break; fi\n            echo \"Attempt $attempt failed, retrying in 10s...\"; sleep 10\n          done\n          [[ -n \"${SHA256:-}\" ]] || { echo \"::error::Failed to download DMG for SHA256\"; exit 1; }\n\n          write_cask() {\n            local CASK_NAME=\"$1\" DISPLAY_NAME=\"$2\" DESC=\"$3\"\n            cat <<CASK\n          cask \"${CASK_NAME}\" do\n            version \"${VERSION}\"\n            sha256 \"${SHA256}\"\n            url \"${DMG_URL}\"\n            name \"${DISPLAY_NAME}\"\n            desc \"${DESC}\"\n            homepage \"https://github.com/TheBoredTeam/boring.notch\"\n            livecheck do\n              url :url\n              strategy :github_latest\n            end\n            auto_updates true\n            depends_on macos: \">= :sonoma\"\n            app \"boringNotch.app\"\n            \n            postflight do\n              app_path = appdir/\"boringNotch.app\"\n              next unless app_path.exist?\n              system_command \"/usr/bin/xattr\", args: [\"-dr\", \"com.apple.quarantine\", app_path]\n            end\n\n            uninstall quit: \"theboringteam.boringnotch\"\n\n            zap trash: [\n              \"~/Library/Application Scripts/theboringteam.boringnotch/\",\n              \"~/Library/Containers/theboringteam.boringnotch/\",\n            ]\n          end\n          CASK\n          }\n\n          write_cask \"boring-notch@rc\" \"Boring Notch RC\" \\\n            \"Not so boring notch That Rocks (Release Candidate)\" > boring-notch@rc.rb\n          if [[ \"${IS_BETA}\" == \"false\" ]]; then\n            write_cask \"boring-notch\" \"Boring Notch\" \\\n              \"Not so boring notch That Rocks\" > boring-notch.rb\n          fi\n\n      - name: Upload cask artifacts\n        uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0\n        with:\n          name: homebrew-cask-${{ env.VERSION }}\n          path: boring-notch*.rb\n\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          repository: TheBoredTeam/homebrew-boring-notch\n          token: ${{ secrets.HOMEBREW_TAP_TOKEN }}\n          path: homebrew-tap\n\n      - name: Update casks in tap\n        run: |\n          set -euo pipefail\n          cp boring-notch@rc.rb homebrew-tap/Casks/boring-notch@rc.rb\n          COMMIT_MSG=\"Update boring-notch@rc to v${VERSION}\"\n          if [[ \"${IS_BETA}\" == \"false\" ]]; then\n            cp boring-notch.rb homebrew-tap/Casks/boring-notch.rb\n            COMMIT_MSG=\"Update boring-notch and boring-notch@rc to v${VERSION}\"\n          fi\n          cd homebrew-tap\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git add Casks/\n          if git diff --cached --quiet; then\n            echo \"No changes to commit\"\n          else\n            git commit -m \"$COMMIT_MSG\"\n            git push\n          fi\n\n  ending:\n    name: Finalize\n    if: ${{ always() && needs.preparation.result != 'skipped' && needs.check_release.outputs.is_release == 'true' }}\n    needs: [check_release, preparation, build, publish, upgrade-brew]\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write\n    env:\n      ALL_RESULTS: ${{ join(needs.*.result, ',') }}\n      RELEASE_SUCCEEDED: ${{ !contains(join(needs.*.result, ','), 'failure') && !contains(join(needs.*.result, ','), 'cancelled') }}\n    steps:\n      - name: React to trigger comment\n        env:\n          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n        run: |\n          REPO=\"${{ github.repository }}\"\n          COMMENT_ID=\"${{ github.event.comment.id }}\"\n          if [[ \"$ALL_RESULTS\" != *\"failure\"* && \"$ALL_RESULTS\" != *\"cancelled\"* ]]; then\n            REACTION=\"rocket\"\n          else\n            REACTION=\"confused\"\n          fi\n          gh api \"repos/${REPO}/issues/comments/${COMMENT_ID}/reactions\" \\\n            -f content=\"$REACTION\" --silent\n\n      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        if: needs.preparation.outputs.is_beta == 'false' && env.RELEASE_SUCCEEDED == 'true'\n        with:\n          ref: ${{ needs.preparation.outputs.head_ref }}\n          fetch-depth: 0\n\n      - name: Merge PR (stable releases only)\n        if: needs.preparation.outputs.is_beta == 'false' && env.RELEASE_SUCCEEDED == 'true'\n        env:\n          GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }}\n          HEAD_REF: ${{ needs.preparation.outputs.head_ref }}\n          BASE_REF: ${{ needs.preparation.outputs.base_ref }}\n          VERSION: ${{ needs.preparation.outputs.version }}\n        run: |\n          set -euo pipefail\n          git config user.name \"github-actions[bot]\"\n          git config user.email \"github-actions[bot]@users.noreply.github.com\"\n          git config --global credential.https://github.com.helper \\\n            '!f() { echo \"username=x-access-token\"; printf \"password=%s\\n\" \"${GITHUB_TOKEN}\"; }; f'\n          git fetch origin \"$BASE_REF\"\n          git checkout \"$BASE_REF\"\n          git merge --no-ff \"origin/$HEAD_REF\" -m \"Release version v${VERSION}\"\n          git push origin \"$BASE_REF\"\n\n      - name: Summary\n        env:\n          IS_BETA: ${{ needs.preparation.outputs.is_beta }}\n          VERSION: ${{ needs.preparation.outputs.version }}\n          BUILD_NUMBER: ${{ needs.preparation.outputs.build_number }}\n        shell: bash\n        run: |\n          if [[ \"${IS_BETA}\" == \"true\" ]]; then\n            BUILD_TYPE=\"beta\"\n          else\n            BUILD_TYPE=\"stable\"\n          fi\n          if [[ \"${RELEASE_SUCCEEDED}\" == \"true\" ]]; then\n            echo \"✅ Released boringNotch v${VERSION} (${BUILD_TYPE} build ${BUILD_NUMBER})\" >> \"$GITHUB_STEP_SUMMARY\"\n            echo \"🍺 Homebrew cask updated\" >> \"$GITHUB_STEP_SUMMARY\"\n            echo \"📱 Sparkle appcast updated\" >> \"$GITHUB_STEP_SUMMARY\"\n          else\n            echo \"❌ Release failed${VERSION:+ for boringNotch v${VERSION}}\" >> \"$GITHUB_STEP_SUMMARY\"\n          fi\n"
  },
  {
    "path": ".github/workflows/static.yml",
    "content": "# Simple workflow for deploying static content to GitHub Pages\nname: Deploy static content to Pages\n\non:\n  # Runs on pushes targeting the default branch\n  push:\n    branches:\n      - main\n\n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  # Single deploy job since we're just deploying\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n      - name: Setup Pages\n        uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4.0.0\n        with:\n          # Upload entire repository\n          path: 'updater/'\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4\n"
  },
  {
    "path": ".github/workflows/update-version-dropdown.yml",
    "content": "name: Update Version Dropdown in Issue Form\n# Least-privilege: only needs to push the updated issue template file.\npermissions:\n  contents: write\non:\n  push:\n    tags:\n      - '*'\n  workflow_dispatch: {}\n  release:\n    types:\n      - published\njobs:\n  update-dropdown:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2\n        with:\n          ref: ${{ github.event.repository.default_branch }}\n          fetch-depth: 0\n          persist-credentials: true\n\n      - name: Fetch tags\n        run: git fetch --tags\n\n      - name: Get latest 5 versions with placeholder\n        id: tags\n        run: |\n          TAGS=$(git tag --sort=-v:refname | head -n 5 | jq -R -s -c 'split(\"\\n\")[:-1]')\n          VERSIONS=$(echo '[\"Select a version\"]' | jq -c --argjson tags \"$TAGS\" '. + $tags')\n          echo \"versions=$VERSIONS\" >> $GITHUB_OUTPUT\n\n      - name: Issue Forms Dropdown Options\n        uses: ShaMan123/gha-form-dropdown-options@124b08a39f06a6a4fcafdec501ecaca8db019db2 # v2.1.0\n        with:\n          form: .github/ISSUE_TEMPLATE/1-bug-report-form.yml\n          dropdown: version\n          options: ${{ steps.tags.outputs.versions }}\n\n      - name: Commit changes\n        run: |\n          git config user.name \"GitHub Actions Bot\"\n          git config user.email \"actions@github.com\"\n\n          if git diff --quiet; then\n            echo \"No changes to commit.\"\n          else\n            git add .github/ISSUE_TEMPLATE/1-bug-report-form.yml\n            git commit -m \"Update issue form version dropdown with placeholder and latest tags\"\n            git push\n          fi\n"
  },
  {
    "path": ".gitignore",
    "content": "# Xcode\n*.xcuserstate\n*.xcuserdata\n*.xcscheme\n*.xcuserdatad\n*.pbxuser\n*.xccheckout\n*.xcscheme\n*.xcplayground\n*.xcuserdatad\n*.xctest\n*.xcuserdata\n*.xcodeproj/xcshareddata/WorkspaceSettings.xcsettings\n\n# CocoaPods\nPods/\npodfile.lock\n\n# Carthage\nCarthage/Build/\n\n# Swift Package Manager\n.swiftpm/\n.build/\n\n# Derived data\nDerivedData/\n\n# Build output\nbuild/\n*.app\n*.dSYM\n\n# Temporary files\n*.swp\n*.swo\n*.tmp\n*.log\n\n# Other\n*.DS_Store\n*.vscode/\n*.idea/\n\n# User-specific files\n*.xcuserdatad/\n*.xcscheme\n*.xcodeproj/xcuserdata\n*.xcodeproj/project.xcuserdata\n\n# Build artifacts\n*.ipa\n*.xcarchive\n*.dSYM\n\n"
  },
  {
    "path": "BoringNotchXPCHelper/BoringNotchXPCHelper.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<false/>\n</dict>\n</plist>\n"
  },
  {
    "path": "BoringNotchXPCHelper/BoringNotchXPCHelper.swift",
    "content": "//\n//  BoringNotchXPCHelper.swift\n//  BoringNotchXPCHelper\n//\n//  Created by Alexander on 2025-11-16.\n//\n\nimport Foundation\nimport ApplicationServices\nimport IOKit\nimport CoreGraphics\n\nclass BoringNotchXPCHelper: NSObject, BoringNotchXPCHelperProtocol {\n    \n    @objc func isAccessibilityAuthorized(with reply: @escaping (Bool) -> Void) {\n        reply(AXIsProcessTrusted())\n    }\n\n    @objc func requestAccessibilityAuthorization() {\n        let options = [kAXTrustedCheckOptionPrompt.takeUnretainedValue() as String: true] as CFDictionary\n        AXIsProcessTrustedWithOptions(options)\n    }\n\n    @objc func ensureAccessibilityAuthorization(_ promptIfNeeded: Bool, with reply: @escaping (Bool) -> Void) {\n        if AXIsProcessTrusted() {\n            reply(true)\n            return\n        }\n\n        if promptIfNeeded {\n            requestAccessibilityAuthorization()\n        }\n\n        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n            reply(AXIsProcessTrusted())\n        }\n    }\n    \n    private class KeyboardBrightnessClient {\n        private static let keyboardID: UInt64 = 1\n        private var clientInstance: NSObject?\n        private let getSelector = NSSelectorFromString(\"brightnessForKeyboard:\")\n        private let setSelector = NSSelectorFromString(\"setBrightness:forKeyboard:\")\n\n        init() {\n            var loaded = false\n            let bundlePaths = [\n                \"/System/Library/PrivateFrameworks/CoreBrightness.framework\",\n                \"/System/Library/PrivateFrameworks/CoreBrightness.framework/CoreBrightness\"\n            ]\n            for path in bundlePaths where !loaded {\n                if let bundle = Bundle(path: path) {\n                    loaded = bundle.load()\n                }\n            }\n            if loaded, let cls = NSClassFromString(\"KeyboardBrightnessClient\") as? NSObject.Type {\n                clientInstance = cls.init()\n            }\n        }\n\n        var isAvailable: Bool { clientInstance != nil }\n\n        func currentBrightness() -> Float? {\n            guard let clientInstance,\n                  let fn: BrightnessGetter = methodIMP(on: clientInstance, selector: getSelector, as: BrightnessGetter.self)\n            else { return nil }\n            return fn(clientInstance, getSelector, Self.keyboardID)\n        }\n\n        func setBrightness(_ value: Float) -> Bool {\n            guard let clientInstance,\n                  let fn: BrightnessSetter = methodIMP(on: clientInstance, selector: setSelector, as: BrightnessSetter.self)\n            else { return false }\n            return fn(clientInstance, setSelector, value, Self.keyboardID).boolValue\n        }\n\n        private typealias BrightnessGetter = @convention(c) (NSObject, Selector, UInt64) -> Float\n        private typealias BrightnessSetter = @convention(c) (NSObject, Selector, Float, UInt64) -> ObjCBool\n\n        private func methodIMP<T>(on object: NSObject, selector: Selector, as type: T.Type) -> T? {\n            guard let cls = object_getClass(object),\n                  let method = class_getInstanceMethod(cls, selector)\n            else { return nil }\n            let imp = method_getImplementation(method)\n            return unsafeBitCast(imp, to: type)\n        }\n    }\n\n    private static let keyboardClient = KeyboardBrightnessClient()\n\n    @objc func isKeyboardBrightnessAvailable(with reply: @escaping (Bool) -> Void) {\n        reply(Self.keyboardClient.isAvailable)\n    }\n\n    @objc func currentKeyboardBrightness(with reply: @escaping (NSNumber?) -> Void) {\n        reply(Self.keyboardClient.currentBrightness().map { NSNumber(value: $0) })\n    }\n\n    @objc func setKeyboardBrightness(_ value: Float, with reply: @escaping (Bool) -> Void) {\n        reply(Self.keyboardClient.setBrightness(value))\n    }\n    // MARK: - Screen Brightness (moved from client app into helper)\n\n    @objc func isScreenBrightnessAvailable(with reply: @escaping (Bool) -> Void) {\n        var b: Float = 0\n        reply(displayServicesGetBrightness(displayID: CGMainDisplayID(), out: &b) || ioServiceFor(displayID: CGMainDisplayID()) != nil)\n    }\n\n    @objc func currentScreenBrightness(with reply: @escaping (NSNumber?) -> Void) {\n        var b: Float = 0\n        if displayServicesGetBrightness(displayID: CGMainDisplayID(), out: &b) {\n            reply(NSNumber(value: b))\n            return\n        }\n        if let io = ioServiceFor(displayID: CGMainDisplayID()) {\n            var level: Float = 0\n            if IODisplayGetFloatParameter(io, 0, kIODisplayBrightnessKey as CFString, &level) == kIOReturnSuccess {\n                IOObjectRelease(io)\n                reply(NSNumber(value: level))\n                return\n            }\n            IOObjectRelease(io)\n        }\n        reply(nil)\n    }\n\n    @objc func setScreenBrightness(_ value: Float, with reply: @escaping (Bool) -> Void) {\n        let clamped = max(0, min(1, value))\n        if displayServicesSetBrightness(displayID: CGMainDisplayID(), value: clamped) {\n            reply(true)\n            return\n        }\n        if let io = ioServiceFor(displayID: CGMainDisplayID()) {\n            let ok = IODisplaySetFloatParameter(io, 0, kIODisplayBrightnessKey as CFString, clamped) == kIOReturnSuccess\n            IOObjectRelease(io)\n            reply(ok)\n            return\n        }\n        reply(false)\n    }\n\n    // MARK: - Private helpers for DisplayServices / IOKit access\n    private func displayServicesGetBrightness(displayID: CGDirectDisplayID, out: inout Float) -> Bool {\n        guard let sym = dlsym(DisplayServicesHandle.handle, \"DisplayServicesGetBrightness\") else { return false }\n        typealias Fn = @convention(c) (CGDirectDisplayID, UnsafeMutablePointer<Float>) -> Int32\n        let fn = unsafeBitCast(sym, to: Fn.self)\n        var tmp: Float = 0\n        let r = fn(displayID, &tmp)\n        if r == 0 { out = tmp; return true }\n        return false\n    }\n\n    private func displayServicesSetBrightness(displayID: CGDirectDisplayID, value: Float) -> Bool {\n        guard let sym = dlsym(DisplayServicesHandle.handle, \"DisplayServicesSetBrightness\") else { return false }\n        typealias Fn = @convention(c) (CGDirectDisplayID, Float) -> Int32\n        let fn = unsafeBitCast(sym, to: Fn.self)\n        return fn(displayID, value) == 0\n    }\n\n    private func ioServiceFor(displayID: CGDirectDisplayID) -> io_service_t? {\n        var iterator: io_iterator_t = 0\n        guard IOServiceGetMatchingServices(kIOMainPortDefault, IOServiceMatching(\"IODisplayConnect\"), &iterator) == kIOReturnSuccess else { return nil }\n        defer { IOObjectRelease(iterator) }\n\n        while case let service = IOIteratorNext(iterator), service != 0 {\n            let info = IODisplayCreateInfoDictionary(service, 0).takeRetainedValue() as NSDictionary\n            if let vendorID = info[kDisplayVendorID] as? UInt32,\n               let productID = info[kDisplayProductID] as? UInt32,\n               vendorID == CGDisplayVendorNumber(displayID),\n               productID == CGDisplayModelNumber(displayID) {\n                return service\n            }\n            IOObjectRelease(service)\n        }\n        return nil\n    }\n\n    // MARK: - Helper handle for private framework\n    private enum DisplayServicesHandle {\n        static let handle: UnsafeMutableRawPointer? = {\n            let paths = [\n                \"/System/Library/PrivateFrameworks/DisplayServices.framework/DisplayServices\",\n                \"/System/Library/PrivateFrameworks/DisplayServices.framework/Versions/Current/DisplayServices\"\n            ]\n            for p in paths {\n                if let h = dlopen(p, RTLD_LAZY) { return h }\n            }\n            return nil\n        }()\n    }\n}\n"
  },
  {
    "path": "BoringNotchXPCHelper/BoringNotchXPCHelperProtocol.swift",
    "content": "//\n//  BoringNotchXPCHelperProtocol.swift\n//  BoringNotchXPCHelper\n//\n//  Created by Alexander on 2025-11-16.\n//\n\nimport Foundation\n\n/// The protocol that this service will vend as its API. This protocol will also need to be visible to the process hosting the service.\n@objc protocol BoringNotchXPCHelperProtocol {\n    func isAccessibilityAuthorized(with reply: @escaping (Bool) -> Void)\n    func requestAccessibilityAuthorization()\n    func ensureAccessibilityAuthorization(_ promptIfNeeded: Bool, with reply: @escaping (Bool) -> Void)\n    // Keyboard backlight / CoreBrightness access (performed by the helper)\n    func isKeyboardBrightnessAvailable(with reply: @escaping (Bool) -> Void)\n    func currentKeyboardBrightness(with reply: @escaping (NSNumber?) -> Void)\n    func setKeyboardBrightness(_ value: Float, with reply: @escaping (Bool) -> Void)\n    // Screen brightness access (performed by the helper)\n    func isScreenBrightnessAvailable(with reply: @escaping (Bool) -> Void)\n    func currentScreenBrightness(with reply: @escaping (NSNumber?) -> Void)\n    func setScreenBrightness(_ value: Float, with reply: @escaping (Bool) -> Void)\n}\n\n/*\n To use the service from an application or other process, use NSXPCConnection to establish a connection to the service by doing something like this:\n\n     connectionToService = NSXPCConnection(serviceName: \"theboringteam.boringnotch.BoringNotchXPCHelper\")\n     connectionToService.remoteObjectInterface = NSXPCInterface(with: (any BoringNotchXPCHelperProtocol).self)\n     connectionToService.resume()\n\n Once you have a connection to the service, you can use it like this:\n\n     if let proxy = connectionToService.remoteObjectProxy as? BoringNotchXPCHelperProtocol {\n         proxy.performCalculation(firstNumber: 23, secondNumber: 19) { result in\n             NSLog(\"Result of calculation is: \\(result)\")\n         }\n     }\n\n And, when you are finished with the service, clean up the connection like this:\n\n     connectionToService.invalidate()\n*/\n"
  },
  {
    "path": "BoringNotchXPCHelper/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>XPCService</key>\n\t<dict>\n\t\t<key>ServiceType</key>\n\t\t<string>Application</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "BoringNotchXPCHelper/main.swift",
    "content": "//\n//  main.swift\n//  BoringNotchXPCHelper\n//\n//  Created by Alexander on 2025-11-16.\n//\n\nimport Foundation\n\nclass ServiceDelegate: NSObject, NSXPCListenerDelegate {\n    \n    /// This method is where the NSXPCListener configures, accepts, and resumes a new incoming NSXPCConnection.\n    func listener(_ listener: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {\n        \n        // Configure the connection.\n        // First, set the interface that the exported object implements.\n        newConnection.exportedInterface = NSXPCInterface(with: (any BoringNotchXPCHelperProtocol).self)\n        \n        // Next, set the object that the connection exports. All messages sent on the connection to this service will be sent to the exported object to handle. The connection retains the exported object.\n        let exportedObject = BoringNotchXPCHelper()\n        newConnection.exportedObject = exportedObject\n        \n        // Resuming the connection allows the system to deliver more incoming messages.\n        newConnection.resume()\n        \n        // Returning true from this method tells the system that you have accepted this connection. If you want to reject the connection for some reason, call invalidate() on the connection and return false.\n        return true\n    }\n}\n\n// Create the delegate for the service.\nlet delegate = ServiceDelegate()\n\n// Set up the one NSXPCListener for this service. It will handle all incoming connections.\nlet listener = NSXPCListener.service()\nlistener.delegate = delegate\n\n// Resuming the serviceListener starts this service. This method does not return.\nlistener.resume()\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing\n\nThank you for taking the time to contribute! ❤️\n\nThese guidelines help streamline the contribution process for everyone involved. By following them, you'll make it easier for maintainers to review your work and collaborate with you effectively.\n\nYou can contribute in many ways: writing code, improving documentation, reporting bugs, requesting features, or creating tutorials and blog posts. Every contribution, large or small, helps make Boring Notch better.\n\n## Table of Contents\n\n- [Localizations](#localizations)\n- [Contributing Code](#contributing-code)\n  - [Before You Start](#before-you-start)\n  - [Setting Up Your Environment](#setting-up-your-environment)\n  - [Making Changes](#making-changes)\n  - [Pull Requests](#pull-requests)\n<!-- - [Code Style Guidelines](#code-style-guidelines) -->\n- [Reporting Bugs](#reporting-bugs)\n- [Feature Requests](#feature-requests)\n- [Getting Help](#getting-help)\n\n## Localizations\n\nPlease submit all translations to [Crowdin](https://crowdin.com/project/boring-notch). New strings added to the `dev` branch from code changes will sync automatically to Crowdin, and Crowdin will automatically open a new PR with translations to allow us to integrate them.\n\n## Contributing Code\n\n### Before You Start\n\n- **Check existing issues**: Before creating a new issue or starting work, search existing issues to avoid duplicates.\n- **Discuss major changes**: For significant features or major changes, please open an issue first to discuss your approach with maintainers and the community.\n<!-- - **Review the code style**: Familiarize yourself with our code style guidelines below to ensure consistency. -->\n\n> [!IMPORTANT]\n> All code contributions must be based on the `dev` branch, not `main`. Documentation changes should be based on `main` instead.\n\n### Setting Up Your Environment\n\n1. **Fork the repository**: Click the \"Fork\" button at the top of the repository page to create your own copy.\n\n2. **Clone your fork**:\n   ```bash\n   git clone https://github.com/{your-username}/boring.notch.git\n   cd boring.notch\n   ```\n   Replace `{your-username}` with your GitHub username.\n\n3. **Switch to the `dev` branch**:\n   ```bash\n   git checkout dev\n   ```\n   All code contributions must be based on the `dev` branch, not `main`. Documentation changes should be based on `main` instead.\n\n5. **Create a new feature branch**:\n   ```bash\n   git checkout -b feature/{your-feature-name}\n   ```\n   Replace `{your-feature-name}` with a descriptive name. Use lowercase letters, numbers, and hyphens only (e.g., `feature/add-dark-mode` or `fix/notification-crash`).\n\n### Making Changes\n\n1. **Make your changes**: Implement your feature or bug fix. Write clean, well-documented code <!-- following the project's style guidelines. -->\n\n2. **Test your changes**: Ensure your changes work as expected and don't break existing functionality.\n\n3. **Commit your changes**:\n   ```bash\n   git add .\n   git commit -m \"Add descriptive commit message\"\n   ```\n   Write clear, concise commit messages that explain what your changes do and why.\n\n4. **Keep your branch up to date**:\n   Regularly sync your branch with the latest changes from the `dev` branch to avoid conflicts.\n\n5. **Push to your fork**:\n   ```bash\n   git push origin feature/{your-feature-name}\n   ```\n\n### Pull Requests\n\n1. **Create a pull request**: Go to the original repository and click \"New Pull Request.\" Select your feature branch and set the base branch to `dev`.\n\n2. **Write a detailed description**: Your PR should include:\n   - A clear title summarizing the changes\n   - A detailed description of what was changed and why\n   - Reference to any related issues (e.g., \"Fixes #123\" or \"Relates to #456\")\n   - Screenshots or screen recordings for UI changes\n\n3. **Respond to feedback**: Maintainers may request changes.\n\n4. **Be patient**: Reviews take time. Maintainers will get to your PR as soon as they can.\n\n<!-- ## Code Style Guidelines\n\n- Follow the existing code style and conventions used in the project\n- Write clear, self-documenting code with meaningful variable and function names\n- Add comments for complex logic or non-obvious implementations\n- Ensure your code is properly formatted before committing\n- Remove any debugging code, console logs, or commented-out code before submitting -->\n\n## Reporting Bugs\n\nWhen reporting bugs, please include:\n\n- A clear, descriptive title\n- Steps to reproduce the issue\n- Expected behavior vs. actual behavior\n- Screenshots or error messages if applicable\n- Your environment details (OS version, app version, etc.)\n\n## Feature Requests\n\nFeature requests are welcome! Please:\n\n- Check if the feature has already been requested\n- Clearly describe the feature and its use case\n- Explain why this feature would be valuable to users\n- Be open to discussion and alternative approaches\n\n## Getting Help\n\nIf you need help or have questions:\n\n- Check the project documentation\n- Search existing issues for similar questions\n- Open a new issue with the \"question\" label\n- Join our [community Discord server](https://discord.com/servers/boring-notch-1269588937320566815)\n\n---\n\nThank you for contributing to Boring Notch! Your efforts help make this project better for everyone. 🎉\n"
  },
  {
    "path": "Configuration/dmg/create_dmg.sh",
    "content": "#!/usr/bin/env bash\nset -euo pipefail\n\n# Minimal wrapper to create a DMG using dmgbuild.\n# Usage: ./create_dmg.sh <app_path> <dmg_output> <volume_name>\n\nAPP_PATH=\"${1:?App path required}\"\nDMG_OUTPUT=\"${2:?DMG output path required}\"\nVOLUME_NAME=\"${3:?Volume name required}\"\n\nSCRIPT_DIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nSETTINGS=\"$SCRIPT_DIR/dmgbuild_settings.py\"\n\nBACKGROUND_DIR=\"$SCRIPT_DIR/.background\"\n\ndie() {\n  echo \"Error: $*\" >&2\n  exit 1\n}\n\nabs_path() {\n  python3 -c 'import os,sys; print(os.path.abspath(sys.argv[1]))' \"$1\"\n}\n\nensure_dmgbuild_and_badge_support() {\n  if command -v dmgbuild >/dev/null 2>&1; then\n    return 0\n  fi\n\n  if ! command -v pip3 >/dev/null 2>&1; then\n    die \"dmgbuild is not installed and pip3 is not available. Please install dmgbuild.\"\n  fi\n\n  echo \"dmgbuild not found — installing via pip3 (user scope)...\"\n  python3 -m pip install --user \"dmgbuild[badge_icons]\" || python3 -m pip install \"dmgbuild[badge_icons]\"\n  USER_BIN=\"$(python3 -c 'import site,sys; print(site.getuserbase() + \"/bin\")')\"\n  export PATH=\"$USER_BIN:$PATH\"\n}\n\nfind_app_icns() {\n  local app=\"$1\"\n  local info_plist=\"$app/Contents/Info.plist\"\n\n  if [ ! -f \"$info_plist\" ]; then\n    return 1\n  fi\n\n  local icon_file=\"\"\n  icon_file=\"$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconFile' \"$info_plist\" 2>/dev/null || true)\"\n  if [ -z \"$icon_file\" ]; then\n    icon_file=\"$(/usr/libexec/PlistBuddy -c 'Print :CFBundleIconName' \"$info_plist\" 2>/dev/null || true)\"\n  fi\n\n  if [ -n \"$icon_file\" ]; then\n    if [[ \"$icon_file\" != *.icns ]]; then\n      icon_file=\"$icon_file.icns\"\n    fi\n    if [ -f \"$app/Contents/Resources/$icon_file\" ]; then\n      echo \"$app/Contents/Resources/$icon_file\"\n      return 0\n    fi\n  fi\n\n  # Fallback: any .icns inside the app bundle\n  local candidate\n  candidate=\"$(find \"$app/Contents/Resources\" -maxdepth 1 -name '*.icns' -print -quit 2>/dev/null || true)\"\n  if [ -n \"$candidate\" ] && [ -f \"$candidate\" ]; then\n    echo \"$candidate\"\n    return 0\n  fi\n\n  return 1\n}\n\nif [ ! -f \"$SETTINGS\" ]; then\n  die \"dmgbuild settings not found: $SETTINGS\"\nfi\n\nensure_dmgbuild_and_badge_support\n\nexport DMG_APP_PATH=\"$(abs_path \"$APP_PATH\")\"\nexport DMG_VOLUME_NAME=\"$VOLUME_NAME\"\n\nBACKGROUND_TIFF=\"$BACKGROUND_DIR/background.tiff\"\n\nexport DMG_BACKGROUND=\"$(abs_path \"$BACKGROUND_TIFF\")\"\n\n# Badge icon: use the app's icon for badging the volume icon\nif DMG_ICON_ICNS=\"$(find_app_icns \"$DMG_APP_PATH\" 2>/dev/null)\"; then\n  export DMG_BADGE_ICON=\"$(abs_path \"$DMG_ICON_ICNS\")\"\n  echo \"Using badge icon for DMG volume.\"\nelse\n  echo \"No app icon found, skipping badge.\"\nfi\n\necho \"Creating DMG via dmgbuild: app=$DMG_APP_PATH output=$DMG_OUTPUT volume=$DMG_VOLUME_NAME\"\n\n# Validate inputs early to give clearer errors for common typos\nif [ ! -e \"$DMG_APP_PATH\" ]; then\n  echo \"Error: App path not found: $DMG_APP_PATH\" >&2\n  echo \"Make sure you passed the correct .app path (e.g. Release/boringNotch.app)\" >&2\n  exit 2\nfi\n\nif [ ! -d \"$DMG_APP_PATH\" ]; then\n  echo \"Error: App path exists but is not a directory: $DMG_APP_PATH\" >&2\n  exit 3\nfi\n\ndmgbuild -s \"$SETTINGS\" \"$DMG_VOLUME_NAME\" \"$DMG_OUTPUT\"\n\nexit $?\n"
  },
  {
    "path": "Configuration/dmg/dmgbuild_settings.py",
    "content": "import os\n\n# dmgbuild settings file. This is read by the `dmgbuild` CLI (or Python API).\n# It uses environment variables exported by the shell wrapper script:\n#  - DMG_APP_PATH: path to the .app bundle to put in the DMG\n#  - DMG_VOLUME_NAME: volume name to display when the DMG is mounted\n#  - DMG_BACKGROUND: absolute path to the background image to use\n\nAPP_PATH = os.environ.get('DMG_APP_PATH')\nVOLUME_NAME = os.environ.get('DMG_VOLUME_NAME', 'boringNotch')\nBACKGROUND = os.environ.get('DMG_BACKGROUND', '')\nBADGE_ICON = os.environ.get('DMG_BADGE_ICON', '')\n\n# If DMG_BACKGROUND not provided, default to the hiDPI TIFF in .background.\nif not BACKGROUND:\n    base = os.path.join(os.path.dirname(__file__), '.background', 'background.tiff')\n\n# Basic DMG metadata\nvolume_name = VOLUME_NAME\nformat = 'UDZO'\ncompression_level = 9\n\n# Files and symlinks to include in the DMG\nfiles = [APP_PATH] if APP_PATH else []\nsymlinks = {'Applications': '/Applications'}\n\n# Background image path (dmgbuild will copy this file into the DMG's .background)\nbackground = BACKGROUND\n\n\n# Window rectangle: ((left, top), (right, bottom))\nwindow_rect = ((0, 0), (660, 400))\n\n# Icon size (points)\nicon_size = 128\n\n# Icon locations: map filename (or bundle name) -> (x, y) in window coords\napp_basename = os.path.basename(APP_PATH) if APP_PATH else 'boringNotch.app'\nicon_locations = {\n    app_basename: (150, 180),\n    'Applications': (510, 180),\n}\n\n# Misc Finder options\nshow_statusbar = False\nshow_tabview = False\nshow_toolbar = False\n\n# Optionally set a custom icon for the DMG volume (leave empty to skip)\nif BADGE_ICON and os.path.exists(BADGE_ICON):\n    badge_icon = BADGE_ICON\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": "README.md",
    "content": "<h1 align=\"center\">\n  <br>\n  <a href=\"http://theboring.name\"><img src=\"https://framerusercontent.com/images/RFK4vs0kn8pRMuOO58JeyoemXA.png?scale-down-to=256\" alt=\"Boring Notch\" width=\"150\"></a>\n  <br>\n  Boring Notch\n  <br>\n</h1>\n\n\n<p align=\"center\">\n  <a title=\"Crowdin\" target=\"_blank\" href=\"https://crowdin.com/project/boring-notch\"><img src=\"https://badges.crowdin.net/boring-notch/localized.svg\"></a>\n  <img src=\"https://github.com/TheBoredTeam/boring.notch/actions/workflows/cicd.yml/badge.svg\" alt=\"TheBoringNotch Build & Test\" style=\"margin-right: 10px;\" />\n  <a href=\"https://discord.gg/c8JXA7qrPm\">\n    <img src=\"https://dcbadge.limes.pink/api/server/https://discord.gg/c8JXA7qrPm?style=flat\" alt=\"Discord Badge\" />\n  </a>\n  <a href=\"https://www.ko-fi.com/alexander5015\">\n    <img src=\"https://srv-cdn.himpfen.io/badges/kofi/kofi-flat.svg\" alt=\"Ko-Fi\" />\n  </a>\n</p>\n\n<!--Welcome to **Boring.Notch**, the coolest way to make your MacBook's notch the star of the show! Forget about those boring status bars—our notch turns into a dynamic music control center, complete with a snazzy visualizer and all the music controls you need. It's like having a mini concert right at the top of your screen! -->\n\nSay hello to **Boring Notch**, the coolest way to make your MacBook’s notch the star of the show! Say goodbye to boring status bars: with Boring Notch, your notch transforms into a dynamic music control center, complete with a vibrant visualizer and all the essential music controls you need. But that’s just the start! Boring Notch also offers calendar integration, a handy file shelf with AirDrop support, a complete MacOS HUD replacement and more!\n\n<p align=\"center\">\n  <img src=\"https://github.com/user-attachments/assets/2d5f69c1-6e7b-4bc2-a6f1-bb9e27cf88a8\" alt=\"Demo GIF\" />\n</p>\n\n<!--https://github.com/user-attachments/assets/19b87973-4b3a-4853-b532-7e82d1d6b040-->\n---\n<!--## Table of Contents\n- [Installation](#installation)\n- [Usage](#usage)\n- [Roadmap](#-roadmap)\n- [Building from Source](#building-from-source)\n- [Contributing](#-contributing)\n- [Join our Discord Server](#join-our-discord-server)\n- [Star History](#star-history)\n- [Buy us a coffee!](#buy-us-a-coffee)\n- [Acknowledgments](#-acknowledgments)-->\n\n## Installation\n\n**System Requirements:**\n- macOS **14 Sonoma** or later\n- Apple Silicon or Intel Mac\n\n---\n\n### Option 1: Download and Install Manually\n\n<a href=\"https://github.com/TheBoredTeam/boring.notch/releases/latest/download/boringNotch.dmg\" target=\"_self\"><img width=\"200\" src=\"https://github.com/user-attachments/assets/e3179be1-8416-4b8a-b417-743e1ecc67d6\" alt=\"Download for macOS\" /></a>\n\nOnce downloaded, open the `.dmg` and move **Boring Notch** to your `/Applications` folder.\n\n> [!IMPORTANT]\n> We don't have an Apple Developer account (yet 👀), so macOS will warn you that Boring Notch is from an unidentified developer on first launch. This is expected behavior.\n>\n> You'll need to bypass this before the app will open. You only need to do this once. Use one of the methods below.\n\n---\n\n#### Recommended: Terminal (Always Works)\n\nThis is the fastest and simplest method. It requires just one command and works reliably for all users, unlike System Settings, which occasionally doesn't.\n\nAfter moving Boring Notch to your Applications folder, run:\n\n```bash\nxattr -dr com.apple.quarantine /Applications/boringNotch.app\n```\n\nThen open the app normally.\n\n---\n\n#### Alternative: System Settings\n\n> [!NOTE]\n> This method doesn't work for all users. If this doesn't work, use the Terminal method above.\n\n1. Try to open the app — you'll see a security warning.\n2. Click **OK** to dismiss it.\n3. Open **System Settings** > **Privacy & Security**.\n4. Scroll to the bottom and click **Open Anyway** next to the Boring Notch warning.\n5. Confirm if prompted.\n\n---\n\n### Option 2: Install via Homebrew\n\nYou can also install using [Homebrew](https://brew.sh). The Homebrew installation automatically bypasses the macOS security warning described above.\n\n```bash\nbrew install --cask TheBoredTeam/boring-notch/boring-notch\n```\n\n## Usage\n\n- Launch the app, and voilà—your notch is now the coolest part of your screen.\n- Hover over the notch to see it expand and reveal all its secrets.\n- Use the controls to manage your music like a rockstar.\n- Click the star in your menu bar to customize your notch to your heart's content.\n\n## 📋 Roadmap\n- [x] Playback live activity 🎧\n- [x] Calendar integration 📆\n- [x] Reminders integration ☑️\n- [x] Mirror 📷\n- [x] Charging indicator and current percentage 🔋\n- [x] Customizable gesture control 👆🏻\n- [x] Shelf functionality with AirDrop 📚\n- [x] Notch sizing customization, finetuning on different display sizes 🖥️\n- [x] System HUD replacements (volume, brightness, backlight) 🎚️💡⌨️\n- [ ] Bluetooth Live Activity (connect/disconnect for bluetooth devices) \n- [ ] Weather integration ⛅️\n- [ ] Customizable Layout options 🛠️\n- [ ] Lock Screen Widgets 🔒\n- [ ] Extension system 🧩\n- [ ] Notifications (under consideration) 🔔\n<!-- - [ ] Clipboard history manager 📌 `Extension` -->\n<!-- - [ ] Download indicator of different browsers (Safari, Chromium browsers, Firefox) 🌍 `Extension`-->\n<!-- - [ ] Customizable function buttons 🎛️ -->\n<!-- - [ ] App switcher 🪄 -->\n\n<!-- ## 🧩 Extensions\n> [!NOTE]\n> We’re hard at work on some awesome extensions! Stay tuned, and we’ll keep you updated as soon as they’re released. -->\n\n## Building from Source\n\n### Prerequisites\n\n- **macOS 14 or later**: If you’re not on the latest macOS, we might need to send a search party.\n- **Xcode 16 or later**: This is where the magic happens, so make sure it’s up-to-date.\n\n### Installation\n\n1. **Clone the Repository**:\n   ```bash\n   git clone https://github.com/TheBoredTeam/boring.notch.git\n   cd boring.notch\n   ```\n\n2. **Open the Project in Xcode**:\n   ```bash\n   open boringNotch.xcodeproj\n   ```\n\n3. **Build and Run**:\n    - Click the \"Run\" button or press `Cmd + R`. Watch the magic unfold!\n\n## 🤝 Contributing\n\nWe’re all about good vibes and awesome contributions! Read [CONTRIBUTING.md](CONTRIBUTING.md) to learn how you can join the fun!\n\n## Join our Discord Server\n\n<a href=\"https://discord.gg/GvYcYpAKTu\" target=\"_blank\"><img src=\"https://iili.io/28m3GHv.png\" alt=\"Join The Boring Server!\" style=\"height: 60px !important;width: 217px !important;\" ></a>\n\n## Star History\n\n<a href=\"https://www.star-history.com/#TheBoredTeam/boring.notch&Timeline\">\n <picture>\n   <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://api.star-history.com/svg?repos=TheBoredTeam/boring.notch&type=Timeline&theme=dark\" />\n   <source media=\"(prefers-color-scheme: light)\" srcset=\"https://api.star-history.com/svg?repos=TheBoredTeam/boring.notch&type=Timeline\" />\n   <img alt=\"Star History Chart\" src=\"https://api.star-history.com/svg?repos=TheBoredTeam/boring.notch&type=Timeline\" />\n </picture>\n</a>\n\n## Support us on Ko-fi!\n<!-- <a href=\"https://www.buymeacoffee.com/jfxh67wvfxq\" target=\"_blank\"><img src=\"https://cdn.buymeacoffee.com/buttons/v2/default-red.png\" alt=\"Buy Me A Coffee\" style=\"height: 60px !important;width: 217px !important;\" ></a> -->\n<a href=\"https://www.ko-fi.com/alexander5015\" target=\"_blank\"><img src=\"https://github.com/user-attachments/assets//a76175ef-7e93-475a-8b67-4922ba5964c2\" alt=\"Support us on Ko-fi\" style=\"height: 70px !important;width: 346px !important;\" ></a>\n\n## 🎉 Acknowledgments\n\nWe would like to express our gratitude to the authors and maintainers of the open-source projects that made this possible. \n\n## Notable Projects\n- **[MediaRemoteAdapter](https://github.com/ungive/mediaremote-adapter)** –  An open-source project that allowed us to use the Now Playing source in macOS 15.4+\n- **[NotchDrop](https://github.com/Lakr233/NotchDrop)** – An open-source project that has been instrumental in developing the first version of the \"Shelf\" feature in Boring Notch.\n\nFor a full list of licenses and attributions, please see the [Third-Party Licenses](./THIRD_PARTY_LICENSES.md) file.\n\n### Icon credits: [@maxtron95](https://github.com/maxtron95)\n### Website credits: [@himanshhhhuv](https://github.com/himanshhhhuv)\n\n- **SwiftUI**: For making us look like coding wizards.\n- **You**: For being awesome and checking out **boring.notch**!\n\n\n"
  },
  {
    "path": "SECURITY.md",
    "content": "# Security Policy\n\n## Reporting a Vulnerability\n\nThe Bored Team and community take security bugs in Boring Notch seriously. We appreciate your efforts to responsibly disclose your findings, and will make every effort to acknowledge your contributions.\n\nTo report a security issue, please use the GitHub Security Advisory [\"Report a Vulnerability\"](https://github.com/TheBoredTeam/boring.notch/security/advisories/new) tab.\n\nThe Bored Team will send a response indicating the next steps in handling your report. After the initial reply to your report, we will keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance.\n\nReport security bugs in third-party dependencies to the person or team maintaining the package or dependency.\n"
  },
  {
    "path": "THIRD_PARTY_LICENSES",
    "content": "-----------------------------------------------------------------------------\n                        BSD 3-Clause License\n        applies to:\n        - MediaRemoteAdapter, Copyright (c) 2025, Jonas van den Berg and contributors\n-----------------------------------------------------------------------------\n\nRedistribution and use in source and binary forms, with or without\nmodification, are permitted provided that the following conditions are met:\n\n1. Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n2. Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n3. Neither the name of the copyright holder nor the names of its\n   contributors may be used to endorse or promote products derived from\n   this software without specific prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\"\nAND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE\nFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL\nDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR\nSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\nCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,\nOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE\nOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n-----------------------------------------------------------------------------\n                        The MIT License (MIT)\n        applies to:\n        - Calendr, Copyright (c) 2021 Carlos César Neves Enumo\n        - DynamicNotchKit, Copyright (c) 2025 Kai Azim\n        - NotchDrop Copyright (c) 2024 Lakr Aream\n-----------------------------------------------------------------------------\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n\n-----------------------------------------------------------------------------\n                Mozilla Public License Version 2.0\n        applies to:\n        - Parrot, Copyright (c) 2018, Aditya Vaidyam and Contributors\n-----------------------------------------------------------------------------\n\n1. Definitions\n--------------\n\n1.1. \"Contributor\"\nmeans each individual or legal entity that creates, contributes to\nthe creation of, or owns Covered Software.\n\n1.2. \"Contributor Version\"\nmeans the combination of the Contributions of others (if any) used\nby a Contributor and that particular Contributor's Contribution.\n\n1.3. \"Contribution\"\nmeans Covered Software of a particular Contributor.\n\n1.4. \"Covered Software\"\nmeans Source Code Form to which the initial Contributor has attached\nthe notice in Exhibit A, the Executable Form of such Source Code\nForm, and Modifications of such Source Code Form, in each case\nincluding portions thereof.\n\n1.5. \"Incompatible With Secondary Licenses\"\nmeans\n\n(a) that the initial Contributor has attached the notice described\nin Exhibit B to the Covered Software; or\n\n(b) that the Covered Software was made available under the terms of\nversion 1.1 or earlier of the License, but not also under the\nterms of a Secondary License.\n\n1.6. \"Executable Form\"\nmeans any form of the work other than Source Code Form.\n\n1.7. \"Larger Work\"\nmeans a work that combines Covered Software with other material, in\na separate file or files, that is not Covered Software.\n\n1.8. \"License\"\nmeans this document.\n\n1.9. \"Licensable\"\nmeans having the right to grant, to the maximum extent possible,\nwhether at the time of the initial grant or subsequently, any and\nall of the rights conveyed by this License.\n\n1.10. \"Modifications\"\nmeans any of the following:\n\n(a) any file in Source Code Form that results from an addition to,\ndeletion from, or modification of the contents of Covered\nSoftware; or\n\n(b) any new file in Source Code Form that contains any Covered\nSoftware.\n\n1.11. \"Patent Claims\" of a Contributor\nmeans any patent claim(s), including without limitation, method,\nprocess, and apparatus claims, in any patent Licensable by such\nContributor that would be infringed, but for the grant of the\nLicense, by the making, using, selling, offering for sale, having\nmade, import, or transfer of either its Contributions or its\nContributor Version.\n\n1.12. \"Secondary License\"\nmeans either the GNU General Public License, Version 2.0, the GNU\nLesser General Public License, Version 2.1, the GNU Affero General\nPublic License, Version 3.0, or any later versions of those\nlicenses.\n\n1.13. \"Source Code Form\"\nmeans the form of the work preferred for making modifications.\n\n1.14. \"You\" (or \"Your\")\nmeans an individual or a legal entity exercising rights under this\nLicense. For legal entities, \"You\" includes any entity that\ncontrols, is controlled by, or is under common control with You. For\npurposes of this definition, \"control\" means (a) the power, direct\nor indirect, to cause the direction or management of such entity,\nwhether by contract or otherwise, or (b) ownership of more than\nfifty percent (50%) of the outstanding shares or beneficial\nownership of such entity.\n\n2. License Grants and Conditions\n--------------------------------\n\n2.1. Grants\n\nEach Contributor hereby grants You a world-wide, royalty-free,\nnon-exclusive license:\n\n(a) under intellectual property rights (other than patent or trademark)\nLicensable by such Contributor to use, reproduce, make available,\nmodify, display, perform, distribute, and otherwise exploit its\nContributions, either on an unmodified basis, with Modifications, or\nas part of a Larger Work; and\n\n(b) under Patent Claims of such Contributor to make, use, sell, offer\nfor sale, have made, import, and otherwise transfer either its\nContributions or its Contributor Version.\n\n2.2. Effective Date\n\nThe licenses granted in Section 2.1 with respect to any Contribution\nbecome effective for each Contribution on the date the Contributor first\ndistributes such Contribution.\n\n2.3. Limitations on Grant Scope\n\nThe licenses granted in this Section 2 are the only rights granted under\nthis License. No additional rights or licenses will be implied from the\ndistribution or licensing of Covered Software under this License.\nNotwithstanding Section 2.1(b) above, no patent license is granted by a\nContributor:\n\n(a) for any code that a Contributor has removed from Covered Software;\nor\n\n(b) for infringements caused by: (i) Your and any other third party's\nmodifications of Covered Software, or (ii) the combination of its\nContributions with other software (except as part of its Contributor\nVersion); or\n\n(c) under Patent Claims infringed by Covered Software in the absence of\nits Contributions.\n\nThis License does not grant any rights in the trademarks, service marks,\nor logos of any Contributor (except as may be necessary to comply with\nthe notice requirements in Section 3.4).\n\n2.4. Subsequent Licenses\n\nNo Contributor makes additional grants as a result of Your choice to\ndistribute the Covered Software under a subsequent version of this\nLicense (see Section 10.2) or under the terms of a Secondary License (if\npermitted under the terms of Section 3.3).\n\n2.5. Representation\n\nEach Contributor represents that the Contributor believes its\nContributions are its original creation(s) or it has sufficient rights\nto grant the rights to its Contributions conveyed by this License.\n\n2.6. Fair Use\n\nThis License is not intended to limit any rights You have under\napplicable copyright doctrines of fair use, fair dealing, or other\nequivalents.\n\n2.7. Conditions\n\nSections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted\nin Section 2.1.\n\n3. Responsibilities\n-------------------\n\n3.1. Distribution of Source Form\n\nAll distribution of Covered Software in Source Code Form, including any\nModifications that You create or to which You contribute, must be under\nthe terms of this License. You must inform recipients that the Source\nCode Form of the Covered Software is governed by the terms of this\nLicense, and how they can obtain a copy of this License. You may not\nattempt to alter or restrict the recipients' rights in the Source Code\nForm.\n\n3.2. Distribution of Executable Form\n\nIf You distribute Covered Software in Executable Form then:\n\n(a) such Covered Software must also be made available in Source Code\nForm, as described in Section 3.1, and You must inform recipients of\nthe Executable Form how they can obtain a copy of such Source Code\nForm by reasonable means in a timely manner, at a charge no more\nthan the cost of distribution to the recipient; and\n\n(b) You may distribute such Executable Form under the terms of this\nLicense, or sublicense it under different terms, provided that the\nlicense for the Executable Form does not attempt to limit or alter\nthe recipients' rights in the Source Code Form under this License.\n\n3.3. Distribution of a Larger Work\n\nYou may create and distribute a Larger Work under terms of Your choice,\nprovided that You also comply with the requirements of this License for\nthe Covered Software. If the Larger Work is a combination of Covered\nSoftware with a work governed by one or more Secondary Licenses, and the\nCovered Software is not Incompatible With Secondary Licenses, this\nLicense permits You to additionally distribute such Covered Software\nunder the terms of such Secondary License(s), so that the recipient of\nthe Larger Work may, at their option, further distribute the Covered\nSoftware under the terms of either this License or such Secondary\nLicense(s).\n\n3.4. Notices\n\nYou may not remove or alter the substance of any license notices\n(including copyright notices, patent notices, disclaimers of warranty,\nor limitations of liability) contained within the Source Code Form of\nthe Covered Software, except that You may alter any license notices to\nthe extent required to remedy known factual inaccuracies.\n\n3.5. Application of Additional Terms\n\nYou may choose to offer, and to charge a fee for, warranty, support,\nindemnity or liability obligations to one or more recipients of Covered\nSoftware. However, You may do so only on Your own behalf, and not on\nbehalf of any Contributor. You must make it absolutely clear that any\nsuch warranty, support, indemnity, or liability obligation is offered by\nYou alone, and You hereby agree to indemnify every Contributor for any\nliability incurred by such Contributor as a result of warranty, support,\nindemnity or liability terms You offer. You may include additional\ndisclaimers of warranty and limitations of liability specific to any\njurisdiction.\n\n4. Inability to Comply Due to Statute or Regulation\n---------------------------------------------------\n\nIf it is impossible for You to comply with any of the terms of this\nLicense with respect to some or all of the Covered Software due to\nstatute, judicial order, or regulation then You must: (a) comply with\nthe terms of this License to the maximum extent possible; and (b)\ndescribe the limitations and the code they affect. Such description must\nbe placed in a text file included with all distributions of the Covered\nSoftware under this License. Except to the extent prohibited by statute\nor regulation, such description must be sufficiently detailed for a\nrecipient of ordinary skill to be able to understand it.\n\n5. Termination\n--------------\n\n5.1. The rights granted under this License will terminate automatically\nif You fail to comply with any of its terms. However, if You become\ncompliant, then the rights granted under this License from a particular\nContributor are reinstated (a) provisionally, unless and until such\nContributor explicitly and finally terminates Your grants, and (b) on an\nongoing basis, if such Contributor fails to notify You of the\nnon-compliance by some reasonable means prior to 60 days after You have\ncome back into compliance. Moreover, Your grants from a particular\nContributor are reinstated on an ongoing basis if such Contributor\nnotifies You of the non-compliance by some reasonable means, this is the\nfirst time You have received notice of non-compliance with this License\nfrom such Contributor, and You become compliant prior to 30 days after\nYour receipt of the notice.\n\n5.2. If You initiate litigation against any entity by asserting a patent\ninfringement claim (excluding declaratory judgment actions,\ncounter-claims, and cross-claims) alleging that a Contributor Version\ndirectly or indirectly infringes any patent, then the rights granted to\nYou by any and all Contributors for the Covered Software under Section\n2.1 of this License shall terminate.\n\n5.3. In the event of termination under Sections 5.1 or 5.2 above, all\nend user license agreements (excluding distributors and resellers) which\nhave been validly granted by You or Your distributors under this License\nprior to termination shall survive termination.\n\n************************************************************************\n*                                                                      *\n*  6. Disclaimer of Warranty                                           *\n*  -------------------------                                           *\n*                                                                      *\n*  Covered Software is provided under this License on an \"as is\"       *\n*  basis, without warranty of any kind, either expressed, implied, or  *\n*  statutory, including, without limitation, warranties that the       *\n*  Covered Software is free of defects, merchantable, fit for a        *\n*  particular purpose or non-infringing. The entire risk as to the     *\n*  quality and performance of the Covered Software is with You.        *\n*  Should any Covered Software prove defective in any respect, You     *\n*  (not any Contributor) assume the cost of any necessary servicing,   *\n*  repair, or correction. This disclaimer of warranty constitutes an   *\n*  essential part of this License. No use of any Covered Software is   *\n*  authorized under this License except under this disclaimer.         *\n*                                                                      *\n************************************************************************\n\n************************************************************************\n*                                                                      *\n*  7. Limitation of Liability                                          *\n*  --------------------------                                          *\n*                                                                      *\n*  Under no circumstances and under no legal theory, whether tort      *\n*  (including negligence), contract, or otherwise, shall any           *\n*  Contributor, or anyone who distributes Covered Software as          *\n*  permitted above, be liable to You for any direct, indirect,         *\n*  special, incidental, or consequential damages of any character      *\n*  including, without limitation, damages for lost profits, loss of    *\n*  goodwill, work stoppage, computer failure or malfunction, or any    *\n*  and all other commercial damages or losses, even if such party      *\n*  shall have been informed of the possibility of such damages. This   *\n*  limitation of liability shall not apply to liability for death or   *\n*  personal injury resulting from such party's negligence to the       *\n*  extent applicable law prohibits such limitation. Some               *\n*  jurisdictions do not allow the exclusion or limitation of           *\n*  incidental or consequential damages, so this exclusion and          *\n*  limitation may not apply to You.                                    *\n*                                                                      *\n************************************************************************\n\n8. Litigation\n-------------\n\nAny litigation relating to this License may be brought only in the\ncourts of a jurisdiction where the defendant maintains its principal\nplace of business and such litigation shall be governed by laws of that\njurisdiction, without reference to its conflict-of-law provisions.\nNothing in this Section shall prevent a party's ability to bring\ncross-claims or counter-claims.\n\n9. Miscellaneous\n----------------\n\nThis License represents the complete agreement concerning the subject\nmatter hereof. If any provision of this License is held to be\nunenforceable, such provision shall be reformed only to the extent\nnecessary to make it enforceable. Any law or regulation which provides\nthat the language of a contract shall be construed against the drafter\nshall not be used to construe this License against a Contributor.\n\n10. Versions of the License\n---------------------------\n\n10.1. New Versions\n\nMozilla Foundation is the license steward. Except as provided in Section\n10.3, no one other than the license steward has the right to modify or\npublish new versions of this License. Each version will be given a\ndistinguishing version number.\n\n10.2. Effect of New Versions\n\nYou may distribute the Covered Software under the terms of the version\nof the License under which You originally received the Covered Software,\nor under the terms of any subsequent version published by the license\nsteward.\n\n10.3. Modified Versions\n\nIf you create software not governed by this License, and you want to\ncreate a new license for such software, you may create and use a\nmodified version of this License if you rename the license and remove\nany references to the name of the license steward (except to note that\nsuch modified license differs from this License).\n\n10.4. Distributing Source Code Form that is Incompatible With Secondary\nLicenses\n\nIf You choose to distribute Source Code Form that is Incompatible With\nSecondary Licenses under the terms of this version of the License, the\nnotice described in Exhibit B of this License must be attached.\n\nExhibit A - Source Code Form License Notice\n-------------------------------------------\n\nThis Source Code Form is subject to the terms of the Mozilla Public\nLicense, v. 2.0. If a copy of the MPL was not distributed with this\nfile, You can obtain one at http://mozilla.org/MPL/2.0/.\n\nIf it is not possible or desirable to put the notice in a particular\nfile, then You may include the notice in a location (such as a LICENSE\nfile in a relevant directory) where a recipient would be likely to look\nfor such a notice.\n\nYou may add additional accurate notices of copyright ownership.\n\nExhibit B - \"Incompatible With Secondary Licenses\" Notice\n---------------------------------------------------------\n\nThis Source Code Form is \"Incompatible With Secondary Licenses\", as\ndefined by the Mozilla Public License, v. 2.0.\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"notch-stage-icon2 2.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 5.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 6.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 11.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 12.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 13.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 7.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 8.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 9.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"notch-stage-icon2 10.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/Github.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"GitHub Mark White.svg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"GitHub Mark White 1.svg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"GitHub Mark White 2.svg\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/bolt.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"bolt.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/chrome.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Google Chrome macOS BigSur Icon.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"Google Chrome macOS BigSur Icon 1.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"Google Chrome macOS BigSur Icon 2.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/defaultmusic.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/logo.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"256-mac 1.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/logo2.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"BoringNotch icon.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/plug.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"plug.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/sparkle.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"sparkle.svg\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/spotlight.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"spotlight.svg\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Assets.xcassets/theboringteam.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"TheBoringTeam.svg\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/BoringViewCoordinator.swift",
    "content": "//\n//  BoringViewCoordinator.swift\n//  boringNotch\n//\n//  Created by Alexander on 2024-11-20.\n//\n\nimport AppKit\nimport Combine\nimport Defaults\nimport SwiftUI\n\nenum SneakContentType {\n    case brightness\n    case volume\n    case backlight\n    case music\n    case mic\n    case battery\n    case download\n}\n\nstruct sneakPeek {\n    var show: Bool = false\n    var type: SneakContentType = .music\n    var value: CGFloat = 0\n    var icon: String = \"\"\n}\n\nstruct SharedSneakPeek: Codable {\n    var show: Bool\n    var type: String\n    var value: String\n    var icon: String\n}\n\nenum BrowserType {\n    case chromium\n    case safari\n}\n\nstruct ExpandedItem {\n    var show: Bool = false\n    var type: SneakContentType = .battery\n    var value: CGFloat = 0\n    var browser: BrowserType = .chromium\n}\n\n@MainActor\nclass BoringViewCoordinator: ObservableObject {\n    static let shared = BoringViewCoordinator()\n\n    @Published var currentView: NotchViews = .home\n    @Published var helloAnimationRunning: Bool = false\n    private var sneakPeekDispatch: DispatchWorkItem?\n    private var expandingViewDispatch: DispatchWorkItem?\n    private var hudEnableTask: Task<Void, Never>?\n\n    @AppStorage(\"firstLaunch\") var firstLaunch: Bool = true\n    @AppStorage(\"showWhatsNew\") var showWhatsNew: Bool = true\n    @AppStorage(\"musicLiveActivityEnabled\") var musicLiveActivityEnabled: Bool = true\n    @AppStorage(\"currentMicStatus\") var currentMicStatus: Bool = true\n\n    @AppStorage(\"alwaysShowTabs\") var alwaysShowTabs: Bool = true {\n        didSet {\n            if !alwaysShowTabs {\n                openLastTabByDefault = false\n                if ShelfStateViewModel.shared.isEmpty || !Defaults[.openShelfByDefault] {\n                    currentView = .home\n                }\n            }\n        }\n    }\n\n    @AppStorage(\"openLastTabByDefault\") var openLastTabByDefault: Bool = false {\n        didSet {\n            if openLastTabByDefault {\n                alwaysShowTabs = true\n            }\n        }\n    }\n    \n    @Default(.hudReplacement) var hudReplacement: Bool\n    \n    // Legacy storage for migration\n    @AppStorage(\"preferred_screen_name\") private var legacyPreferredScreenName: String?\n    \n    // New UUID-based storage\n    @AppStorage(\"preferred_screen_uuid\") var preferredScreenUUID: String? {\n        didSet {\n            if let uuid = preferredScreenUUID {\n                selectedScreenUUID = uuid\n            }\n            NotificationCenter.default.post(name: Notification.Name.selectedScreenChanged, object: nil)\n        }\n    }\n\n    @Published var selectedScreenUUID: String = NSScreen.main?.displayUUID ?? \"\"\n\n    @Published var optionKeyPressed: Bool = true\n    private var accessibilityObserver: Any?\n    private var hudReplacementCancellable: AnyCancellable?\n\n    private init() {\n        // Perform migration from name-based to UUID-based storage\n        if preferredScreenUUID == nil, let legacyName = legacyPreferredScreenName {\n            // Try to find screen by name and migrate to UUID\n            if let screen = NSScreen.screens.first(where: { $0.localizedName == legacyName }),\n               let uuid = screen.displayUUID {\n                preferredScreenUUID = uuid\n                NSLog(\"✅ Migrated display preference from name '\\(legacyName)' to UUID '\\(uuid)'\")\n            } else {\n                // Fallback to main screen if legacy screen not found\n                preferredScreenUUID = NSScreen.main?.displayUUID\n                NSLog(\"⚠️ Could not find display named '\\(legacyName)', falling back to main screen\")\n            }\n            // Clear legacy value after migration\n            legacyPreferredScreenName = nil\n        } else if preferredScreenUUID == nil {\n            // No legacy value, use main screen\n            preferredScreenUUID = NSScreen.main?.displayUUID\n        }\n        \n        selectedScreenUUID = preferredScreenUUID ?? NSScreen.main?.displayUUID ?? \"\"\n        // Observe changes to accessibility authorization and react accordingly\n        accessibilityObserver = NotificationCenter.default.addObserver(\n            forName: Notification.Name.accessibilityAuthorizationChanged,\n            object: nil,\n            queue: .main\n        ) { _ in\n            Task { @MainActor in\n                if Defaults[.hudReplacement] {\n                    await MediaKeyInterceptor.shared.start(promptIfNeeded: false)\n                }\n            }\n        }\n\n        // Observe changes to hudReplacement\n        hudReplacementCancellable = Defaults.publisher(.hudReplacement)\n            .sink { [weak self] change in\n                Task { @MainActor in\n                    guard let self = self else { return }\n\n                    self.hudEnableTask?.cancel()\n                    self.hudEnableTask = nil\n\n                    if change.newValue {\n                        self.hudEnableTask = Task { @MainActor in\n                            let granted = await XPCHelperClient.shared.ensureAccessibilityAuthorization(promptIfNeeded: true)\n                            if Task.isCancelled { return }\n\n                            if granted {\n                                await MediaKeyInterceptor.shared.start()\n                            } else {\n                                Defaults[.hudReplacement] = false\n                            }\n                        }\n                    } else {\n                        MediaKeyInterceptor.shared.stop()\n                    }\n                }\n            }\n\n        Task { @MainActor in\n            helloAnimationRunning = firstLaunch\n\n            if Defaults[.hudReplacement] {\n                let authorized = await XPCHelperClient.shared.isAccessibilityAuthorized()\n                if !authorized {\n                    Defaults[.hudReplacement] = false\n                } else {\n                    await MediaKeyInterceptor.shared.start(promptIfNeeded: false)\n                }\n            }\n        }\n    }\n    \n    @objc func sneakPeekEvent(_ notification: Notification) {\n        let decoder = JSONDecoder()\n        if let decodedData = try? decoder.decode(\n            SharedSneakPeek.self, from: notification.userInfo?.first?.value as! Data)\n        {\n            let contentType =\n                decodedData.type == \"brightness\"\n                ? SneakContentType.brightness\n                : decodedData.type == \"volume\"\n                    ? SneakContentType.volume\n                    : decodedData.type == \"backlight\"\n                        ? SneakContentType.backlight\n                        : decodedData.type == \"mic\"\n                            ? SneakContentType.mic : SneakContentType.brightness\n\n            let formatter = NumberFormatter()\n            formatter.locale = Locale(identifier: \"en_US_POSIX\")\n            formatter.numberStyle = .decimal\n            let value = CGFloat((formatter.number(from: decodedData.value) ?? 0.0).floatValue)\n            let icon = decodedData.icon\n\n            print(\"Decoded: \\(decodedData), Parsed value: \\(value)\")\n\n            toggleSneakPeek(status: decodedData.show, type: contentType, value: value, icon: icon)\n\n        } else {\n            print(\"Failed to decode JSON data\")\n        }\n    }\n\n    func toggleSneakPeek(\n        status: Bool, type: SneakContentType, duration: TimeInterval = 1.5, value: CGFloat = 0,\n        icon: String = \"\"\n    ) {\n        sneakPeekDuration = duration\n        if type != .music {\n            // close()\n            if !Defaults[.hudReplacement] {\n                return\n            }\n        }\n        Task { @MainActor in\n            withAnimation(.smooth) {\n                self.sneakPeek.show = status\n                self.sneakPeek.type = type\n                self.sneakPeek.value = value\n                self.sneakPeek.icon = icon\n            }\n        }\n\n        if type == .mic {\n            currentMicStatus = value == 1\n        }\n    }\n\n    private var sneakPeekDuration: TimeInterval = 1.5\n    private var sneakPeekTask: Task<Void, Never>?\n\n    // Helper function to manage sneakPeek timer using Swift Concurrency\n    private func scheduleSneakPeekHide(after duration: TimeInterval) {\n        sneakPeekTask?.cancel()\n\n        sneakPeekTask = Task { [weak self] in\n            try? await Task.sleep(for: .seconds(duration))\n            guard let self = self, !Task.isCancelled else { return }\n            await MainActor.run {\n                withAnimation {\n                    self.toggleSneakPeek(status: false, type: .music)\n                    self.sneakPeekDuration = 1.5\n                }\n            }\n        }\n    }\n\n    @Published var sneakPeek: sneakPeek = .init() {\n        didSet {\n            if sneakPeek.show {\n                scheduleSneakPeekHide(after: sneakPeekDuration)\n            } else {\n                sneakPeekTask?.cancel()\n            }\n        }\n    }\n\n    func toggleExpandingView(\n        status: Bool,\n        type: SneakContentType,\n        value: CGFloat = 0,\n        browser: BrowserType = .chromium\n    ) {\n        Task { @MainActor in\n            withAnimation(.smooth) {\n                self.expandingView.show = status\n                self.expandingView.type = type\n                self.expandingView.value = value\n                self.expandingView.browser = browser\n            }\n        }\n    }\n\n    private var expandingViewTask: Task<Void, Never>?\n\n    @Published var expandingView: ExpandedItem = .init() {\n        didSet {\n            if expandingView.show {\n                expandingViewTask?.cancel()\n                let duration: TimeInterval = (expandingView.type == .download ? 2 : 3)\n                let currentType = expandingView.type\n                expandingViewTask = Task { [weak self] in\n                    try? await Task.sleep(for: .seconds(duration))\n                    guard let self = self, !Task.isCancelled else { return }\n                    self.toggleExpandingView(status: false, type: currentType)\n                }\n            } else {\n                expandingViewTask?.cancel()\n            }\n        }\n    }\n    \n    func showEmpty() {\n        currentView = .home\n    }\n}\n"
  },
  {
    "path": "boringNotch/ContentView.swift",
    "content": "//\n//  ContentView.swift\n//  boringNotchApp\n//\n//  Created by Harsh Vardhan Goswami  on 02/08/24\n//  Modified by Richard Kunkli on 24/08/2024.\n//\n\nimport AVFoundation\nimport Combine\nimport Defaults\nimport KeyboardShortcuts\nimport SwiftUI\nimport SwiftUIIntrospect\n\n@MainActor\nstruct ContentView: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject var webcamManager = WebcamManager.shared\n\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @ObservedObject var musicManager = MusicManager.shared\n    @ObservedObject var batteryModel = BatteryStatusViewModel.shared\n    @ObservedObject var brightnessManager = BrightnessManager.shared\n    @ObservedObject var volumeManager = VolumeManager.shared\n    @State private var hoverTask: Task<Void, Never>?\n    @State private var isHovering: Bool = false\n    @State private var anyDropDebounceTask: Task<Void, Never>?\n\n    @State private var gestureProgress: CGFloat = .zero\n\n    @State private var haptics: Bool = false\n\n    @Namespace var albumArtNamespace\n\n    @Default(.useMusicVisualizer) var useMusicVisualizer\n\n    @Default(.showNotHumanFace) var showNotHumanFace\n\n    // Shared interactive spring for movement/resizing to avoid conflicting animations\n    private let animationSpring = Animation.interactiveSpring(response: 0.38, dampingFraction: 0.8, blendDuration: 0)\n\n    private let extendedHoverPadding: CGFloat = 30\n    private let zeroHeightHoverPadding: CGFloat = 10\n\n    private var topCornerRadius: CGFloat {\n       ((vm.notchState == .open) && Defaults[.cornerRadiusScaling])\n                ? cornerRadiusInsets.opened.top\n                : cornerRadiusInsets.closed.top\n    }\n\n    private var currentNotchShape: NotchShape {\n        NotchShape(\n            topCornerRadius: topCornerRadius,\n            bottomCornerRadius: ((vm.notchState == .open) && Defaults[.cornerRadiusScaling])\n                ? cornerRadiusInsets.opened.bottom\n                : cornerRadiusInsets.closed.bottom\n        )\n    }\n\n    private var computedChinWidth: CGFloat {\n        var chinWidth: CGFloat = vm.closedNotchSize.width\n\n        if coordinator.expandingView.type == .battery && coordinator.expandingView.show\n            && vm.notchState == .closed && Defaults[.showPowerStatusNotifications]\n        {\n            chinWidth = 640\n        } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .music)\n            && vm.notchState == .closed && (musicManager.isPlaying || !musicManager.isPlayerIdle)\n            && coordinator.musicLiveActivityEnabled && !vm.hideOnClosed\n        {\n            chinWidth += (2 * max(0, vm.effectiveClosedNotchHeight - 12) + 20)\n        } else if !coordinator.expandingView.show && vm.notchState == .closed\n            && (!musicManager.isPlaying && musicManager.isPlayerIdle) && Defaults[.showNotHumanFace]\n            && !vm.hideOnClosed\n        {\n            chinWidth += (2 * max(0, vm.effectiveClosedNotchHeight - 12) + 20)\n        }\n\n        return chinWidth\n    }\n\n    var body: some View {\n        // Calculate scale based on gesture progress only\n        let gestureScale: CGFloat = {\n            guard gestureProgress != 0 else { return 1.0 }\n            let scaleFactor = 1.0 + gestureProgress * 0.01\n            return max(0.6, scaleFactor)\n        }()\n        \n        ZStack(alignment: .top) {\n            VStack(spacing: 0) {\n                let mainLayout = NotchLayout()\n                    .frame(alignment: .top)\n                    .padding(\n                        .horizontal,\n                        vm.notchState == .open\n                        ? Defaults[.cornerRadiusScaling]\n                        ? (cornerRadiusInsets.opened.top) : (cornerRadiusInsets.opened.bottom)\n                        : cornerRadiusInsets.closed.bottom\n                    )\n                    .padding([.horizontal, .bottom], vm.notchState == .open ? 12 : 0)\n                    .background(.black)\n                    .clipShape(currentNotchShape)\n                    .overlay(alignment: .top) {\n                        Rectangle()\n                            .fill(.black)\n                            .frame(height: 1)\n                            .padding(.horizontal, topCornerRadius)\n                    }\n                    .shadow(\n                        color: ((vm.notchState == .open || isHovering) && Defaults[.enableShadow])\n                            ? .black.opacity(0.7) : .clear, radius: Defaults[.cornerRadiusScaling] ? 6 : 4\n                    )\n                    .padding(\n                        .bottom,\n                        vm.effectiveClosedNotchHeight == 0 ? 10 : 0\n                    )\n                \n                mainLayout\n                    .frame(height: vm.notchState == .open ? vm.notchSize.height : nil)\n                    .conditionalModifier(true) { view in\n                        let openAnimation = Animation.spring(response: 0.42, dampingFraction: 0.8, blendDuration: 0)\n                        let closeAnimation = Animation.spring(response: 0.45, dampingFraction: 1.0, blendDuration: 0)\n                        \n                        return view\n                            .animation(vm.notchState == .open ? openAnimation : closeAnimation, value: vm.notchState)\n                            .animation(.smooth, value: gestureProgress)\n                    }\n                    .contentShape(Rectangle())\n                    .onHover { hovering in\n                        handleHover(hovering)\n                    }\n                    .onTapGesture {\n                        doOpen()\n                    }\n                    .conditionalModifier(Defaults[.enableGestures]) { view in\n                        view\n                            .panGesture(direction: .down) { translation, phase in\n                                handleDownGesture(translation: translation, phase: phase)\n                            }\n                    }\n                    .conditionalModifier(Defaults[.closeGestureEnabled] && Defaults[.enableGestures]) { view in\n                        view\n                            .panGesture(direction: .up) { translation, phase in\n                                handleUpGesture(translation: translation, phase: phase)\n                            }\n                    }\n                    .onReceive(NotificationCenter.default.publisher(for: .sharingDidFinish)) { _ in\n                        if vm.notchState == .open && !isHovering && !vm.isBatteryPopoverActive {\n                            hoverTask?.cancel()\n                            hoverTask = Task {\n                                try? await Task.sleep(for: .milliseconds(100))\n                                guard !Task.isCancelled else { return }\n                                await MainActor.run {\n                                    if self.vm.notchState == .open && !self.isHovering && !self.vm.isBatteryPopoverActive && !SharingStateManager.shared.preventNotchClose {\n                                        self.vm.close()\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    .onChange(of: vm.notchState) { _, newState in\n                        if newState == .closed && isHovering {\n                            withAnimation {\n                                isHovering = false\n                            }\n                        }\n                    }\n                    .onChange(of: vm.isBatteryPopoverActive) {\n                        if !vm.isBatteryPopoverActive && !isHovering && vm.notchState == .open && !SharingStateManager.shared.preventNotchClose {\n                            hoverTask?.cancel()\n                            hoverTask = Task {\n                                try? await Task.sleep(for: .milliseconds(100))\n                                guard !Task.isCancelled else { return }\n                                await MainActor.run {\n                                    if !self.vm.isBatteryPopoverActive && !self.isHovering && self.vm.notchState == .open && !SharingStateManager.shared.preventNotchClose {\n                                        self.vm.close()\n                                    }\n                                }\n                            }\n                        }\n                    }\n                    .sensoryFeedback(.alignment, trigger: haptics)\n                    .contextMenu {\n                        Button(\"Settings\") {\n                            SettingsWindowController.shared.showWindow()\n                        }\n                        .keyboardShortcut(KeyEquivalent(\",\"), modifiers: .command)\n                        //                    Button(\"Edit\") { // Doesnt work....\n                        //                        let dn = DynamicNotch(content: EditPanelView())\n                        //                        dn.toggle()\n                        //                    }\n                        //                    .keyboardShortcut(\"E\", modifiers: .command)\n                    }\n                if vm.chinHeight > 0 {\n                    Rectangle()\n                        .fill(Color.black.opacity(0.01))\n                        .frame(width: computedChinWidth, height: vm.chinHeight)\n                }\n            }\n        }\n        .padding(.bottom, 8)\n        .frame(maxWidth: windowSize.width, maxHeight: windowSize.height, alignment: .top)\n        .compositingGroup()\n        .scaleEffect(\n            x: gestureScale,\n            y: gestureScale,\n            anchor: .top\n        )\n        .animation(.smooth, value: gestureProgress)\n        .background(dragDetector)\n        .preferredColorScheme(.dark)\n        .environmentObject(vm)\n        .onChange(of: vm.anyDropZoneTargeting) { _, isTargeted in\n            anyDropDebounceTask?.cancel()\n\n            if isTargeted {\n                if vm.notchState == .closed {\n                    coordinator.currentView = .shelf\n                    doOpen()\n                }\n                return\n            }\n\n            anyDropDebounceTask = Task { @MainActor in\n                try? await Task.sleep(for: .milliseconds(500))\n                guard !Task.isCancelled else { return }\n\n                if vm.dropEvent {\n                    vm.dropEvent = false\n                    return\n                }\n\n                vm.dropEvent = false\n                if !SharingStateManager.shared.preventNotchClose {\n                    vm.close()\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    func NotchLayout() -> some View {\n        VStack(alignment: .leading) {\n            VStack(alignment: .leading) {\n                if coordinator.helloAnimationRunning {\n                    Spacer()\n                    HelloAnimation(onFinish: {\n                        vm.closeHello()\n                    }).frame(\n                        width: getClosedNotchSize().width,\n                        height: 80\n                    )\n                    .padding(.top, 40)\n                    Spacer()\n                } else {\n                    if coordinator.expandingView.type == .battery && coordinator.expandingView.show\n                        && vm.notchState == .closed && Defaults[.showPowerStatusNotifications]\n                    {\n                        HStack(spacing: 0) {\n                            HStack {\n                                Text(batteryModel.statusText)\n                                    .font(.subheadline)\n                                    .foregroundStyle(.white)\n                            }\n\n                            Rectangle()\n                                .fill(.black)\n                                .frame(width: vm.closedNotchSize.width + 10)\n\n                            HStack {\n                                BoringBatteryView(\n                                    batteryWidth: 30,\n                                    isCharging: batteryModel.isCharging,\n                                    isInLowPowerMode: batteryModel.isInLowPowerMode,\n                                    isPluggedIn: batteryModel.isPluggedIn,\n                                    levelBattery: batteryModel.levelBattery,\n                                    isForNotification: true\n                                )\n                            }\n                            .frame(width: 76, alignment: .trailing)\n                        }\n                        .frame(height: vm.effectiveClosedNotchHeight, alignment: .center)\n                      } else if coordinator.sneakPeek.show && Defaults[.inlineHUD] && (coordinator.sneakPeek.type != .music) && (coordinator.sneakPeek.type != .battery) && vm.notchState == .closed {\n                          InlineHUD(type: $coordinator.sneakPeek.type, value: $coordinator.sneakPeek.value, icon: $coordinator.sneakPeek.icon, hoverAnimation: $isHovering, gestureProgress: $gestureProgress)\n                              .transition(.opacity)\n                      } else if (!coordinator.expandingView.show || coordinator.expandingView.type == .music) && vm.notchState == .closed && (musicManager.isPlaying || !musicManager.isPlayerIdle) && coordinator.musicLiveActivityEnabled && !vm.hideOnClosed {\n                          MusicLiveActivity()\n                              .frame(alignment: .center)\n                      } else if !coordinator.expandingView.show && vm.notchState == .closed && (!musicManager.isPlaying && musicManager.isPlayerIdle) && Defaults[.showNotHumanFace] && !vm.hideOnClosed  {\n                          BoringFaceAnimation()\n                       } else if vm.notchState == .open {\n                           BoringHeader()\n                               .frame(height: max(24, vm.effectiveClosedNotchHeight))\n                               .opacity(gestureProgress != 0 ? 1.0 - min(abs(gestureProgress) * 0.1, 0.3) : 1.0)\n                       } else {\n                           Rectangle().fill(.clear).frame(width: vm.closedNotchSize.width - 20, height: vm.effectiveClosedNotchHeight)\n                       }\n\n                      if coordinator.sneakPeek.show {\n                          if (coordinator.sneakPeek.type != .music) && (coordinator.sneakPeek.type != .battery) && !Defaults[.inlineHUD] && vm.notchState == .closed {\n                              SystemEventIndicatorModifier(\n                                  eventType: $coordinator.sneakPeek.type,\n                                  value: $coordinator.sneakPeek.value,\n                                  icon: $coordinator.sneakPeek.icon,\n                                  sendEventBack: { newVal in\n                                      switch coordinator.sneakPeek.type {\n                                      case .volume:\n                                          VolumeManager.shared.setAbsolute(Float32(newVal))\n                                      case .brightness:\n                                          BrightnessManager.shared.setAbsolute(value: Float32(newVal))\n                                      default:\n                                          break\n                                      }\n                                  }\n                              )\n                              .padding(.bottom, 10)\n                              .padding(.leading, 4)\n                              .padding(.trailing, 8)\n                          }\n                          // Old sneak peek music\n                          else if coordinator.sneakPeek.type == .music {\n                              if vm.notchState == .closed && !vm.hideOnClosed && Defaults[.sneakPeekStyles] == .standard {\n                                  HStack(alignment: .center) {\n                                      Image(systemName: \"music.note\")\n                                      GeometryReader { geo in\n                                          MarqueeText(.constant(musicManager.songTitle + \" - \" + musicManager.artistName),  textColor: Defaults[.playerColorTinting] ? Color(nsColor: musicManager.avgColor).ensureMinimumBrightness(factor: 0.6) : .gray, minDuration: 1, frameWidth: geo.size.width)\n                                      }\n                                  }\n                                  .foregroundStyle(.gray)\n                                  .padding(.bottom, 10)\n                              }\n                          }\n                      }\n                  }\n              }\n              .conditionalModifier((coordinator.sneakPeek.show && (coordinator.sneakPeek.type == .music) && vm.notchState == .closed && !vm.hideOnClosed && Defaults[.sneakPeekStyles] == .standard) || (coordinator.sneakPeek.show && (coordinator.sneakPeek.type != .music) && (vm.notchState == .closed))) { view in\n                  view\n                      .fixedSize()\n              }\n              .zIndex(2)\n            if vm.notchState == .open {\n                VStack {\n                    switch coordinator.currentView {\n                    case .home:\n                        NotchHomeView(albumArtNamespace: albumArtNamespace)\n                    case .shelf:\n                        ShelfView()\n                    }\n                }\n                .transition(\n                    .scale(scale: 0.8, anchor: .top)\n                    .combined(with: .opacity)\n                    .animation(.smooth(duration: 0.35))\n                )\n                .zIndex(1)\n                .allowsHitTesting(vm.notchState == .open)\n                .opacity(gestureProgress != 0 ? 1.0 - min(abs(gestureProgress) * 0.1, 0.3) : 1.0)\n            }\n        }\n        .onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data], delegate: GeneralDropTargetDelegate(isTargeted: $vm.generalDropTargeting))\n    }\n\n    @ViewBuilder\n    func BoringFaceAnimation() -> some View {\n        HStack {\n            HStack {\n                Rectangle()\n                    .fill(.clear)\n                    .frame(\n                        width: max(0, vm.effectiveClosedNotchHeight - 12),\n                        height: max(0, vm.effectiveClosedNotchHeight - 12)\n                    )\n                Rectangle()\n                    .fill(.black)\n                    .frame(width: vm.closedNotchSize.width - 20)\n                MinimalFaceFeatures()\n            }\n        }.frame(\n            height: vm.effectiveClosedNotchHeight,\n            alignment: .center\n        )\n    }\n\n    @ViewBuilder\n    func MusicLiveActivity() -> some View {\n        HStack {\n            Image(nsImage: musicManager.albumArt)\n                .resizable()\n                .clipped()\n                .clipShape(\n                    RoundedRectangle(\n                        cornerRadius: MusicPlayerImageSizes.cornerRadiusInset.closed)\n                )\n                .matchedGeometryEffect(id: \"albumArt\", in: albumArtNamespace)\n                .frame(\n                    width: max(0, vm.effectiveClosedNotchHeight - 12),\n                    height: max(0, vm.effectiveClosedNotchHeight - 12)\n                )\n\n            Rectangle()\n                .fill(.black)\n                .overlay(\n                    HStack(alignment: .top) {\n                        if coordinator.expandingView.show\n                            && coordinator.expandingView.type == .music\n                        {\n                            MarqueeText(\n                                .constant(musicManager.songTitle),\n                                textColor: Defaults[.coloredSpectrogram]\n                                    ? Color(nsColor: musicManager.avgColor) : Color.gray,\n                                minDuration: 0.4,\n                                frameWidth: 100\n                            )\n                            .opacity(\n                                (coordinator.expandingView.show\n                                    && Defaults[.sneakPeekStyles] == .inline)\n                                    ? 1 : 0\n                            )\n                            Spacer(minLength: vm.closedNotchSize.width)\n                            // Song Artist\n                            Text(musicManager.artistName)\n                                .lineLimit(1)\n                                .truncationMode(.tail)\n                                .foregroundStyle(\n                                    Defaults[.coloredSpectrogram]\n                                        ? Color(nsColor: musicManager.avgColor)\n                                        : Color.gray\n                                )\n                                .opacity(\n                                    (coordinator.expandingView.show\n                                        && coordinator.expandingView.type == .music\n                                        && Defaults[.sneakPeekStyles] == .inline)\n                                        ? 1 : 0\n                                )\n                        }\n                    }\n                )\n                .frame(\n                    width: (coordinator.expandingView.show\n                        && coordinator.expandingView.type == .music\n                        && Defaults[.sneakPeekStyles] == .inline)\n                        ? 380\n                        : vm.closedNotchSize.width\n                            + -cornerRadiusInsets.closed.top\n                )\n\n            HStack {\n                if useMusicVisualizer {\n                    Rectangle()\n                        .fill(\n                            Defaults[.coloredSpectrogram]\n                                ? Color(nsColor: musicManager.avgColor).gradient\n                                : Color.gray.gradient\n                        )\n                        .frame(width: 50, alignment: .center)\n                        .matchedGeometryEffect(id: \"spectrum\", in: albumArtNamespace)\n                        .mask {\n                            AudioSpectrumView(isPlaying: $musicManager.isPlaying)\n                                .frame(width: 16, height: 12)\n                        }\n                } else {\n                    LottieAnimationContainer()\n                        .frame(maxWidth: .infinity, maxHeight: .infinity)\n                }\n            }\n            .frame(\n                width: max(\n                    0,\n                    vm.effectiveClosedNotchHeight - 12\n                        + gestureProgress / 2\n                ),\n                height: max(\n                    0,\n                    vm.effectiveClosedNotchHeight - 12\n                ),\n                alignment: .center\n            )\n        }\n        .frame(\n            height: vm.effectiveClosedNotchHeight,\n            alignment: .center\n        )\n    }\n\n    @ViewBuilder\n    var dragDetector: some View {\n        if Defaults[.boringShelf] && vm.notchState == .closed {\n            Color.clear\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .contentShape(Rectangle())\n        .onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data], isTargeted: $vm.dragDetectorTargeting) { providers in\n            vm.dropEvent = true\n            ShelfStateViewModel.shared.load(providers)\n            return true\n        }\n        } else {\n            EmptyView()\n        }\n    }\n\n    private func doOpen() {\n        withAnimation(animationSpring) {\n            vm.open()\n        }\n    }\n\n    // MARK: - Hover Management\n\n    private func handleHover(_ hovering: Bool) {\n        if coordinator.firstLaunch { return }\n        hoverTask?.cancel()\n        \n        if hovering {\n            withAnimation(animationSpring) {\n                isHovering = true\n            }\n            \n            if vm.notchState == .closed && Defaults[.enableHaptics] {\n                haptics.toggle()\n            }\n            \n            guard vm.notchState == .closed,\n                  !coordinator.sneakPeek.show,\n                  Defaults[.openNotchOnHover] else { return }\n            \n            hoverTask = Task {\n                try? await Task.sleep(for: .seconds(Defaults[.minimumHoverDuration]))\n                guard !Task.isCancelled else { return }\n                \n                await MainActor.run {\n                    guard self.vm.notchState == .closed,\n                          self.isHovering,\n                          !self.coordinator.sneakPeek.show else { return }\n                    \n                    self.doOpen()\n                }\n            }\n        } else {\n            hoverTask = Task {\n                try? await Task.sleep(for: .milliseconds(100))\n                guard !Task.isCancelled else { return }\n                \n                await MainActor.run {\n                    withAnimation(animationSpring) {\n                        self.isHovering = false\n                    }\n                    \n                    if self.vm.notchState == .open && !self.vm.isBatteryPopoverActive && !SharingStateManager.shared.preventNotchClose {\n                        self.vm.close()\n                    }\n                }\n            }\n        }\n    }\n\n    // MARK: - Gesture Handling\n\n    private func handleDownGesture(translation: CGFloat, phase: NSEvent.Phase) {\n        guard vm.notchState == .closed else { return }\n\n        if phase == .ended {\n            withAnimation(animationSpring) { gestureProgress = .zero }\n            return\n        }\n\n        withAnimation(animationSpring) {\n            gestureProgress = (translation / Defaults[.gestureSensitivity]) * 20\n        }\n\n        if translation > Defaults[.gestureSensitivity] {\n            if Defaults[.enableHaptics] {\n                haptics.toggle()\n            }\n            withAnimation(animationSpring) {\n                gestureProgress = .zero\n            }\n            doOpen()\n        }\n    }\n\n    private func handleUpGesture(translation: CGFloat, phase: NSEvent.Phase) {\n        guard vm.notchState == .open && !vm.isHoveringCalendar else { return }\n\n        withAnimation(animationSpring) {\n            gestureProgress = (translation / Defaults[.gestureSensitivity]) * -20\n        }\n\n        if phase == .ended {\n            withAnimation(animationSpring) {\n                gestureProgress = .zero\n            }\n        }\n\n        if translation > Defaults[.gestureSensitivity] {\n            withAnimation(animationSpring) {\n                isHovering = false\n            }\n            if !SharingStateManager.shared.preventNotchClose { \n                gestureProgress = .zero\n                vm.close()\n            }\n\n            if Defaults[.enableHaptics] {\n                haptics.toggle()\n            }\n        }\n    }\n}\n\nstruct FullScreenDropDelegate: DropDelegate {\n    @Binding var isTargeted: Bool\n    let onDrop: () -> Void\n\n    func dropEntered(info _: DropInfo) {\n        isTargeted = true\n    }\n\n    func dropExited(info _: DropInfo) {\n        isTargeted = false\n    }\n\n    func performDrop(info _: DropInfo) -> Bool {\n        isTargeted = false\n        onDrop()\n        return true\n    }\n\n}\n\nstruct GeneralDropTargetDelegate: DropDelegate {\n    @Binding var isTargeted: Bool\n\n    func dropEntered(info: DropInfo) {\n        isTargeted = true\n    }\n\n    func dropExited(info: DropInfo) {\n        isTargeted = false\n    }\n\n    func dropUpdated(info: DropInfo) -> DropProposal? {\n        return DropProposal(operation: .cancel)\n    }\n\n    func performDrop(info: DropInfo) -> Bool {\n        return false\n    }\n}\n\n#Preview {\n    let vm = BoringViewModel()\n    vm.open()\n    return ContentView()\n        .environmentObject(vm)\n        .frame(width: vm.notchSize.width, height: vm.notchSize.height)\n}\n"
  },
  {
    "path": "boringNotch/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSAppTransportSecurity</key>\n\t<dict>\n\t\t<key>NSAllowsArbitraryLoads</key>\n\t\t<true/>\n\t</dict>\n\t<key>SUEnableDownloaderService</key>\n\t<true/>\n\t<key>SUEnableInstallerLauncherService</key>\n\t<true/>\n\t<key>SUFeedURL</key>\n\t<string>https://TheBoredTeam.github.io/boring.notch/appcast.xml</string>\n\t<key>SUPublicEDKey</key>\n\t<string>B1Y47t8C/v8ImurYA+9arEsuCrpxwJSviekiflMElbI=</string>\n\t<key>UTImportedTypeDeclarations</key>\n\t<array>\n\t\t<dict/>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "boringNotch/Localizable.xcstrings",
    "content": "{\n  \"sourceLanguage\" : \"en\",\n  \"strings\" : {\n    \"\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        }\n      }\n    },\n    \" – %lld\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \" – %lld\"\n          }\n        }\n      }\n    },\n    \"'Now Playing' was the only option on previous versions and works with all media apps.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Now Playing' was the only option on previous versions and works with all media apps.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\\\"Nyní hraje\\\" byla jediná možnost na předchozích verzích a funguje se všemi mediálními aplikacemi.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"\\\"Jetzt läuft\\\" war die einzige Option auf vorherigen Versionen und funktioniert mit allen Medien-Apps.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"'Now Playing' was the only option on previous versions and works with all media apps.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"'Now Playing' was the only option on previous versions and works with all media apps.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\\\"Ahora suena\\\" era la única opción disponible en versiones anteriores y funciona con todas las aplicaciones multimedia.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"“Lecture en cours” était la seule option dans les versions précédentes et fonctionne avec toutes les applications multimédias.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Now Playing' was the only option on previous versions and works with all media apps.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'In riproduzione' era l’unica opzione nelle versioni precedenti e funziona con tutte le app multimediali.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"이전 버전에서는 ‘현재 재생 중' 만 선택할 수 있었으며, 모든 미디어 애플리케이션과 호환됩니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Teraz odtwarzane' było jedyną opcją w poprzednich wersjach i działa ze wszystkimi multimediami.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Tocando agora' era a única opção em versões anteriores e funciona em todos os aplicativos de mídia.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Сейчас играть' была единственной опцией в предыдущих версиях и работает со всеми медиа-приложениями.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Şimdi Çalınan' önceki sürümlerdeki tek seçenekti ve tüm medya uygulamalarıyla çalışır.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"'Зараз грає' був єдиним варіантом на попередніх версіях і працює з усіма медіа застосунками.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"“当前播放”是历史版本中的唯一选项。该选项可兼容所有媒体应用。\"\n          }\n        }\n      }\n    },\n    \"(%@)\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"(%@)\"\n          }\n        }\n      }\n    },\n    \"%.0f seconds\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f seconds\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f sekund\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f Sekunden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%.0f seconds\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%.0f seconds\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f segundos\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%.0f secondes\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f seconds\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f secondi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f 초\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f sekundy\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f segundos\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f секунды\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f saniye\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f секунд\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.0f 秒\"\n          }\n        }\n      }\n    },\n    \"%.1fs\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%.1fs\"\n          }\n        }\n      }\n    },\n    \"%@\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%@\"\n          }\n        }\n      }\n    },\n    \"%d%%\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%d%%\"\n          }\n        }\n      }\n    },\n    \"%lld\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld\"\n          }\n        }\n      }\n    },\n    \"%lld%%\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"%lld%%\"\n          }\n        }\n      }\n    },\n    \"• Bug fixes\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Bug fixes\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Opravy chyb\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Fehlerbehebungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• Bug fixes\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• Bug fixes\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Corrección de errores\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• Corrections de bugs\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Bug fixes\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Correzione bug\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"수정된 버그들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Poprawki błędów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Correções de bugs\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Исправления ошибок\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Hata düzeltmeleri\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Виправлення помилок\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Bug 修复\"\n          }\n        }\n      }\n    },\n    \"• Improved performance\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Improved performance\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Zlepšený výkon\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Verbesserte Leistung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• Improved performance\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• Improved performance\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Rendimiento mejorado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Amélioration des performances\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Improved performance\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Prestazioni migliorate\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• 향상된 성능\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Poprawiona wydajność\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Desempenho melhorado\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• повышение эффективности\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Geliştirilmiş performans\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Покращено продуктивність\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• 提升性能\"\n          }\n        }\n      }\n    },\n    \"• New feature 1\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• New feature 1\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Nová funkce 1\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Neue Funktion 1\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• New feature 1\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"• New feature 1\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Nueva funcionalidad 1\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nouvelle fonctionnalité 1\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• New feature 1\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Nuova funzionalità 1\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• 새 기능 1\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nowa funkcja 1\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Novo recurso 1\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• New feature 1\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yeni özellik 1\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• Нова функція 1\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"• 新功能1\"\n          }\n        }\n      }\n    },\n    \"About\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"About\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"O aplikaci\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Über\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"About\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"About\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Acerca de\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"À propos\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"About\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Info\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"정보\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"O programie\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sobre\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"О программе\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hakkında\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Про додаток\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"关于\"\n          }\n        }\n      }\n    },\n    \"Accent color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accent color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Barva odstínu\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Akzentfarbe\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Accent color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Accent colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color de resaltado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Couleur d'accent\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accent color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colore in rilievo\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"강조 색상\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kolor akcentu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cor de destaque\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Основной цвет\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vurgu rengi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Колір відтінку\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"首选颜色\"\n          }\n        }\n      }\n    },\n    \"Access Denied\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Access Denied\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přístup zamítnut\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zugriff verweigert\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Access Denied\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Access Denied\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Acceso denegado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accès Refusé\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Access Denied\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accesso Negato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"권한 거부됨\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odmowa dostępu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Acesso Negado\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Доступ запрещен\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erişim Reddedildi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Доступ заборонено\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"访问被拒绝\"\n          }\n        }\n      }\n    },\n    \"Accessibility access is required to replace the system HUD.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Der Zugriff auf Barrierefreiheit ist erforderlich, um das System HUD zu ersetzen.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시스템 HUD를 교체하기 위해서는 접근성 권한이 필요합니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zezwolenie dostępności jest wymagane, żeby zastąpić system HUD.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Accessibility access is required to replace the system HUD.\"\n          }\n        }\n      }\n    },\n    \"Add\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Add\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přidat\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hinzufügen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Añadir\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ajouter\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Add\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aggiungi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"추가\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dodaj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Adicionar\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Добавить\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ekle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Додати\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"添加\"\n          }\n        }\n      }\n    },\n    \"Add manually\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Add manually\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přidat manuálně\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Manuell hinzufügen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add manually\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add manually\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Añadir manualmente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ajouter manuellement\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Add manually\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aggiungi manualmente\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"수동 추가\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dodaj ręcznie\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Adicionar manualmente\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Добавить вручную\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Elle ekle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Додати вручну\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"手动添加\"\n          }\n        }\n      }\n    },\n    \"Add new visualizer\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Add new visualizer\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přidat nový vizualizér\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Neuen Visualisierer hinzufügen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add new visualizer\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Add new visualiser\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Añadir nuevo visualizador\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ajouter un nouveau visualiseur\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Add new visualizer\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aggiungi nuovo visualizzatore\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"새 시각화 도구 추가\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dodaj nowy wizualizer\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Adicionar novo visualizador\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Добавить визуализатор\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yeni görselleştirici ekle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Додати новий візуалізатор\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"添加新的可视化器\"\n          }\n        }\n      }\n    },\n    \"Additional features\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Additional features\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Další funkce\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zusätzliche Funktionen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Additional features\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Additional features\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Características adicionales\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fonctionnalités supplémentaires\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Additional features\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Funzionalità aggiuntive\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"추가 기능들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dodatkowe funkcje\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Recursos adicionais\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Дополнительные возможности\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ek özellikler\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Додаткові можливості\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"其他功能\"\n          }\n        }\n      }\n    },\n    \"Advanced\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erweitert\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"고급\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zaawansowane\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Advanced\"\n          }\n        }\n      }\n    },\n    \"All-day\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"All-day\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Celý den\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ganztägig\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"All-day\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"All-day\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Todo el día\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toute la journée\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"All-day\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tutto Il Giorno\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"하루 종일\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Całodniowy\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dia inteiro\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"All-day\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gün boyu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Увесь день\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"全天\"\n          }\n        }\n      }\n    },\n    \"Allow Access\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Allow Access\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Povolit přístup\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zugriff erlauben\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Allow Access\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Allow Access\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Permitir acceso\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Autoriser l'accès\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Allow Access\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Consenti Accesso\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"권한 허용\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zezwól na dostęp\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Permitir acesso\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Разрешить доступ\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erişime izin ver\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Надати доступ\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"允许访问\"\n          }\n        }\n      }\n    },\n    \"Always show full event titles\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vollständige Ereignistitel immer anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"항상 이벤트 제목 보이기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zawsze pokazuj pełne tytuły wydarzeń\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show full event titles\"\n          }\n        }\n      }\n    },\n    \"Always show tabs\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show tabs\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vždy zobrazovat panely\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Immer Tabs anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Always show tabs\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Always show tabs\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar siempre las pestañas\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toujours afficher les onglets\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Always show tabs\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra sempre schede\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"항상 탭 보이기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zawsze pokazuj zakładki\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Permitir mostrar guias\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Всегда показывать вкладки\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sekmeleri daima göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Завжди показувати вкладки\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"总是显示标签页\"\n          }\n        }\n      }\n    },\n    \"App icon\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"App icon\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ikona aplikace\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"App Icon\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"App icon\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"App icon\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Icono de la aplicación\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Icône d'application\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"App icon\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Icona app\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"앱 아이콘\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ikona aplikacji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ícone do App\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Значок приложения\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uygulama simgesi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Значок додатка\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"应用图标\"\n          }\n        }\n      }\n    },\n    \"Appearance\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Appearance\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vzhled\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Darstellung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Appearance\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Appearance\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apariencia\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apparence\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Appearance\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aspetto\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"모양\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wygląd\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aparência\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Внешний вид\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Belirme\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Вигляд\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"外观\"\n          }\n        }\n      }\n    },\n    \"Auto-scroll to next event\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatisch zum nächsten Ereignis scrollen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"다음 이벤트로 오토-스크롤\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatyczne przewijanie do następnego wydarzenia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auto-scroll to next event\"\n          }\n        }\n      }\n    },\n    \"Automatically check for updates\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatically check for updates\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automaticky vyhledávat aktualizace\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatisch nach Updates suchen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Automatically check for updates\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Automatically check for updates\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Comprobar actualizaciones automáticamente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vérifier les mises à jour automatiquement\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatically check for updates\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Controlla automaticamente gli aggiornamenti\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"자동으로 업데이트 확인\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatycznie sprawdzaj aktualizacje\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Procurar por atualizações automaticamente\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Автоматически проверять наличие обновлений\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Güncellemeleri otomatik kontrol et\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Автоматично перевіряти наявність оновлень\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自动检查更新\"\n          }\n        }\n      }\n    },\n    \"Automatically download updates\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatically download updates\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automaticky stahovat aktualizace\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Updates automatisch herunterladen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Automatically download updates\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Automatically download updates\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descargar actualizaciones automáticamente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Télécharger les mises à jour automatiquement\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatically download updates\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scarica automaticamente gli aggiornamenti\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"자동으로 업데이트 다운로드\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatycznie pobieraj aktualizacje\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Baixar atualizações automaticamente\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Автоматически скачивать обновления\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Güncellemeleri otomatik indir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Автоматично завантажувати оновлення\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自动下载更新\"\n          }\n        }\n      }\n    },\n    \"Automatically switch displays\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatically switch displays\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automaticky přepnout obrazovky\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Anzeige automatisch wechseln\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Automatically switch displays\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Automatically switch displays\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cambiar automáticamente las pantallas\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Basculer automatiquement d'écran\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatically switch displays\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cambiamento automatico display\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"디스플레이 자동 변경\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Automatycznie przełączaj wyświetlacze\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Trocar telas automaticamente\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Автоматически переключать экраны\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ekranları otomatik olarak değiştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Автоматично перемикати екрани\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自动切换显示器\"\n          }\n        }\n      }\n    },\n    \"Battery\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Baterie\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterie\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batería\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batteria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"배터리\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bateria\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bateria\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Батарея\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pil\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Акумулятор\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"电池\"\n          }\n        }\n      }\n    },\n    \"Battery Information\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery Information\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informace o baterii\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterie Informationen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery Information\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery Information\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Información de la batería\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informations de la batterie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery Information\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informazioni Batteria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"배터리 정보\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informacje o baterii\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informações da Bateria\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Информация о батарее\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pil bilgisi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Інформація про акумулятор\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"电池信息\"\n          }\n        }\n      }\n    },\n    \"Battery Settings\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery Settings\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nastavení baterie\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterie Einstellungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery Settings\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery Settings\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Configuración de la batería\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Paramètres de la batterie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery Settings\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Impostazioni Batteria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"배터리 설정\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ustawienia baterii\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Configurações da Bateria\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Параметры батареи\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pil ayarları\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Налаштування акумулятора\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"电池设置\"\n          }\n        }\n      }\n    },\n    \"Battery Status\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery Status\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stav baterie\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterie Status\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery Status\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Battery Status\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estado de la batería\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Statut de la batterie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Battery Status\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stato Batteria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"배터리 상태\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stan baterii\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Status da bateria\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Состояние батареи\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pil durumu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Стан заряду акумулятора\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"电池状态\"\n          }\n        }\n      }\n    },\n    \"Boost your productivity with Clipboard Manager\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boost your productivity with Clipboard Manager\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zvyšte svou produktivitu pomocí správce schránky\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Steigere deine Produktivität mit dem Zwischenablage-Manager\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Boost your productivity with Clipboard Manager\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Boost your productivity with Clipboard Manager\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aumenta tu productividad con Clipboard Manager\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boostez votre productivité avec le gestionnaire de presse-papiers\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boost your productivity with Clipboard Manager\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Incrementa la tua produttività con il gestore appunti\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"클립보드 매니저로 업무 효율을 높이세요\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zwiększ swoją produktywność dzięki menedżerowi schowka\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aumente sua produtividade com o Clipboard Manager\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boost your productivity with Clipboard Manager\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pano Yöneticisiyle üretkenliğinizi arttırın\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Підвищіть продуктивність за допомогою менеджера буфера обміну\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"使用剪贴板管理器提升你的效率\"\n          }\n        }\n      }\n    },\n    \"Boring Notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch\"\n          }\n        }\n      }\n    },\n    \"boring.notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"boring.notch\"\n          }\n        }\n      }\n    },\n    \"Calendar\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendar\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalendář\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalender\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Calendar\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Calendar\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendario\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendrier\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendar\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendario\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"캘린더\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalendarz\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendário\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Календарь\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Takvim\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Календар\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"日历\"\n          }\n        }\n      }\n    },\n    \"Calendar access is denied. Please enable it in System Settings.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendar access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přístup do kalendáře byl odepřen. Prosím povolte jej v nastavení systému.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalender Zugriff verweigert. Bitte aktiviere es in den Systemeinstellungen.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Calendar access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Calendar access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Acceso al calendario denegado. Por favor, habilítelo en Configuración del sistema.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"L'accès au calendrier est refusé. Veuillez l'activer dans les réglages.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendar access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"L'accesso al calendario è negato. Si prega di abilitarlo nelle impostazioni di sistema.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"캘린더 권한 거부됨. 시스템 설정에서 허용해주세요..\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dostęp do kalendarza został zablokowany. Proszę odblokować dostęp w Ustawieniach Systemowych.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Acesso ao Calendário negado. Por favor, autorize nas Configurações do Sistema.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Доступ к календарю запрещен. Пожалуйста, включите его в Системных настройках.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Takvim erişimi reddedildi. Lütfen Sistem Ayarlarından izin verin.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Доступ до календаря заборонений. Будь ласка, увімкніть його в Налаштуваннях Системи.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"日历访问被拒绝。请在系统设置中启用它。\"\n          }\n        }\n      }\n    },\n    \"Calendars\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendars\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalendáře\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalender\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Calendars\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Calendars\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendarios\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendriers\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendars\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendari\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"캘린더들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalendarze\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Calendários\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Календари\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Takvimler\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Календарі\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"日历\"\n          }\n        }\n      }\n    },\n    \"Cancel\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cancel\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zrušit\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abbrechen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Cancel\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Cancel\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cancelar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Annuler\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cancel\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Annulla\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"취소\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Anuluj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cancelar\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Отмена\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"İptal et\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Скасувати\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"取消\"\n          }\n        }\n      }\n    },\n    \"Change media with horizontal gestures\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medien mit horizontalen Gesten ändern\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"수평 제스처로 미디어 변경\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zmień multimedia używając poziomych gestów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Change media with horizontal gestures\"\n          }\n        }\n      }\n    },\n    \"Charging\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charging\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nabíjení\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lädt\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Charging\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Charging\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cargando\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"En charge\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charging\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Caricamento\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"충전 중\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ładowanie\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Carregando\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charging\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Şarj oluyor\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Заряджається\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"充电中\"\n          }\n        }\n      }\n    },\n    \"Charging on Hold: Desktop Mode\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charging on Hold: Desktop Mode\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nabíjení zadrženo: Režim plochy\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ladevorgang unterbrochen: Desktop-Modus\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Charging on Hold: Desktop Mode\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Charging on Hold: Desktop Mode\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Carga en Espera: Modo Escritorio\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charge en pause : mode bureau\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charging on Hold: Desktop Mode\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Caricamento in attesa: Modalità Desktop\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"충전 대기 중: 데스크탑 모드\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ładowanie wstrzymane: Tryb stacjonarny\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Carregando em espera: Modo Desktop\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Charging on Hold: Desktop Mode\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Şarj beklemede: Masaüstü Modu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Заряджання в Режимі очікування: Режим робочого столу\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"暂停充电：桌面模式\"\n          }\n        }\n      }\n    },\n    \"Check for Updates…\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Check for Updates…\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zkontrolovat aktualizace…\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Suche nach Updates…\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Check for Updates…\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Check for Updates…\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Buscar actualizaciones…\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vérifier les mises à jour…\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Check for Updates…\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Controlla aggiornamenti…\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"업데이트 확인…\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sprawdź aktualizacje…\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verificar atualizações…\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Проверить обновления…\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Güncellemeleri Kontrol et…\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Перевірка наявності оновлень…\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"检查更新…\"\n          }\n        }\n      }\n    },\n    \"Choose a Music Source\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose a Music Source\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vyberte zdroj hudby\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wähle eine Musikquelle\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Choose a Music Source\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Choose a Music Source\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Elige una Fuente de Música\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choisir une source de musique\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose a Music Source\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scegli una fonte musicale\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음악 소스 선택\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybierz źródło muzyki\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Escolha fonte da música\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Выбрать источник музыки\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Müzik kaynağı seç\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Оберіть джерело музики\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"选择音乐来源\"\n          }\n        }\n      }\n    },\n    \"Choose any color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Farbe auswählen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"아무 색상을 선택해 주세요\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybierz dowolny kolor\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose any color\"\n          }\n        }\n      }\n    },\n    \"Choose between your system accent color or customize it with your own selection.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wählen Sie zwischen der Akzentfarbe Ihres Systems oder passen Sie sie mit Ihrer eigenen Auswahl an.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent colour or customize it with your own selection.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시스템 강조 색상을 사용하거나 직접 색상을 선택해 설정하세요.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybierz kolor akcentu systemowego lub dostosuj go według własnego wyboru.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose between your system accent color or customize it with your own selection.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        }\n      }\n    },\n    \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wählen Sie den zu verwendenden Dienst aus, wenn Dateien geteilt werden. Klicken Sie auf die Ablage-Taste, um Dateien auszuwählen oder ziehen Sie Dateien auf die Ablage, um sie sofort zu teilen.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"선반에서 파일을 공유할 때 사용할 서비스를 선택하세요. 선반 버튼을 클릭해 파일을 선택하거나, 파일을 끌어다 놓으면 바로 공유할 수 있습니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybierz usługę używaną podczas udostępniania plików z półki. Kliknij przycisk półki, aby wybrać pliki, lub przeciągnij pliki na niego, aby udostępnić natychmiast.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\"\n          }\n        }\n      }\n    },\n    \"Circle\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Circle\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kruh\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kreis\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Circle\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Circle\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Círculo\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cercle\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Circle\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cerchio\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"원형의\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Okręg\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Círculo\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Circle\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Çember\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Коло\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"圆形\"\n          }\n        }\n      }\n    },\n    \"Clear slot\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Slot leeren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"슬롯 초기화\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyczyść slot\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Clear slot\"\n          }\n        }\n      }\n    },\n    \"Close\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"قُفْل\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zavřít\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schließen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Close\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Close\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cerrar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fermer\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Close\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Chiudi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"닫기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zamknij\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fechar\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Close\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kapat\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Закрити\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"关闭\"\n          }\n        }\n      }\n    },\n    \"Close gesture\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Close gesture\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesto zavření\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schließgeste\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Close gesture\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Close gesture\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesto de cerrar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Geste de fermeture\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Close gesture\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesto di chiusura\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"닫기 제스처\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gest zamknięcia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Close gesture\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Close gesture\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"El hareketlerini kapat\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Жест вимкнення\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"关闭手势\"\n          }\n        }\n      }\n    },\n    \"Closed Notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Closed Notch\"\n          }\n        }\n      }\n    },\n    \"Color Presets\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Farbvorgaben\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colour Presets\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"색상 프리셋들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Predefiniowane kolory\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color Presets\"\n          }\n        }\n      }\n    },\n    \"Colored spectrogram\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Farbiges Spektrogramm\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Coloured spectrogram\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"컬러 스펙트로그램\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kolorowy spektrogram\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colored spectrogram\"\n          }\n        }\n      }\n    },\n    \"Coming soon\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Coming soon\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Již brzy\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Demnächst\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Coming soon\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Coming soon\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Próximamente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bientôt disponible\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Coming soon\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In arrivo\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"곧 출시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Już wkrótce\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Em breve\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Coming soon\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Çok yakında geliyor\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Незабаром\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"即将上线\"\n          }\n        }\n      }\n    },\n    \"Continue\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Continue\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokračovat\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fortfahren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Continue\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Continue\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Continuar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Continuer\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Continue\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Continua\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"계속\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kontynuuj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Continue\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Продолжить\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Devam Et\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Далі\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"继续\"\n          }\n        }\n      }\n    },\n    \"Copy items on drag\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Elemente beim Draufziehen kopieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"드래그로 항목 복사\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kopiuj elementy po przeciągnięciu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copy items on drag\"\n          }\n        }\n      }\n    },\n    \"Corner radius scaling\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Skalierung Eckenradius\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Escalado del radio de las esquinas\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Redimensionnement du rayon des angles\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scala raggio angoli\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"모서리 반경 크기 조정\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Skalowanie zaokrąglenia narożników\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Corner radius scaling\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Köşe yarıçap ölçeği\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Масштабування радіусу кутів\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"圆角半径\"\n          }\n        }\n      }\n    },\n    \"Currently selected: %@\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Derzeit ausgewählt: %@\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"현재 선택된: %@\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aktualnie wybrane: %@\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Currently selected: %@\"\n          }\n        }\n      }\n    },\n    \"Custom\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Benutzerdefiniert\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자정의\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niestandardowe\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom\"\n          }\n        }\n      }\n    },\n    \"Custom height\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom height\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vlastní výška\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Benutzerdefinierte Höhe\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom height\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom height\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Altura personalizada\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Personnaliser la hauteur\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom height\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Altezza personalizzata\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자 정의 높이\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niestandardowa wysokość\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customizar altura\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom height\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Özel yükseklik\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Користувацька висота\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自定义高度\"\n          }\n        }\n      }\n    },\n    \"Custom music live activity animation\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom music live activity animation\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vlastní animace živé aktivity hudby\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Benutzerdefinierte Animation für Musik-Live-Aktivitäten\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom music live activity animation\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom music live activity animation\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Animación de actividad de música en vivo personalizada\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Personnaliser l'animation d'activité musicale\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom music live activity animation\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Animazione attività musicale personalizzata\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자 정의 음악 재생 애니메이션\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niestandardowa animacja aktywności muzyki\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customizar animação da música\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom music live activity animation\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Özel canlı müzik animasyonu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Власна анімація музики в реальному часі\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自定义音乐实时动画\"\n          }\n        }\n      }\n    },\n    \"Custom notch size - %.0f\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom notch size - %.0f\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vlastní velikost výřezu - %.0f\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Benutzerdefinierte Notchgröße - %.0f\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom notch size - %.0f\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom notch size - %.0f\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tamaño personalizado del notch - %.0f\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Personnaliser la taille de l'encoche - %.0f\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom notch size - %.0f\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dimensione notch personalizzata - %.0f\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자 정의 노치 사이즈 - %.0f\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niestandardowy rozmiar notcha - %.0f\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descrição\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom notch size - %.0f\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Özel çentik boyutu - %.0f\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Користувацький розмір брові - %.0f\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自定义Notch大小 - %.0f\"\n          }\n        }\n      }\n    },\n    \"Custom vizualizers (Lottie)\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom vizualizers (Lottie)\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vlastní vizualizátory (Lottie)\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Eigene Audiovisualisierung (via Lottie)\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom vizualizers (Lottie)\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Custom visualisers (Lottie)\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Visualizadores personalizados (Lottie)\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Visualiseurs personnalisés (Lottie)\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom vizualizers (Lottie)\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Visualizzatori personalizzati (Lottie)\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자 지정 시각화 도구(Lottie)\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niestandardowe wizualizatory (Lottie)\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customizar visualizadores (Lottie)\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Custom vizualizers (Lottie)\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Özel görselleştirmeler (Lottie)\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Користувацькі візуалізатори (Лотті)\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"自定义可视化器（Lottie）\"\n          }\n        }\n      }\n    },\n    \"Customize in Settings\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize in Settings\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přizpůsobit v nastavení\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In den Einstellungen anpassen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Customize in Settings\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Customise in Settings\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Personalizar en Ajustes\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Personnaliser dans les paramètres\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize in Settings\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Personalizza nelle impostazioni\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"설정에서 커스텀하기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dostosuj w ustawieniach\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customizar em Configurações\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize in Settings\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ayarlarda özelleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Змінити в Налаштуваннях\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"在设置中自定义\"\n          }\n        }\n      }\n    },\n    \"Customize which controls appear in the music player. Volume expands when active.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Passen Sie an, welche Steuerelemente im Musik-Player angezeigt werden. Die Lautstärke wird erweitert, wenn aktiviert.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음악 플레이어에 표시할 컨트롤을 사용자 지정하세요. 볼륨은 활성화되면 확장됩니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dostosuj które ustawienia wyświetlane w odtwarzaczu muzyki. Głośność rozwija się, gdy jest aktywna.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Customize which controls appear in the music player. Volume expands when active.\"\n          }\n        }\n      }\n    },\n    \"Default\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Default\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Výchozí\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Standard\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Default\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Default\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Por defecto\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Par défaut\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Default\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Predefinito\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"기본\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Domyślny\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Padrão\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Default\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Varsayılan\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Типово\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"默认\"\n          }\n        }\n      }\n    },\n    \"Description\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Description\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Popis\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Beschreibung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Description\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Description\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descripción\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Description\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Description\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descrizione\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"설명\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Opis\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descrição\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Description\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Açıklama\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Опис\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"描述\"\n          }\n        }\n      }\n    },\n    \"Disable\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disable\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vypnout\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Deaktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Disable\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Disable\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Deshabilitar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Désactiver\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disable\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disabilita\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"비활성화\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyłącz\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disable\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disable\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Devre dışı bırak\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Вимкнути\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"禁用\"\n          }\n        }\n      }\n    },\n    \"Disabled\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disabled\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vypnuto\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Deaktiviert\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Disabled\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Disabled\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Deshabilitado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Désactivé\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disabled\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disabilitato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"비활성화됨\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyłączone\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disabled\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disabled\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Devre dışı bırakıldı\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Вимкнено\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已禁用\"\n          }\n        }\n      }\n    },\n    \"Download\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Download\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stáhnout\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Herunterladen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Download\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Download\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descargar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Télécharger\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Download\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scarica\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"다운로드\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pobierz\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Download\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Download\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"İndir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Завантажити\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"下载\"\n          }\n        }\n      }\n    },\n    \"Downloads\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stahování\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Descargas\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Téléchargements\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scaricati\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"다운로드\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pobrane\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Downloads\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"İndirilenler\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Завантаження\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"下载\"\n          }\n        }\n      }\n    },\n    \"Drag a control onto a slot\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ziehen Sie ein Steuerelement auf einen Slot\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"컨트롤을 슬롯으로 끌어다 놓으세요.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Przeciągnij sterowanie na slot\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag a control onto a slot\"\n          }\n        }\n      }\n    },\n    \"Drag items in the preview to reorder or drop from the palette\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ziehen Sie Elemente in die Vorschau, um sie neu zu ordnen oder aus der Palette zu ziehen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미리보기에서 항목을 끌어 순서를 변경하거나 팔레트에서 끌어다 놓으세요.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Przeciągnij elementy w podglądzie, aby zmienić kolejność lub upuścić z palety\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drag items in the preview to reorder or drop from the palette\"\n          }\n        }\n      }\n    },\n    \"Drop files here\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drop files here\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přetáhněte soubory sem\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dateien hier ablegen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Drop files here\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Drop files here\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Suelte los archivos aquí\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Déposer les fichiers ici\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drop files here\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Trascina i file qui\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"여기로 파일을 드래그하세요\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upuść pliki tutaj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drop files here\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Drop files here\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dosyaları buraya bırak\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Перетягніть файл сюди\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"拖放文件到这里\"\n          }\n        }\n      }\n    },\n    \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Snadno kopírujte, ukládejte a spravujte svůj nejpoužívanejší obsah. Upgradujte nyní pro pokročilé funkce, jako je vícenásobné úložiště a rychlý přístup!\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kopieren, speichern und verwalten Sie ganz einfach Ihre am häufigsten verwendeten Inhalte. Aktualisieren Sie jetzt, um erweiterte Funktionen wie die Speicherung mehrerer Elemente und schnellen Zugriff zu erhalten!\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copie, almacene y gestione fácilmente su contenido más usado. ¡Actualice ahora para obtener características avanzadas como almacenamiento de múltiples elementos y acceso rápido!\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copiez, stockez et gérez facilement votre contenu le plus utilisé. Mettez à niveau maintenant pour des fonctionnalités avancées telles que le stockage de multiples items et un accès rapide !\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Copia facilmente, memorizza e gestisci i tuoi contenuti più usati. Aggiorna ora per funzioni avanzate come storage multi-item e accesso rapido!\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"자주 사용하는 파일들을 쉽게 복사, 저장, 관리하세요. 업그레이드 해서 다중 저장 기능과 빠른 접근 기능을 활성화하세요!\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Łatwo kopiuj, przechowuj i zarządzaj najczęściej używaną treścią. Uaktualnij teraz, aby uzyskać zaawansowane funkcje, takie jak przechowywanie wielu produktów i szybki dostęp!\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"En sık kullandığınız içerikleri kolayca kopyalayın, saklayın ve yönetin. Çoklu öğe depolama ve hızlı erişim gibi gelişmiş özellikler için şimdi yükseltin!\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"轻松复制、存储和管理您最常用的内容。升级后可解锁高级功能，如多项目存储和快速访问！\"\n          }\n        }\n      }\n    },\n    \"Edit layout\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Edit layout\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upravit rozložení\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout bearbeiten\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Edit layout\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Edit layout\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Editar diseño\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Modifier la mise en page\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Edit layout\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Modifica layout\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"레이아웃 수정\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Edytuj układ\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Edit layout\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Edit layout\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yerleşimi düzenle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Редагувати розташування\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"编辑布局\"\n          }\n        }\n      }\n    },\n    \"Enable blur effect behind album art\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable blur effect behind album art\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Povolit efekt rozostření za obalem alba\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unschärfe-Effekt hinter Albumcover aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable blur effect behind album art\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable blur effect behind album art\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar el efecto de difuminado detrás de la portada del álbum\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer l'effet de flou derrière la pochette d'album\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable blur effect behind album art\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita sfocatura dietro alla copertina dell'album\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"앨범 커버 뒤 블러효과 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz efekt rozmycia za okładką albumu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable blur effect behind album art\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable blur effect behind album art\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Albüm kapağının arkasındaki bulanıklık efektini aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Увімкнути ефект розмиття позаду обкладинки альбому\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用专辑封面的模糊效果\"\n          }\n        }\n      }\n    },\n    \"Enable boring mirror\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Povolit boring zrcadlo\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Spiegelfunktion aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar espejo boring\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer le miroir ennuyeux\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita specchio\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring 거울 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz boring mirror\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sıkıcı aynayı aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable boring mirror\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用虚拟镜像\"\n          }\n        }\n      }\n    },\n    \"Enable colored spectrograms\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable colored spectrograms\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Povolit barevné spektrogramy\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Farbe für Audiovisualisierung aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable colored spectrograms\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable coloured spectrograms\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar espectrogramas de colores\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer les spectrogrammes de couleurs\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable colored spectrograms\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita spettrogramma colorato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"컬러 스펙트로그램을 활성화하세요\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz kolorowe spektrogramy\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable colored spectrograms\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable colored spectrograms\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Renkli spektrogramları aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable colored spectrograms\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用彩色谱图\"\n          }\n        }\n      }\n    },\n    \"Enable gestures\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable gestures\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Povolit gesta\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gestensteuerung aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable gestures\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable gestures\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar gestos\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer les gestes\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable gestures\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita gesti\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"제스처 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz obsługę gestów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable gestures\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable gestures\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"El hareketlerini aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Увімкнути жести\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用手势\"\n          }\n        }\n      }\n    },\n    \"Enable glowing effect\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Leuchteffekt aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar efecto de resplandor\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer l'effet lumineux\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita effetto luminoso\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"발광 효과 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz efekt poświaty\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Parlama efektini aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable glowing effect\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用发光效果\"\n          }\n        }\n      }\n    },\n    \"Enable haptic feedback\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Haptisches Feedback aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"햅틱 피드백 활성화\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz informację zwrotną w postaci wibracji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable haptic feedback\"\n          }\n        }\n      }\n    },\n    \"Enable shelf\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable shelf\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Povolit polici\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ablage aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable shelf\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable shelf\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar bandeja\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer la bibliothèque\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable shelf\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita scaffale\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"서랍 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz półkę\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable shelf\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable shelf\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rafı aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Активувати полицю\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用暂存区\"\n          }\n        }\n      }\n    },\n    \"Enable window shadow\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fensterschatten aktivieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar sombra de la ventana\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer l'ombre de la fenêtre\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abilita ombra della finestra\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"윈도우 그림자 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz cień okna\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pencere gölgesini aktifleştir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enable window shadow\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"启用窗口阴影\"\n          }\n        }\n      }\n    },\n    \"Enhance your experience with HUDs\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vylepšete své zkušenosti s HUDy\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verbessere deine Erfahrung mit HUDs\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mejora tu experiencia con estilos de información en pantalla\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Améliorez votre expérience avec les HUD\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Migliora la tua esperienza con gli HUD\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD로 경험을 향상 하세요\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ulepsz swoje doświadczenia z HUD\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"BÜG ile deneyiminizi artırın\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enhance your experience with HUDs\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"用 HUD 提升您的体验\"\n          }\n        }\n      }\n    },\n    \"Enjoy your free time!\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enjoy your free time!\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Užijte si svůj volný čas!\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Genieße deine Freizeit!\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enjoy your free time!\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Enjoy your free time!\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"¡Disfruta de tu tiempo libre!\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Profitez de votre temps libre !\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enjoy your free time!\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Goditi il tuo tempo libero!\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"즐거운 시간 되세요!\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ciesz się swoim wolnym czasem!\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enjoy your free time!\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Enjoy your free time!\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kıymetli vaktinizin keyfini çıkarın!\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Насолоджуйтесь своїм вільним часом!\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"享受您的闲暇时光！\"\n          }\n        }\n      }\n    },\n    \"Expanded drag detection area\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erweiterter Ziehen Erkennungsbereich\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"확장된 드래그 감지 영역\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozszerzony obszar wykrywania przeciągnięcia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Expanded drag detection area\"\n          }\n        }\n      }\n    },\n    \"Extend hover area\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bereich für Mausberührung erweitern\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extender la zona de activación del cursor\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Étendre la zone de survol\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estendi area di attivazione col mouse\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"갖다대기 범위 늘이기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozszerz obszar najechania kursorem\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sürükleme alanını genişlet\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extend hover area\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"扩展悬停区域\"\n          }\n        }\n      }\n    },\n    \"Extensions\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozšíření\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erweiterungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensiones\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estensioni\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"확장기능들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozszerzenia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensions\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Genişletmeler\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Розширення\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"扩展\"\n          }\n        }\n      }\n    },\n    \"Files dropped on the shelf will be shared via this service\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dateien, die in der Ablage abgelegt werden, werden über diesen Dienst geteilt\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"선반에 놓인 파일은 이 서비스를 통해 공유됩니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pliki upuszczone na półkę będą udostępniane za pośrednictwem tej usługi\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Files dropped on the shelf will be shared via this service\"\n          }\n        }\n      }\n    },\n    \"Finish\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Finish\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dokončit\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Beenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Finish\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Finish\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Finalizar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Terminer\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Finish\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fine\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"완료\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zakończ\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Finish\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Finish\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bitir\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Завершити\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"完成\"\n          }\n        }\n      }\n    },\n    \"Full screen behavior\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vollbild-Verhalten\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"전체 화면 동작\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zachowanie na pełnym ekranie\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Full screen behavior\"\n          }\n        }\n      }\n    },\n    \"General\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"General\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Obecné\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Allgemein\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"General\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"General\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"General\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Général\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"General\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Generali\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"일반\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ogólne\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"General\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"General\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Genel\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"General\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"通用\"\n          }\n        }\n      }\n    },\n    \"Gesture control\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture control\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kontrola gest\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gestensteuerung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Gesture control\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Gesture control\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Control de gestos\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Contrôle gestuel\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture control\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Controllo gesti\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"제스처 컨트롤\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ustawienia gestów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture control\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture control\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hareketle Kontrol\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Керування жестами\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"手势控制\"\n          }\n        }\n      }\n    },\n    \"Gesture sensitivity\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture sensitivity\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Citlivost gesta\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesten Empfindlichkeit\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Gesture sensitivity\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Gesture sensitivity\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sensibilidad del gesto\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sensibilité de la gestuelle\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture sensitivity\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sensibilità gesti\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"제스처 민감도\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Czułość gestów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture sensitivity\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gesture sensitivity\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hareket hassasiyeti\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Чутливість жестів\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"手势灵敏度\"\n          }\n        }\n      }\n    },\n    \"Get started\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Začněte\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erste Schritte\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Comenzar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Commencez\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inizia\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시작하기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozpocznij\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Başlayın\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Get started\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"开始使用\"\n          }\n        }\n      }\n    },\n    \"GitHub\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub\"\n          }\n        }\n      }\n    },\n    \"Got it!\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mám to!\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verstanden!\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"¡Entendido!\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Compris !\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Capito!\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"알겠습니다!\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozumiem!\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Anlaşıldı!\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Got it!\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"明白了！\"\n          }\n        }\n      }\n    },\n    \"Gradient\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradient\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přechod\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Farbverlauf\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Gradient\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Gradient\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradiente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dégradé\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradient\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sfumatura\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"그라디언트\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradient\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradiente\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradient\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gradyan\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Градієнт\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"渐变\"\n          }\n        }\n      }\n    },\n    \"Hide all-day events\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ganztägige Ereignisse ausblenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"종일 이벤트를 숨기기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukryj wydarzenia całodniowe\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide all-day events\"\n          }\n        }\n      }\n    },\n    \"Hide completed reminders\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide completed reminders\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schovat vyřízené připomínky\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erledigte Erinnerungen verbergen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide completed reminders\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide completed reminders\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ocultar recordatorios completados\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cacher les rappels terminés\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide completed reminders\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"완료된 알림 숨기기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukryj zakończone przypomnienia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide completed reminders\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide completed reminders\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tamamlanan Hatırlatıcıları gizle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Приховати завершені нагадування\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"隐藏已完成提醒\"\n          }\n        }\n      }\n    },\n    \"Hide for all apps\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Für alle Apps ausblenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"모든 앱에서 숨기기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukryj dla wszystkich aplikacji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for all apps\"\n          }\n        }\n      }\n    },\n    \"Hide for media app only\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nur für Medien-App ausblenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미디어 앱에서만 숨기기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukryj tylko dla aplikacji multimediów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide for media app only\"\n          }\n        }\n      }\n    },\n    \"Hide from screen recording\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bei Bildschirmaufnahmen ausblenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"화면 녹화 시 숨기기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukryj przed nagrywaniem ekranu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide from screen recording\"\n          }\n        }\n      }\n    },\n    \"Hide title bar\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Titelleiste ausblenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"제목 표시줄 숨기기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukryj pasek tytułu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hide title bar\"\n          }\n        }\n      }\n    },\n    \"Hierarchical\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchické\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchisch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Jerárquico\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hiérarchique\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gerarchico\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"계층적\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchiczny\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hiyerarşik\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hierarchical\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"分级\"\n          }\n        }\n      }\n    },\n    \"High\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"High\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vysoká\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hoch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"High\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"High\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Alto\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Haut\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"High\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Alta\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"높음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wysoki\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"High\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"High\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yüksek\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Високий\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"高\"\n          }\n        }\n      }\n    },\n    \"Hover delay\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schwebeverzögerung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"호버 지연 시간\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Opóźnienie po najechaniu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hover delay\"\n          }\n        }\n      }\n    },\n    \"https://github.com/pear-devs/pear-desktop\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"https://github.com/pear-devs/pear-desktop\"\n          }\n        }\n      }\n    },\n    \"HUD style\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Styl HUD\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD Stil\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estilo de la información en pantalla\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Style de l'HUD\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stile HUD\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD 스타일\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Styl HUD\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"BÜG stili\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD style\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD 样式\"\n          }\n        }\n      }\n    },\n    \"HUDs\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDy\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estilos de información en pantalla\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Interfejsy HUD\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"BÜG (Baş Üstü Göstergesi)\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUDs\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"HUD\"\n          }\n        }\n      }\n    },\n    \"In progress\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In progress\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Probíhá\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In Bearbeitung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In progress\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"In progress\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"En progreso\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"En cours\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In progress\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In corso\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"진행 중\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"W toku\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In progress\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"İşleniyor\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"В процесі\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"进行中\"\n          }\n        }\n      }\n    },\n    \"Inline\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"V řadě\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"En Línea\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In linea\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"인라인\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wbudowany\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Em linha\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hiza\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Inline\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"内嵌\"\n          }\n        }\n      }\n    },\n    \"Installed extensions\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Installed extensions\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nainstalovaná rozšíření\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Installierte Erweiterungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Installed extensions\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Installed extensions\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensiones instaladas\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Extensions installées\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Installed extensions\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estensioni installate\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"설치된 확장기능들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zainstalowane rozszerzenia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Installed extensions\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Installed extensions\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yüklenmiş eklentiler\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Встановлені розширення\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已安装扩展\"\n          }\n        }\n      }\n    },\n    \"Layout Preview\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layoutvorschau\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"레이아웃 미리보기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Podgląd układu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Layout Preview\"\n          }\n        }\n      }\n    },\n    \"Lottie JSON URL\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lien URL Lottie JSON\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"URL Lottie JSON\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON URL\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lottie JSON 链接\"\n          }\n        }\n      }\n    },\n    \"Low\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nízká\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niedrig\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bajo\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bas\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bassa\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"낮음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niski\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Düşük\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"低\"\n          }\n        }\n      }\n    },\n    \"Low Power Mode\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low Power Mode\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Režim nízké spotřeby\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Energiesparmodus\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Low Power Mode\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Low Power Mode\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Modo de bajo consumo\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mode économie d'énergie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low Power Mode\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Modalità Bassa Potenza\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"저전력 모드\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tryb niskiego zużycia energii\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low Power Mode\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Low Power Mode\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Düşük Güç Modu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Режим економії енергії\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"低电量模式\"\n          }\n        }\n      }\n    },\n    \"Made with 🫶🏻 by not so boring not.people\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vyrobeno s 🫶🏻 ne tak nudnými ne lidmi\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gemacht mit 🫶🏻 durch nicht so boring not.people\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hecho con 🫶🏻 por gente no tan aburrida\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fait avec 🫶🏻 par des personnes pas si ennuyeuses - boring not.people\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Realizzato col 🫶🏻 da not so boring not.people\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"지루하지 않은 비사람들이 🫶🏻 마음을 담아 만들었습니다\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zrobione z 🫶🏻 przez niezbyt nudnych ludzi\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"O kadar da sıkıcı olmayan insanlar tarafından 🫶🏻 ile yapılmıştır\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Made with 🫶🏻 by not so boring not.people\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"不那么无趣的 not.people 团队 用🫶🏻出品\"\n          }\n        }\n      }\n    },\n    \"Mark as complete\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as complete\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Označit jako dokončené\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Als erledigt markieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mark as complete\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mark as complete\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Marcar como completado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Marquer comme terminé\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as complete\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Segna come completato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"완료로 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Oznacz jako ukończone\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as complete\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as complete\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tamamlanmış olarak işaretle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Позначити як виконане\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"标记为已完成\"\n          }\n        }\n      }\n    },\n    \"Mark as incomplete\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as incomplete\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Označit jako nedokončené\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Als unvollständig markieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mark as incomplete\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mark as incomplete\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Marcar como incompleto\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Marquer comme incomplet\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as incomplete\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Segna come incompleto\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미완료로 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Oznacz jako nieukończone\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as incomplete\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mark as incomplete\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tamamlanmamış olarak işaretle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Позначити як незавершене\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"标记为未完成\"\n          }\n        }\n      }\n    },\n    \"Match menu bar height\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"An Höhe der Menüleiste anpassen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"메뉴바 높이에 맞추기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dopasuj do wysokości paska menu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menu bar height\"\n          }\n        }\n      }\n    },\n    \"Match menubar height\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odpovídat výšce menubaru\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Höhe der Menüleiste\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Igualar la altura de la Barra de menús\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Atteindre la hauteur de la barre de menu\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Adatta altezza menubar\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"메뉴바 높이에 맞추기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dopasuj do wysokości paska menu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Menubar yüksekliğini eşle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match menubar height\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"匹配菜单栏高度\"\n          }\n        }\n      }\n    },\n    \"Match real notch height\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"An Größe der echten Notch anpassen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"실제 노치 크기에 맞추기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Dopasuj do rzeczywistego rozmiaru notcha\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Match real notch height\"\n          }\n        }\n      }\n    },\n    \"Max Capacity: %lld%%\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Maximální kapacita: %lld%%\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Max. Kapazität: %lld%%\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Capacidad máxima: %lld%%\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Capacité maximale : %lld%%\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Capacità Massima: %lld%%\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"최대 용량: %lld%%\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Maksymalna pojemność: %lld%%\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Maksimum Kapasite: %lld%%\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Max Capacity: %lld%%\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"最大容量：%lld%%\"\n          }\n        }\n      }\n    },\n    \"Media\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Média\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Multimedia\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Média\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미디어\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Multimedia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medya\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"媒体\"\n          }\n        }\n      }\n    },\n    \"Media controls\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ovládání médií\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mediensteuerelemente\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Controles multimedia\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Contrôles des médias\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Controlli multimediali\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미디어 컨트롤\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ustawienia multimediów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medya kontrolleri\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media controls\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"媒体控制\"\n          }\n        }\n      }\n    },\n    \"Media inactivity timeout\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Časový limit nečinnosti médií\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Timeout für Medieninaktivität\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tiempo de espera para inactividad multimedia\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Délai d'inactivité du média\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Timeout inattività media\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미디어 비활성화 시간 초과\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Limit czasu braku aktywności multimediów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medya etkin olmayan zaman aşımı\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media inactivity timeout\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"媒体非活动超时\"\n          }\n        }\n      }\n    },\n    \"Media playback live activity\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media playback live activity\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Živá aktivita přehrávání médií\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Live-Aktivität für Medien\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media playback live activity\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media playback live activity\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Actividad de reproducción multimedia\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activité de lecture de média en direct\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media playback live activity\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Riproduzione media attività live\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미디어 재생 실시간 활동\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odtwarzanie multimediów na żywo\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Atividade ao vivo da reprodução de mídia\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media playback live activity\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medya oynatma canlı etkinliği\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media playback live activity\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"媒体播放实时动态\"\n          }\n        }\n      }\n    },\n    \"Media Source\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zdroj médií\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medienquelle\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fuente de la multimedia\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Source du Média\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sorgente multimediale\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미디어 소스\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Źródło multimediów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medya kaynağı\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media Source\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"媒体来源\"\n          }\n        }\n      }\n    },\n    \"Medium\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Střední\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mittel\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mediano\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Moyen\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Media\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"중간\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Średni\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Orta\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Medium\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"中\"\n          }\n        }\n      }\n    },\n    \"Mic %@\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mikrofon %@\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mikrofon %@\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Micrófono %@\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"마이크 %@\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mikrofon %@\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mikrofon %@\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mic %@\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"麦克风 %@\"\n          }\n        }\n      }\n    },\n    \"Mirror\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zrcátko\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Spiegel\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Espejo\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Miroir\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Specchio\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"거울\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Lustro\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ayna\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"镜子\"\n          }\n        }\n      }\n    },\n    \"Mirror shape\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tvar zrcadla\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Spiegelform\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Forma del espejo\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Forme du miroir\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Forma dello specchio\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"거울 모양\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kształt lustra\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ayna şekli\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mirror shape\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"镜子大小\"\n          }\n        }\n      }\n    },\n    \"More\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"More\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Více\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mehr\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"More\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"More\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Más\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Plus\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"More\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Altro\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"더\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Więcej\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"More\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"More\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Daha fazla\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"More\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"更多\"\n          }\n        }\n      }\n    },\n    \"Music Source\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zdroj hudby\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Musikquelle\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fuente de la música\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Source de la musique\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sorgente Musica\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음악 소스\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Źródło muzyki\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Müzik kaynağı\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Music Source\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"音乐源\"\n          }\n        }\n      }\n    },\n    \"muted\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"ztlumeno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stumm\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"audio desactivado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"en sourdine\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"mutato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음소거됨\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyciszony\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"susturuldu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"muted\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已静音\"\n          }\n        }\n      }\n    },\n    \"Muted\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Muted\"\n          }\n        }\n      }\n    },\n    \"Name\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Jméno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nombre\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nom\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nome\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"이름\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nazwa\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"İsim\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Name\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"名称\"\n          }\n        }\n      }\n    },\n    \"Never hide\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nikdy neskrývat\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Niemals ausblenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nunca ocultar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ne jamais masquer\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Non nascondere mai\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"숨기지 않기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nigdy nie ukrywaj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Asla gizleme\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Never hide\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"永不隐藏\"\n          }\n        }\n      }\n    },\n    \"No custom animation available\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Žádná vlastní animace není k dispozici\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Keine benutzerdefinierte Animation verfügbar\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sin animación personalizada\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aucune animation personnalisée disponible\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nessuna animazione personalizzata disponibile\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자 정의 애니메이션 없음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Brak dostępnych niestandardowych animacji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Özel animasyon mevcut değil\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom animation available\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"没有可用的自定义动画\"\n          }\n        }\n      }\n    },\n    \"No custom visualizer\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom visualizer\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Žádný vlastní vizualizér není k dispozici\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Keine benutzerdefinierte Audiovisualisierung\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No custom visualizer\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No custom visualiser\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sin visualizador personalizado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aucun visualiseur personnalisé\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom visualizer\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nessun visualizzatore personalizzato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사용자 지정 시각화 도구 없음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Brak niestandardowych wizualizerów\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom visualizer\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom visualizer\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Özel görselleştirici bulunmuyor\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No custom visualizer\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"没有自定义可视化器\"\n          }\n        }\n      }\n    },\n    \"No events\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Keine Ereignisse\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"이벤트 없음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Brak wydarzeń\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events\"\n          }\n        }\n      }\n    },\n    \"No events today\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Žádné události\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Keine Ereignisse heute\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sin eventos hoy\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aucun événement aujourd'hui\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Non ci sono eventi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"오늘 일정 없음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Brak wydarzeń na dzisiaj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bugün başka etkinlik yok\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No events today\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"今天无日程\"\n          }\n        }\n      }\n    },\n    \"No extension installed\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Žádné rozšíření není nainstalováno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Keine Erweiterungen installiert\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ninguna extensión instalada\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aucune extension installée\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nessuna estensione installata\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"설치된 확장기능 없음\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Brak zainstalowanych rozszerzeń\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hiçbir uzantı yüklenmemiş\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"No extension installed\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"未安装扩展\"\n          }\n        }\n      }\n    },\n    \"Not Now\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Teď ne\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nicht jetzt\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ahora no\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pas pour l'instant\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Non Ora\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"나중에\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nie teraz\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Şimdi Değil\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Not Now\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"不是现在\"\n          }\n        }\n      }\n    },\n    \"Notch behavior\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Chování výřezu\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verhalten der Notch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Comportamiento del notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Comportement du Notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Comportamento notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치 동작\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zachowanie notcha\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Çentik davranışı\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch behavior\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch行为\"\n          }\n        }\n      }\n    },\n    \"Notch height on non-notch displays\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch Höhe auf nicht-notch Displays\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치 없는 디스플레이에서 노치 높이\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wysokość notcha na wyświetlaczach bez notcha\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on non-notch displays\"\n          }\n        }\n      }\n    },\n    \"Notch height on notch displays\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch Höhe auf Notch Displays\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치 디스플레이에서 노치 높이\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wysokość notcha na wyświetlaczach z notchem\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch height on notch displays\"\n          }\n        }\n      }\n    },\n    \"Notch sizing\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notchgröße\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치 크기 조정\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rozmiar Notcha\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch sizing\"\n          }\n        }\n      }\n    },\n    \"Open Calendar Settings\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Otevřít nastavení Kalendáře\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalendereinstellungen öffnen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abrir ajustes de Calendario\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ouvrir les paramètres du calendrier\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apri Impostazioni Calendario\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"캘린더 설정 열기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Otwórz ustawienia kalendarza\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Takvim Ayarlarını Aç\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Calendar Settings\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"打开日历设置\"\n          }\n        }\n      }\n    },\n    \"Open Notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Notch\"\n          }\n        }\n      }\n    },\n    \"Open notch on hover\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch bei Mausberührung öffnen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abrir el notch al pasar el cursor\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ouvrir l'encoche au survol\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apri notch portando il mouse sopra di esso\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"갖다대서 노치 열기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Otwórz notch po najechaniu kursorem\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fare sürüklendiğinde çentiği aç\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open notch on hover\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"悬停时打开Notch\"\n          }\n        }\n      }\n    },\n    \"Open Reminder Settings\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Otevřít nastavení Připomínek\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erinnerungseinstellungen öffnen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abrir ajustes de Recordatorios\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ouvrir les paramètres de rappel\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apri Impostazioni Promemoria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미리알림 설정 열기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Otwórz ustawienia przypomnień\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Anımsatıcı Ayarlarını Aç\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open Reminder Settings\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"打开提醒事项设置\"\n          }\n        }\n      }\n    },\n    \"Open shelf by default if items are present\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Standardmäßig Ablage öffnen, wenn Dateien abgelegt sind\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Abrir siempre la bandeja si hay elementos dentro\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ouvrir la bibliothèque par défaut si des éléments sont présents\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apri automaticamente lo scaffale se contiene elementi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"항목이 있으면 기본으로 선반 열기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Otwórz półkę domyślnie, jeśli pliki są obecne\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Öğeler varsa varsayılan olarak rafı aç\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Open shelf by default if items are present\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"当项目存在时默认打开暂存器\"\n          }\n        }\n      }\n    },\n    \"Option key behaviour\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verhalten der Optionstaste\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"옵션 키 동작\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zachowanie klawisza opcji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Option key behaviour\"\n          }\n        }\n      }\n    },\n    \"Pick a Color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wähle eine Farbe\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"색상 선택\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybierz kolor\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pick a Color\"\n          }\n        }\n      }\n    },\n    \"Plugged In\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Připojeno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Angeschlossen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Conectado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Branché\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Collegato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"충전 중\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Podłączony\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Prize takılı\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Plugged In\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已插入\"\n          }\n        }\n      }\n    },\n    \"Preferred display\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bevorzugter Bildschirm\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"선호 디스플레이\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferowany wyświetlacz\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Preferred display\"\n          }\n        }\n      }\n    },\n    \"Progress bar style\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Progress bar style\"\n          }\n        }\n      }\n    },\n    \"Quick Share\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schnellteilen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"퀵 쉐어\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Szybkie udostępnianie\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share\"\n          }\n        }\n      }\n    },\n    \"Quick Share Service\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schnellteildienst\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"퀵 쉐어 서비스\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Usługa szybkiego udostępniania\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quick Share Service\"\n          }\n        }\n      }\n    },\n    \"Quit\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukončit\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verlassen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cerrar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quitter\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Chiudi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"종료\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyjdź\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Çık\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"退出\"\n          }\n        }\n      }\n    },\n    \"Quit app\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ukončit aplikaci\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"App verlassen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cerrar aplicación\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quitter l'application\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Chiudi app\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"앱 종료\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyjdź z aplikacji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uygulamadan Çık\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quit app\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"退出应用\"\n          }\n        }\n      }\n    },\n    \"Release name\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Název vydání\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Veröffentlichungsname\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nombre de la versión\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nom de la version\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nome del rilascio\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"릴리즈 이름\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nazwa wydania\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yayın İsmi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Release name\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"版本名称\"\n          }\n        }\n      }\n    },\n    \"Remember last tab\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zapamatovat poslední kartu\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Merke den letzten Tab\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Recordar la última pestaña\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Se souvenir du dernier onglet\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ricorda l'ultima scheda\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"마지막 탭 기억하기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zapamiętaj ostatnią kartę\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Son sekmeyi hatırla\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remember last tab\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"记住上一个标签页\"\n          }\n        }\n      }\n    },\n    \"Reminder access is denied. Please enable it in System Settings.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Přístup do kalendáře byl odepřen. Prosím povolte jej v nastavení systému.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erinnerungen Zugriff verweigert. Bitte aktiviere es in den Systemeinstellungen.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Acceso a Recordatorios denegado. Conceda acceso en Configuración del Sistema\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"L'accès au rappel est refusé. Veuillez l'activer dans les paramètres.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"L'accesso al promemoria è negato. Si prega di abilitarlo nelle impostazioni di sistema.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미리알림 엑세스 거부됨. 시스템설정에서 허용해주세요.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odmowa dostępu do przypomnień. Proszę włączyć dostęp w ustawieniach systemowych.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hatırlatıcılar erişimi reddedildi. Lütfen Sistem Ayarlarından izin verin.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminder access is denied. Please enable it in System Settings.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"提醒事项访问被拒绝。请在系统设置中启用它。\"\n          }\n        }\n      }\n    },\n    \"Reminders\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Připomínky\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Erinnerungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Recordatorios\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rappels\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Promemoria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"미리알림들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Przypomnienia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hatırlatıcılar\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reminders\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"提醒事项\"\n          }\n        }\n      }\n    },\n    \"Remove from shelf after dragging\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nach Ziehen aus der Ablage entfernen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"드래그 후 선반에서 제거\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Usuń z półki po przeciąganiu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Remove from shelf after dragging\"\n          }\n        }\n      }\n    },\n    \"Replace system HUD\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System HUD ersetzen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시스템 HUD 교체\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zastąp system HUD\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replace system HUD\"\n          }\n        }\n      }\n    },\n    \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\"\n          }\n        }\n      }\n    },\n    \"Request Accessibility\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bedienungshilfen anfordern\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"접근성 권한 요청\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Prośba o dostępność\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Request Accessibility\"\n          }\n        }\n      }\n    },\n    \"Reset to Defaults\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auf Standard zurücksetzen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"기본값으로 재설정\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Przywróć ustawienia domyślne\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reset to Defaults\"\n          }\n        }\n      }\n    },\n    \"Restart\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restartovat\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Neustarten\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reiniciar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Redémarrer\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Riavvia\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"재시작\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uruchom ponownie\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yeniden başlat\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"重启\"\n          }\n        }\n      }\n    },\n    \"Restart Boring Notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restartovat Boring Notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch neu starten\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Reiniciar Boring Notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Redémarrer Boring Notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Riavvia Boring Notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch 재시작\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uruchom ponownie Boring Notch\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Boring Notch'u yeniden başlat\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Restart Boring Notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"重启 Boring Notch\"\n          }\n        }\n      }\n    },\n    \"Running\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Běží\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Läuft\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ejecutándose\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"En cours d'exécution\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"In esecuzione\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"실행 중\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uruchomiony\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Çalışıyor\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Running\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"运行中\"\n          }\n        }\n      }\n    },\n    \"Select the music source you want to use. You can change this later in the app settings.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vyberte zdroj hudby který chcete používat. Můžete jej později změnit v nastavení aplikace.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wähle die Musik Quelle aus, die du benutzen willst. Du kannst dies später in den Einstellungen ändern.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Seleccione la fuente de la música que desea utilizar. Puede cambiar esto más tarde en los ajustes de la aplicación.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sélectionnez la source de musique que vous souhaitez utiliser. Vous pourrez la modifier plus tard dans les paramètres de l'application.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Selezionare la sorgente musicale che si desidera utilizzare. È possibile modificare questa impostazione in seguito nelle impostazioni dell'app.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음악 소스 선택하세요. 나중에 앱 설정에서 변경 가능합니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybierz źródło muzyki, którego chcesz użyć. Możesz to później zmienić w ustawieniach aplikacji.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kullanmak istediğiniz müzik kaynağını seçin. Bunu daha sonra uygulama ayarlarından değiştirebilirsiniz.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Select the music source you want to use. You can change this later in the app settings.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"选择您想要使用的音乐源。您可以稍后在应用设置中更改此项。\"\n          }\n        }\n      }\n    },\n    \"selected\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"vybráno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"ausgewählt\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"seleccionado(s)\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"sélectionné(s)\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"selezionato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"선택된\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"zaznaczone\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"seçildi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"selected\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已选择\"\n          }\n        }\n      }\n    },\n    \"Selected animation\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vybraná animace\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ausgewählte Animation\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Animación seleccionada\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Animations sélectionné(es)\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Animazione selezionata\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"선택된 애니메이션\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wybrana animacja\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Seçili animasyon\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Selected animation\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"所选动画\"\n          }\n        }\n      }\n    },\n    \"Settings\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nastavení\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Einstellungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ajustes\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Paramètres\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Impostazioni\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"설정\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ustawienia\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ayarlar\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Settings\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"设置\"\n          }\n        }\n      }\n    },\n    \"Shelf\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Police\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ablage\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bandeja\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Étagère\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scaffale\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"서랍\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Półka\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Raf\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shelf\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"暂存器\"\n          }\n        }\n      }\n    },\n    \"Shortcuts\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zkratky\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tastaturkürzel\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Atajos\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Raccourcis\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scorciatoie\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"바로가기들\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Skróty klawiszowe\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kestirmeler\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Shortcuts\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"快捷方式\"\n          }\n        }\n      }\n    },\n    \"Show battery indicator\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobrazit stav baterie\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterie-Symbol anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar indicador de la batería\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Afficher l'indicateur de la batterie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra indicatore della batteria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"배터리 상태 표시하기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż wskaźnik baterii\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pil göstergesini göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery indicator\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"显示电量\"\n          }\n        }\n      }\n    },\n    \"Show battery percentage\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobrazit % baterie\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Batterieanzeige in Prozent\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar porcentaje de la batería\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Afficher le pourcentage de la batterie\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra percentuale batteria\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"배터리 퍼센트 표시하기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż procent baterii\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pil yüzdesini göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show battery percentage\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"显示电量百分比\"\n          }\n        }\n      }\n    },\n    \"Show calendar\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobrazit kalendář\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kalender anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar Calendario\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Afficher le calendrier\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra calendario\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"캘린더 보이기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż kalendarz\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Takvimi göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show calendar\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"显示日历\"\n          }\n        }\n      }\n    },\n    \"Show cool face animation while inactive\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zeige coole Gesichtsanimation während Inaktivität\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"비활성 시 쿨한 얼굴 애니메이션 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyświetlaj fajną animację twarzy podczas bezczynności\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show cool face animation while inactive\"\n          }\n        }\n      }\n    },\n    \"Show HUD in open notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show HUD in open notch\"\n          }\n        }\n      }\n    },\n    \"Show lyrics below artist name\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Songtexte unter Künstlernamen anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"아티스트 이름 아래 가사 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż tekst utworu pod nazwą wykonawcy\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show lyrics below artist name\"\n          }\n        }\n      }\n    },\n    \"Show menu bar icon\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Menüleistensymbol anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"메뉴 막대 아이콘 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz ikonę na pasku menu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show menu bar icon\"\n          }\n        }\n      }\n    },\n    \"Show music live activity\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Musik-Live-Aktivität anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음악 실시간 현황 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż aktywność muzyczną na żywo\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show music live activity\"\n          }\n        }\n      }\n    },\n    \"Show notch on lock screen\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch auf dem Sperrbildschirm anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"잠금 화면에 노치 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż notch na ekranie blokady\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show notch on lock screen\"\n          }\n        }\n      }\n    },\n    \"Show on all displays\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobrazit na všech obrazovkách\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auf allen Displays anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar en todas las pantallas\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Afficher sur tous les écrans\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra su tutti gli schermi\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"모든 디스플레이에 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż na wszystkich wyświetlaczach\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tüm ekranlarda göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show on all displays\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"在所有显示器上显示\"\n          }\n        }\n      }\n    },\n    \"Show percentage\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show percentage\"\n          }\n        }\n      }\n    },\n    \"Show power status icons\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobrazit ikonu stavu napájení\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Symbole zum Ladestatus anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar iconos del adaptador de corriente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Afficher les icônes d'états de chargement\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra icona stato alimentazione\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"전원 상태 아이콘 보이기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż ikony stanu zasilania\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Güç durumu simgelerini göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status icons\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"显示电源状态图标\"\n          }\n        }\n      }\n    },\n    \"Show power status notifications\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobrazovat oznámení o stavu napájení\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Benachrichtigungen zum Ladestatus anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostrar notificaciones del adaptador de corriente\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Afficher les notifications d'état de chargement\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mostra notifiche alimentazione\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"전원 상태 알림 켜기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż powiadomienia o stanie zasilania\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Güç durumu bildirimlerini göster\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show power status notifications\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"显示电源状态通知\"\n          }\n        }\n      }\n    },\n    \"Show settings icon in notch\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Einstellungssymbol in Noch anzeigen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치에 설정 아이콘 보이기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż ikonę ustawień w notchu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show settings icon in notch\"\n          }\n        }\n      }\n    },\n    \"Show sneak peek on playback changes\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zeige Sneakpeek bei Wiedergabeänderungen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"재생 변경 시 훔쳐보기 표시\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pokaż podgląd przy zmianach odtwarzania\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Show sneak peek on playback changes\"\n          }\n        }\n      }\n    },\n    \"Slider color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Slider color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Barva posuvníku\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schieberegler-Farbe\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Slider color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Slider colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Color de la barra de progreso\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Couleur de la barre de défilement\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Slider color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Colore cursore\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"슬라이더 컬러\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kolor suwaka\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Slider color\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Slider color\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kaydırıcı rengi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Slider color\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"滑块颜色\"\n          }\n        }\n      }\n    },\n    \"Sneak Peek shows the media title and artist under the notch for a few seconds.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Náhled zobrazuje název skladby a autora pod výřezem na několik vteřin.\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek zeigt für ein paar Sekunden den Medientitel und den Künstler unter der Notch an.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vistazo Rápido muestra el título y nombre del artista debajo del notch por unos segundos.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek montre le titre du média et l'artiste sous l'encoche pendant quelques secondes.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek mostra per alcuni secondi il titolo dei media e l'artista sotto la notch.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"훔쳐보기가 몇 초 동안 미디어 제목과 아티스트명을 노치 아래에 보여줍니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Podgląd pokazuje tytuł multimediów i artystę pod notchem przez kilka sekund.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ön izleme, birkaç saniye boyunca çentik altında medya başlığını ve sanatçıyı gösterir.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek 在 Notch 下显示媒体标题和艺术家几秒钟。\"\n          }\n        }\n      }\n    },\n    \"Sneak Peek Style\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Styl náhledu\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek Stil\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Estilo del Vistazo Rápido\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Style du Sneak Peek\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stile Sneak Peek\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"훔쳐보기 스타일\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Styl podglądu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ön izleme stili\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek Style\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek 样式\"\n          }\n        }\n      }\n    },\n    \"Software updates\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aktualizace\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Softwareupdates\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Actualizaciones de software\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Mises à jour du logiciel\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aggiornamenti software\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"소프트웨어 업데이트\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Aktualizacje oprogramowania\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yazılım güncellemeleri\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Software updates\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"软件更新\"\n          }\n        }\n      }\n    },\n    \"Speed\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Rychlost\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Geschwindigkeit\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Velocidad\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vitesse\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Velocità\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"속도\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Szybkość\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hız\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Speed\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"速度\"\n          }\n        }\n      }\n    },\n    \"Square\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Čtverec\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quadratisch\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Cuadrado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Carré\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Quadrato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"사각형\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kwadrat\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kare\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Square\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"方形\"\n          }\n        }\n      }\n    },\n    \"Stopped\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zastaveno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gestoppt\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Detenido\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Arrêt\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Interrotto\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"중지 됨\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zatrzymano\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Durduruldu\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Stopped\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已停止\"\n          }\n        }\n      }\n    },\n    \"Support Us\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Podpořte nás\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unterstütze uns\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apóyenos\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Soutenez-nous\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Supportaci\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"후원하기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wesprzyj nas\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bizi Destekle\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Support Us\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"支持我们\"\n          }\n        }\n      }\n    },\n    \"System\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"System\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시스템\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System\"\n          }\n        }\n      }\n    },\n    \"System features\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Systémové funkce\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Systemfunktionen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Características del Sistema\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fonctionnalités système\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Funzionalità di sistema\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시스템 기능\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Funkcje systemowe\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sistem özellikleri\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System features\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"系统功能\"\n          }\n        }\n      }\n    },\n    \"Time to Full Charge: %lld min\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Čas do plného nabití: %lld min\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zeit bis zur vollen Ladung: %lld min\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tiempo para recarga completa: %lld min\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Temps avant la charge complète : %lld min\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tempo alla carica completa: %lld min\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"완충 까지 남은 시간: %lld 분\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Czas do pełnego naładowania: %lld min\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tam Şarj Süresi: %lld dakika\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Time to Full Charge: %lld min\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"充电时间：%lld 分钟\"\n          }\n        }\n      }\n    },\n    \"Tint progress bar with accent color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Leiste in Akzentfarbe färben\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"강조 색상으로 진행 바 색상 변경\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Przyciemnij pasek postępu przy użyciu koloru akcentu\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tint progress bar with accent color\"\n          }\n        }\n      }\n    },\n    \"Toggle Notch Open:\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zapnout otevření výřezu:\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Notch öffnen umschalten:\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar apertura del notch:\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer/désactiver le Notch Ouvert:\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apri Notch:\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치 열기 토글:\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz/wyłącz Notch:\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Çentik Aç/Kapat:\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Notch Open:\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"切换Notch：\"\n          }\n        }\n      }\n    },\n    \"Toggle Sneak Peek:\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zapnout náhled:\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sneak Peek umschalten:\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Habilitar Vistazo Rápido:\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Activer/désactiver Sneak Peek:\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Apri Sneak Peek:\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"훔쳐보기 토글:\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Włącz/wyłącz podgląd:\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ön izlemeyi Aç/Kapat:\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Toggle Sneak Peek:\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"触发 Sneak Peek：\"\n          }\n        }\n      }\n    },\n    \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Posunutím dvěma prsty nahoru zavřete, dvojitým posunem dolů otevřete, když je zakázána možnost **Otevření výřezu při nápovědě**\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zwei-Finger Wischen nach oben zum Schließen der Notch, zwei-Finger wischen nach unten um die Notch zu öffnen, wenn die Option **Notch bei Mausberührung öffnen** deaktiviert ist\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Deslice con dos dedos hacia arriba para cerrar, deslice con dos dedos hacia abajo para abrir cuando la opción de **Abrir el notch al pasar el cursor** esté deshabilitada\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Glisser deux doigts vers le haut sur l'encoche pour fermer, glisser deux doigts vers le bas sur l'encoche pour l'ouvrir lorsque l'option **Ouvrir l'encoche au survolant** est désactivée\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Scorri due dita verso l'alto sulla notch per chiudere, scorri due dita verso il basso sulla tacca per aprirla quando l'opzione **Apri notch all'hover ** è disabilitata\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"노치에 가져다 대서 열기 옵션이 꺼져있을 때, 노치를 두 손가락을 위로 밀어서 닫고, 두 손가락을 아래로 내려서 열기\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Przesuń dwoma palcami w górę na notchu, aby zamknąć, przesuń dwoma palcami w dół na notchu, aby otworzyć, gdy opcja **Otwórz notch przy najechaniu palcem ** jest wyłączona\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"**Fareyi üzerine getirdiğinde aç** seçeneği devre dışı bırakıldığında, kapatmak için çentiği iki parmağınızla yukarı kaydırın, açmak için çentiği iki parmağınızla aşağı kaydırın.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"在Notch里双指向上滑动以关闭。当**悬停在Notch时打开**选项禁用时，双指向下滑动即可打开\"\n          }\n        }\n      }\n    },\n    \"Uninstall\" : {\n      \"extractionState\" : \"stale\",\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odinstalovat\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Deinstallieren\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Desinstalar\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Désinstaller\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Disinstalla\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"설치제거\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odinstaluj\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Yüklemeyi Kaldır\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Uninstall\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"卸载\"\n          }\n        }\n      }\n    },\n    \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odemkněte pokročilé funkce a vylepšete si svůj zážitek. Upgradujte nyní pro více přizpůsobení!\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Schalte erweiterte Funktionen frei und verbessere deine Erfahrung. Upgrade jetzt für weitere Anpassungsmöglichkeiten!\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Desbloquee funciones avanzadas y mejore su experiencia. ¡Actualice ahora para más personalizaciones!\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Déverrouillez des fonctionnalités avancées et améliorez votre expérience. Mettez à niveau maintenant pour plus de personnalisations !\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Sblocca le funzionalità avanzate e migliora la tua esperienza. Aggiorna ora per ulteriori personalizzazioni!\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"고급 설정을 해제해 경험을 향상하세요. 더 많은 맞춤화 설정을 지금 바로 업그레이드해서 즐기세요!\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Odblokuj dodatkowe funkcje i ciesz się lepszymi możliwościami. Ulepsz teraz, aby móc bardziej dostosować ustawienia!\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Gelişmiş özelliklerin kilidini açın ve deneyiminizi iyileştirin. Daha fazla özelleştirme için şimdi yükseltin!\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unlock advanced features and improve your experience. Upgrade now for more customizations!\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"解锁高级功能并提升您的体验。现在升级以获取更多自定义功能！\"\n          }\n        }\n      }\n    },\n    \"unmuted\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"neztlumeno\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nicht stummgeschaltet\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"audio reactivado\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"sans sourdine\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"smutato\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음소거 해제됨\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wyciszenie wyłączone\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"sesi açılmış\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"unmuted\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"已取消静音\"\n          }\n        }\n      }\n    },\n    \"Unmuted\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Unmuted\"\n          }\n        }\n      }\n    },\n    \"Upgrade to Pro\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upgradovat na verzi Pro\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auf Pro upgraden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Actualice a Pro\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Passer à la version Pro\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Passa a Pro\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"프로로 업그레이드\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ulepsz do wersji Pro\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Pro sürümüne Yükselt\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Upgrade to Pro\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"升级至 Pro\"\n          }\n        }\n      }\n    },\n    \"Use music visualizer spectrogram\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use music visualizer spectrogram\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Použít hudební vizualizér\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Musik-Visualizer Spektrogramm verwenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Use music visualizer spectrogram\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Use music visualiser spectrogram\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Usar espectrograma del visualizador musical\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Utiliser le spectrogramme du visualiseur de musique\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use music visualizer spectrogram\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Usa spettrogramma per la visualizzatore musicale\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"음악 시각화 스펙트로그램 사용\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Użyj spektrogramu wizualizera muzyki\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use music visualizer spectrogram\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use music visualizer spectrogram\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Müzik görselleştirici spektrogram kullanın\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use music visualizer spectrogram\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"使用音乐可视化器谱图\"\n          }\n        }\n      }\n    },\n    \"Use your macOS system accent color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"macOS-System-Akzentfarbe verwenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"MacOS 시스템 강조 색상 사용\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Użyj koloru akcentu systemu macOS\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Use your macOS system accent color\"\n          }\n        }\n      }\n    },\n    \"Using System Accent\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"System Akzent verwenden\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"시스템 강조 색상 사용\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Korzystanie z akcentu systemowego\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Using System Accent\"\n          }\n        }\n      }\n    },\n    \"Version\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Verze\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Versión\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Versione\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"버전\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wersja\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Versiyon\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"版本\"\n          }\n        }\n      }\n    },\n    \"Version info\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informace o verzi\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Versionsinfo\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Información de la versión\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Infos de la version\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Info versione\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"버전 설명\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Informacje o wersji\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Versiyon bilgisi\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Version info\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"版本信息\"\n          }\n        }\n      }\n    },\n    \"View on GitHub: pear-devs/pear-desktop\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Auf GitHub: pear-devs/pear-desktop anschauen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"GitHub에서 보기: pear-devs/pear-desktop\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zobacz na GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"View on GitHub: pear-devs/pear-desktop\"\n          }\n        }\n      }\n    },\n    \"Welcome\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vítejte\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Willkommen\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bienvenido\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Bienvenue\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Benvenuto\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"환영합니다\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Witamy\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Hoş geldiniz\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Welcome\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"欢迎\"\n          }\n        }\n      }\n    },\n    \"What's New\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Co je nového\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Was ist neu\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Novedades\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Nouveautés\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Novità\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"새로운 기능\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Co nowego\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Neler Yeni\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"What's New\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"更新内容\"\n          }\n        }\n      }\n    },\n    \"Window Appearance\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fenster-Erscheinungsbild\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"창 모양\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wygląd okna\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Appearance\"\n          }\n        }\n      }\n    },\n    \"Window Behavior\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Fensterverhalten\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behaviour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"창 동작\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Zachowanie okna\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Window Behavior\"\n          }\n        }\n      }\n    },\n    \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Du kannst die App nun genießen. Wenn du Dinge weiter anpassen möchtest, kannst du dies jederzeit in den Einstellungen tun.\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ahora puede disfrutar de la aplicación. Si desea modificar otros detalles, siempre puede visitar los ajustes.\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vous pouvez maintenant profiter de l'application. Si vous voulez modifier davantage les choses, vous pouvez toujours consulter les paramètres.\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Ora puoi goderti l'app. Se vuoi modificare ulteriormente le cose, puoi sempre visitare le impostazioni.\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"이제 앱을 즐기실 수 있습니다. 추가로 설정을 조정하고 싶으시면 언제든지 설정 메뉴를 방문하시면 됩니다.\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Możesz już cieszyć się aplikacją. Jeśli chcesz wprowadzić dodatkowe zmiany, zajrzyj do ustawień.\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Artık uygulamanın keyfini çıkarabilirsiniz. Daha fazla ayar yapmak isterseniz, her zaman ayarlar bölümünü ziyaret edebilirsiniz.\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"如果您想进一步调整内容，您可以随时访问设置。\"\n          }\n        }\n      }\n    },\n    \"You're All Set!\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Vše je nastaveno!\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Du bist bereit!\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"¡Todo listo!\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tout est prêt!\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"È Tutto ok!\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"준비되었습니다!\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Wszystko gotowe!\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Tamamen Hazırsın!\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"You're All Set!\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"一切都准备好了！\"\n          }\n        }\n      }\n    },\n    \"Your macOS system accent color\" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Akzentfarbe Ihres macOS-Systems\"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent colour\"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"macOS 시스템 강조 색상\"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Kolor akcentu twojego systemu macOS\"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"Your macOS system accent color\"\n          }\n        }\n      }\n    },\n    \"YouTube Music requires this third-party app to be installed: \" : {\n      \"localizations\" : {\n        \"ar\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"cs\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music vyžaduje instalaci aplikace třetí strany: \"\n          }\n        },\n        \"de\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Musik benötigt diese Drittanbieter-App: \"\n          }\n        },\n        \"en\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"en-GB\" : {\n          \"stringUnit\" : {\n            \"state\" : \"translated\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"es\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requiere la instalación de esta aplicación independiente: \"\n          }\n        },\n        \"fr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requiert l'installation de cette application tierce : \"\n          }\n        },\n        \"hu\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"it\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music richiede l'installazione di questa app di terze parti: \"\n          }\n        },\n        \"ko\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"유튜브 뮤직 서드파티-앱 설치를 필요로 합니다 \"\n          }\n        },\n        \"pl\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music wymaga zainstalowania tej aplikacji firm trzecich: \"\n          }\n        },\n        \"pt-BR\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"ru\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"tr\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music, bu üçüncü taraf uygulamanın yüklenmesini gerektirir: \"\n          }\n        },\n        \"uk\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube Music requires this third-party app to be installed: \"\n          }\n        },\n        \"zh-Hans\" : {\n          \"stringUnit\" : {\n            \"state\" : \"needs_review\",\n            \"value\" : \"YouTube 音乐需要安装此第三方应用程序： \"\n          }\n        }\n      }\n    }\n  },\n  \"version\" : \"1.0\"\n}"
  },
  {
    "path": "boringNotch/MediaControllers/AppleMusicController.swift",
    "content": "//\n//  AppleMusicController.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-03-29.\n//\n\nimport Foundation\nimport Combine\nimport SwiftUI\n\nclass AppleMusicController: MediaControllerProtocol {\n    // MARK: - Properties\n    @Published private var playbackState: PlaybackState = PlaybackState(\n        bundleIdentifier: \"com.apple.Music\",\n        playbackRate: 1\n    )\n    \n    var playbackStatePublisher: AnyPublisher<PlaybackState, Never> {\n        $playbackState.eraseToAnyPublisher()\n    }\n\n    var supportsVolumeControl: Bool {\n        return true\n    }\n\n    var supportsFavorite: Bool {\n        return true\n    }\n\n    private var notificationTask: Task<Void, Never>?\n    \n    // MARK: - Initialization\n    init() {\n        setupPlaybackStateChangeObserver()\n        Task {\n            if isActive() {\n                await updatePlaybackInfo()\n            }\n        }\n    }\n    \n    private func setupPlaybackStateChangeObserver() {\n        notificationTask = Task { @Sendable [weak self] in\n            let notifications = DistributedNotificationCenter.default().notifications(\n                named: NSNotification.Name(\"com.apple.Music.playerInfo\")\n            )\n            \n            for await _ in notifications {\n                await self?.updatePlaybackInfo()\n            }\n        }\n    }\n    \n    deinit {\n        notificationTask?.cancel()\n    }\n    \n    // MARK: - Protocol Implementation\n    func play() async {\n        await executeCommand(\"play\")\n    }\n    \n    func pause() async {\n        await executeCommand(\"pause\")\n    }\n    \n    func togglePlay() async {\n        await executeCommand(\"playpause\")\n    }\n    \n    func nextTrack() async {\n        await executeCommand(\"next track\")\n    }\n    \n    func previousTrack() async {\n        await executeCommand(\"previous track\")\n    }\n    \n    func seek(to time: Double) async {\n        await executeCommand(\"set player position to \\(time)\")\n        await updatePlaybackInfo()\n    }\n    \n    func toggleShuffle() async {\n        await executeCommand(\"set shuffle enabled to not shuffle enabled\")\n        try? await Task.sleep(for: .milliseconds(150))\n        await updatePlaybackInfo()\n    }\n    \n    func toggleRepeat() async {\n        await executeCommand(\"\"\"\n            if song repeat is off then\n                set song repeat to all\n            else if song repeat is all then\n                set song repeat to one\n            else\n                set song repeat to off\n            end if\n            \"\"\")\n        try? await Task.sleep(for: .milliseconds(150))\n        await updatePlaybackInfo()\n    }\n    \n    func setVolume(_ level: Double) async {\n        let clampedLevel = max(0.0, min(1.0, level))\n        let volumePercentage = Int(clampedLevel * 100)\n        await executeCommand(\"set sound volume to \\(volumePercentage)\")\n        try? await Task.sleep(for: .milliseconds(150))\n        await updatePlaybackInfo()\n    }\n    \n    func isActive() -> Bool {\n        let runningApps = NSWorkspace.shared.runningApplications\n        return runningApps.contains { $0.bundleIdentifier == \"com.apple.Music\" }\n    }\n\n    func setFavorite(_ favorite: Bool) async {\n        let script = \"\"\"\n        tell application \\\"Music\\\"\n            try\n                set favorited of current track to \" + (favorite ? \"true\" : \"false\") + \"\n            end try\n        end tell\n        \"\"\"\n        try? await AppleScriptHelper.executeVoid(script)\n        try? await Task.sleep(for: .milliseconds(150))\n        await updatePlaybackInfo()\n    }\n    \n    func updatePlaybackInfo() async {\n        guard let descriptor = try? await fetchPlaybackInfoAsync() else { return }\n        guard descriptor.numberOfItems >= 11 else { return }\n        var updatedState = self.playbackState\n        \n        updatedState.isPlaying = descriptor.atIndex(1)?.booleanValue ?? false\n        updatedState.title = descriptor.atIndex(2)?.stringValue ?? \"Unknown\"\n        updatedState.artist = descriptor.atIndex(3)?.stringValue ?? \"Unknown\"\n        updatedState.album = descriptor.atIndex(4)?.stringValue ?? \"Unknown\"\n        updatedState.currentTime = descriptor.atIndex(5)?.doubleValue ?? 0\n        updatedState.duration = descriptor.atIndex(6)?.doubleValue ?? 0\n        updatedState.isShuffled = descriptor.atIndex(7)?.booleanValue ?? false\n        let repeatModeValue = descriptor.atIndex(8)?.int32Value ?? 0\n        updatedState.repeatMode = RepeatMode(rawValue: Int(repeatModeValue)) ?? .off\n        let volumePercentage = descriptor.atIndex(9)?.int32Value ?? 50\n        updatedState.volume = Double(volumePercentage) / 100.0\n        updatedState.artwork = descriptor.atIndex(10)?.data as Data?\n        let lovedState = descriptor.atIndex(11)?.booleanValue ?? false\n        updatedState.isFavorite = lovedState\n        updatedState.lastUpdated = Date()\n        self.playbackState = updatedState\n    }\n    \n    // MARK: - Private Methods\n    \n    private func executeCommand(_ command: String) async {\n        let script = \"tell application \\\"Music\\\" to \\(command)\"\n        try? await AppleScriptHelper.executeVoid(script)\n    }\n    \n    private func fetchPlaybackInfoAsync() async throws -> NSAppleEventDescriptor? {\n        let script = \"\"\"\n        tell application \"Music\"\n            set isRunning to true\n            try\n                set playerState to player state is playing\n                set currentTrackName to name of current track\n                set currentTrackArtist to artist of current track\n                set currentTrackAlbum to album of current track\n                set trackPosition to player position\n                set trackDuration to duration of current track\n                set shuffleState to shuffle enabled\n                set repeatState to song repeat\n                if repeatState is off then\n                    set repeatValue to 1\n                else if repeatState is one then\n                    set repeatValue to 2\n                else if repeatState is all then\n                    set repeatValue to 3\n                end if\n\n                try\n                    set artData to data of artwork 1 of current track\n                on error\n                    set artData to \"\"\n                end try\n                \n                set currentVolume to sound volume\n                set favoriteState to favorited of current track\n                return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatValue, currentVolume, artData, favoriteState}\n            on error\n                return {false, \"Not Playing\", \"Unknown\", \"Unknown\", 0, 0, false, 0, 50, \"\", false}\n            end try\n        end tell\n        \"\"\"\n        \n        return try await AppleScriptHelper.execute(script)\n    }\n    \n}\n"
  },
  {
    "path": "boringNotch/MediaControllers/MediaControllerProtocol.swift",
    "content": "//\n//  MediaControllerProtocol.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-03-29.\n//\n\nimport Foundation\nimport AppKit\nimport Combine\n\nprotocol MediaControllerProtocol: ObservableObject {\n    var playbackStatePublisher: AnyPublisher<PlaybackState, Never> { get }\n    var supportsVolumeControl: Bool { get }\n    var supportsFavorite: Bool { get }\n    \n    func setFavorite(_ favorite: Bool) async\n    func play() async\n    func pause() async\n    func seek(to time: Double) async\n    func nextTrack() async\n    func previousTrack() async\n    func togglePlay() async\n    func toggleShuffle() async\n    func toggleRepeat() async\n    func setVolume(_ level: Double) async\n    func isActive() -> Bool\n    func updatePlaybackInfo() async\n}\n"
  },
  {
    "path": "boringNotch/MediaControllers/NowPlayingController.swift",
    "content": "//\n//  NowPlayingController.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-03-29.\n//\n\nimport AppKit\nimport Combine\nimport Foundation\n\nfinal class NowPlayingController: ObservableObject, MediaControllerProtocol {\n    func updatePlaybackInfo() async {\n        await fetchFavoriteStateIfSupported()\n    }\n\n    // MARK: - Properties\n    @Published private(set) var playbackState: PlaybackState = .init(\n        bundleIdentifier: \"com.apple.Music\"\n    )\n\n    var playbackStatePublisher: AnyPublisher<PlaybackState, Never> {\n        $playbackState.eraseToAnyPublisher()\n    }\n\n    var supportsVolumeControl: Bool {\n        let bundleID = playbackState.bundleIdentifier\n        return bundleID == \"com.apple.Music\" || bundleID == \"com.spotify.client\"\n    }\n\n    var supportsFavorite: Bool {\n        let bundleID = playbackState.bundleIdentifier\n        return bundleID == \"com.apple.Music\"\n    }\n\n    func setFavorite(_ favorite: Bool) async {\n        let bundleID = playbackState.bundleIdentifier\n        \n        if bundleID == \"com.apple.Music\" {\n            let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.Music\")\n            if !runningApps.isEmpty {\n                let script = \"\"\"\n                tell application \"Music\"\n                    try\n                        set favorited of current track to \\(favorite ? \"true\" : \"false\")\n                    end try\n                end tell\n                \"\"\"\n                try? await AppleScriptHelper.executeVoid(script)\n            }\n        }\n        \n        // Update the favorite state locally and fetch updated info\n        try? await Task.sleep(for: .milliseconds(150))\n        await updatePlaybackInfo()\n    }\n\n    private var lastMusicItem:\n        (title: String, artist: String, album: String, duration: TimeInterval, artworkData: Data?)?\n\n    // MARK: - Media Remote Functions\n    private let mediaRemoteBundle: CFBundle\n    private let MRMediaRemoteSendCommandFunction: @convention(c) (Int, AnyObject?) -> Void\n    private let MRMediaRemoteSetElapsedTimeFunction: @convention(c) (Double) -> Void\n    private let MRMediaRemoteSetShuffleModeFunction: @convention(c) (Int) -> Void\n    private let MRMediaRemoteSetRepeatModeFunction: @convention(c) (Int) -> Void\n\n    private var process: Process?\n    private var pipeHandler: JSONLinesPipeHandler?\n    private var streamTask: Task<Void, Never>?\n\n    // MARK: - Initialization\n    init?() {\n        guard\n            let bundle = CFBundleCreate(\n                kCFAllocatorDefault,\n                NSURL(fileURLWithPath: \"/System/Library/PrivateFrameworks/MediaRemote.framework\")),\n            let MRMediaRemoteSendCommandPointer = CFBundleGetFunctionPointerForName(\n                bundle, \"MRMediaRemoteSendCommand\" as CFString),\n            let MRMediaRemoteSetElapsedTimePointer = CFBundleGetFunctionPointerForName(\n                bundle, \"MRMediaRemoteSetElapsedTime\" as CFString),\n            let MRMediaRemoteSetShuffleModePointer = CFBundleGetFunctionPointerForName(\n                bundle, \"MRMediaRemoteSetShuffleMode\" as CFString),\n            let MRMediaRemoteSetRepeatModePointer = CFBundleGetFunctionPointerForName(\n                bundle, \"MRMediaRemoteSetRepeatMode\" as CFString)\n            \n        else { return nil }\n\n        mediaRemoteBundle = bundle\n        MRMediaRemoteSendCommandFunction = unsafeBitCast(\n            MRMediaRemoteSendCommandPointer, to: (@convention(c) (Int, AnyObject?) -> Void).self)\n        MRMediaRemoteSetElapsedTimeFunction = unsafeBitCast(\n            MRMediaRemoteSetElapsedTimePointer, to: (@convention(c) (Double) -> Void).self)\n        MRMediaRemoteSetShuffleModeFunction = unsafeBitCast(\n            MRMediaRemoteSetShuffleModePointer, to: (@convention(c) (Int) -> Void).self)\n        MRMediaRemoteSetRepeatModeFunction = unsafeBitCast(\n            MRMediaRemoteSetRepeatModePointer, to: (@convention(c) (Int) -> Void).self)\n\n        Task { await setupNowPlayingObserver() }\n    }\n\n    deinit {\n        streamTask?.cancel()\n        \n        if let pipeHandler = self.pipeHandler {\n            Task { await pipeHandler.close()\n            }\n        }\n        \n        if let process = self.process {\n            if process.isRunning {\n                process.terminate()\n                process.waitUntilExit()\n            }\n        }\n\n        self.process = nil\n        self.pipeHandler = nil\n    }\n\n    // MARK: - Protocol Implementation\n    func play() async {\n        MRMediaRemoteSendCommandFunction(0, nil)\n    }\n\n    func pause() async {\n        MRMediaRemoteSendCommandFunction(1, nil)\n    }\n\n    func togglePlay() async {\n        MRMediaRemoteSendCommandFunction(2, nil)\n    }\n\n    func nextTrack() async {\n        MRMediaRemoteSendCommandFunction(4, nil)\n    }\n\n    func previousTrack() async {\n        MRMediaRemoteSendCommandFunction(5, nil)\n    }\n\n    func seek(to time: Double) async {\n        MRMediaRemoteSetElapsedTimeFunction(time)\n    }\n\n    func isActive() -> Bool {\n        return true\n    }\n    \n    func toggleShuffle() async {\n        // MRMediaRemoteSendCommandFunction(6, nil)\n        MRMediaRemoteSetShuffleModeFunction(playbackState.isShuffled ? 1 : 3)\n        playbackState.isShuffled.toggle()\n    }\n    \n    func toggleRepeat() async {\n        // MRMediaRemoteSendCommandFunction(7, nil)\n        let newRepeatMode = (playbackState.repeatMode == .off) ? 3 : (playbackState.repeatMode.rawValue - 1)\n        playbackState.repeatMode = RepeatMode(rawValue: newRepeatMode) ?? .off\n        MRMediaRemoteSetRepeatModeFunction(newRepeatMode)\n    }\n    \n    func setVolume(_ level: Double) async {\n        // MediaRemote framework doesn't provide direct volume control for the active audio session\n        // As a workaround, try to control the currently active music app directly\n        let clampedLevel = max(0.0, min(1.0, level))\n        let volumePercentage = Int(clampedLevel * 100)\n        \n        let bundleID = playbackState.bundleIdentifier\n        if !bundleID.isEmpty {\n            if bundleID == \"com.apple.Music\" {\n                let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.Music\")\n                if !runningApps.isEmpty {\n                    let script = \"tell application \\\"Music\\\" to set sound volume to \\(volumePercentage)\"\n                    try? await AppleScriptHelper.executeVoid(script)\n                }\n            } else if bundleID == \"com.spotify.client\" {\n                let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.spotify.client\")\n                if !runningApps.isEmpty {\n                    let script = \"tell application \\\"Spotify\\\" to set sound volume to \\(volumePercentage)\"\n                    try? await AppleScriptHelper.executeVoid(script)\n                }\n            }\n        }\n        \n        playbackState.volume = clampedLevel\n    }\n    \n    // MARK: - Setup Methods\n    private func setupNowPlayingObserver() async {\n        let process = Process()\n        guard\n            let scriptURL = Bundle.main.url(forResource: \"mediaremote-adapter\", withExtension: \"pl\"),\n            let frameworkPath = Bundle.main.privateFrameworksPath?.appending(\"/MediaRemoteAdapter.framework\")\n        else {\n            assertionFailure(\"Could not find mediaremote-adapter.pl script or framework path\")\n            return\n        }\n        \n        process.executableURL = URL(fileURLWithPath: \"/usr/bin/perl\")\n        process.arguments = [scriptURL.path, frameworkPath, \"stream\"]\n        \n        let pipeHandler = JSONLinesPipeHandler()\n        process.standardOutput = await pipeHandler.getPipe()\n        \n        self.process = process\n        self.pipeHandler = pipeHandler\n\n        do {\n            try process.run()\n            streamTask = Task { [weak self] in\n                await self?.processJSONStream()\n            }\n        } catch {\n            assertionFailure(\"Failed to launch mediaremote-adapter.pl: \\(error)\")\n        }\n    }\n\n    // MARK: - Async Stream Processing\n    private func processJSONStream() async {\n        guard let pipeHandler = self.pipeHandler else { return }\n        \n        await pipeHandler.readJSONLines(as: NowPlayingUpdate.self) { [weak self] update in\n            await self?.handleAdapterUpdate(update)\n        }\n    }\n\n    // MARK: - Update Methods\n    private func handleAdapterUpdate(_ update: NowPlayingUpdate) async {\n        let payload = update.payload\n        let diff = update.diff ?? false\n\n        var newPlaybackState = PlaybackState(bundleIdentifier: playbackState.bundleIdentifier)\n        \n        newPlaybackState.title = payload.title ?? (diff ? self.playbackState.title : \"\")\n        newPlaybackState.artist = payload.artist ?? (diff ? self.playbackState.artist : \"\")\n        newPlaybackState.album = payload.album ?? (diff ? self.playbackState.album : \"\")\n        newPlaybackState.duration = payload.duration ?? (diff ? self.playbackState.duration : 0)\n        \n        if let elapsedTime = payload.elapsedTime {\n            newPlaybackState.currentTime = elapsedTime\n        } else if diff {\n            if payload.playing == false {\n                let timeSinceLastUpdate = Date().timeIntervalSince(self.playbackState.lastUpdated)\n                newPlaybackState.currentTime = self.playbackState.currentTime + (self.playbackState.playbackRate * timeSinceLastUpdate)\n            } else {\n                newPlaybackState.currentTime = self.playbackState.currentTime\n            }\n        } else {\n            newPlaybackState.currentTime = 0\n        }\n\n        \n        if let shuffleMode = payload.shuffleMode {\n            newPlaybackState.isShuffled = shuffleMode != 1\n        } else if !diff {\n            newPlaybackState.isShuffled = false\n        } else {\n            newPlaybackState.isShuffled = self.playbackState.isShuffled\n        }\n        if let repeatModeValue = payload.repeatMode {\n            newPlaybackState.repeatMode = RepeatMode(rawValue: repeatModeValue) ?? .off\n        } else if !diff {\n            newPlaybackState.repeatMode = .off\n        } else {\n            newPlaybackState.repeatMode = self.playbackState.repeatMode\n        }\n\n        if let artworkDataString = payload.artworkData {\n            newPlaybackState.artwork = Data(\n                base64Encoded: artworkDataString.trimmingCharacters(in: .whitespacesAndNewlines)\n            )\n        } else if !diff {\n            newPlaybackState.artwork = nil\n        }\n\n        if let dateString = payload.timestamp,\n           let date = ISO8601DateFormatter().date(from: dateString) {\n            newPlaybackState.lastUpdated = date\n        } else if !diff {\n            newPlaybackState.lastUpdated = Date()\n        } else {\n            newPlaybackState.lastUpdated = self.playbackState.lastUpdated\n        }\n\n        newPlaybackState.playbackRate = payload.playbackRate ?? (diff ? self.playbackState.playbackRate : 1.0)\n        newPlaybackState.isPlaying = payload.playing ?? (diff ? self.playbackState.isPlaying : false)\n        newPlaybackState.bundleIdentifier = (\n            payload.parentApplicationBundleIdentifier ??\n            payload.bundleIdentifier ??\n            (diff ? self.playbackState.bundleIdentifier : \"\")\n        )\n        \n        newPlaybackState.volume = payload.volume ?? (diff ? self.playbackState.volume : 0.5)\n        \n        self.playbackState = newPlaybackState\n        \n        // Fetch favorite state for supported apps asynchronously\n        // await fetchFavoriteStateIfSupported()\n    }\n    \n     private func fetchFavoriteStateIfSupported() async {\n         let bundleID = playbackState.bundleIdentifier\n        \n         if bundleID == \"com.apple.Music\" {\n             let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.Music\")\n             guard !runningApps.isEmpty else { return }\n             \n             let script = \"\"\"\n             tell application \"Music\"\n                 try\n                     return favorited of current track\n                 on error\n                     return false\n                 end try\n             end tell\n             \"\"\"\n             if let result = try? await AppleScriptHelper.execute(script) {\n                 var updated = self.playbackState\n                 updated.isFavorite = result.booleanValue\n                 self.playbackState = updated\n             }\n         }\n     }\n    \n}\n\nstruct NowPlayingUpdate: Codable {\n    let payload: NowPlayingPayload\n    let diff: Bool?\n}\n\nstruct NowPlayingPayload: Codable {\n    let title: String?\n    let artist: String?\n    let album: String?\n    let duration: Double?\n    let elapsedTime: Double?\n    let shuffleMode: Int?\n    let repeatMode: Int?\n    let artworkData: String?\n    let timestamp: String?\n    let playbackRate: Double?\n    let playing: Bool?\n    let parentApplicationBundleIdentifier: String?\n    let bundleIdentifier: String?\n    let volume: Double?\n}\n\nactor JSONLinesPipeHandler {\n    private let pipe: Pipe\n    private let fileHandle: FileHandle\n    private var buffer = \"\"\n    \n    init() {\n        self.pipe = Pipe()\n        self.fileHandle = pipe.fileHandleForReading\n    }\n    \n    func getPipe() -> Pipe {\n        return pipe\n    }\n    \n    func readJSONLines<T: Decodable>(as type: T.Type, onLine: @escaping (T) async -> Void) async {\n        do {\n            try await self.processLines(as: type) { decodedObject in\n                await onLine(decodedObject)\n            }\n        } catch {\n            print(\"Error processing JSON stream: \\(error)\")\n        }\n    }\n    \n    private func processLines<T: Decodable>(as type: T.Type, onLine: @escaping (T) async -> Void) async throws {\n        while true {\n            let data = try await readData()\n            guard !data.isEmpty else { break }\n            \n            if let chunk = String(data: data, encoding: .utf8) {\n                buffer.append(chunk)\n                \n                while let range = buffer.range(of: \"\\n\") {\n                    let line = String(buffer[..<range.lowerBound])\n                    buffer = String(buffer[range.upperBound...])\n                    \n                    if !line.isEmpty {\n                        await processJSONLine(line, as: type, onLine: onLine)\n                    }\n                }\n            }\n        }\n    }\n    \n    private func processJSONLine<T: Decodable>(_ line: String, as type: T.Type, onLine: @escaping (T) async -> Void) async {\n        guard let data = line.data(using: .utf8) else {\n            return\n        }\n        do {\n            let decodedObject = try JSONDecoder().decode(T.self, from: data)\n            await onLine(decodedObject)\n        } catch {\n            // Ignore lines that can't be decoded\n        }\n    }\n    \n    private func readData() async throws -> Data {\n        return try await withCheckedThrowingContinuation { continuation in\n            \n            fileHandle.readabilityHandler = { handle in\n                let data = handle.availableData\n                handle.readabilityHandler = nil\n                continuation.resume(returning: data)\n            }\n        }\n    }\n    \n    func close() async {\n        do {\n            fileHandle.readabilityHandler = nil\n            try fileHandle.close()\n            try pipe.fileHandleForWriting.close()\n        } catch {\n            print(\"Error closing pipe handler: \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/MediaControllers/SpotifyController.swift",
    "content": "//\n//  SpotifyController.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-03-29.\n//\n\nimport Foundation\nimport Combine\nimport SwiftUI\n\nclass SpotifyController: MediaControllerProtocol {\n    func setFavorite(_ favorite: Bool) async {\n        //Placeholder\n    }\n    \n    // MARK: - Properties\n    @Published private var playbackState: PlaybackState = PlaybackState(\n        bundleIdentifier: \"com.spotify.client\"\n    )\n    \n    var playbackStatePublisher: AnyPublisher<PlaybackState, Never> {\n        $playbackState.eraseToAnyPublisher()\n    }\n\n    var supportsVolumeControl: Bool {\n        return true\n    }\n\n    var supportsFavorite: Bool { false }\n\n    private var notificationTask: Task<Void, Never>?\n    \n    // Constant for time between command and update\n    private let commandUpdateDelay: Duration = .milliseconds(25)\n\n    private var lastArtworkURL: String?\n    private var artworkFetchTask: Task<Void, Never>?\n    \n    init() {\n        setupPlaybackStateChangeObserver()\n        Task {\n            if isActive() {\n                await updatePlaybackInfo()\n            }\n        }\n    }\n    \n    private func setupPlaybackStateChangeObserver() {\n        notificationTask = Task { @Sendable [weak self] in\n            let notifications = DistributedNotificationCenter.default().notifications(\n                named: NSNotification.Name(\"com.spotify.client.PlaybackStateChanged\")\n            )\n            \n            for await _ in notifications {\n                await self?.updatePlaybackInfo()\n            }\n        }\n    }\n    \n    deinit {\n        notificationTask?.cancel()\n        artworkFetchTask?.cancel()\n    }\n    \n    // MARK: - Protocol Implementation\n    func play() async { await executeCommand(\"play\") }\n    func pause() async { await executeCommand(\"pause\") }\n    func togglePlay() async { await executeCommand(\"playpause\") }\n    func nextTrack() async { await executeCommand(\"next track\") }\n    func previousTrack() async {\n        await executeAndRefresh(\"previous track\")\n    }\n    \n    func seek(to time: Double) async {\n        await executeAndRefresh(\"set player position to \\(time)\")\n    }\n    \n    func toggleShuffle() async {\n        await executeAndRefresh(\"set shuffling to not shuffling\")\n    }\n    \n    func toggleRepeat() async {\n        await executeAndRefresh(\"set repeating to not repeating\")\n    }\n    \n    func setVolume(_ level: Double) async {\n        let clampedLevel = max(0.0, min(1.0, level))\n        let volumePercentage = Int(clampedLevel * 100)\n        await executeCommand(\"set sound volume to \\(volumePercentage)\")\n        try? await Task.sleep(for: commandUpdateDelay)\n        await updatePlaybackInfo()\n    }\n    \n    func isActive() -> Bool {\n        NSWorkspace.shared.runningApplications.contains { $0.bundleIdentifier == playbackState.bundleIdentifier }\n    }\n    \n    func updatePlaybackInfo() async {\n        guard let descriptor = try? await fetchPlaybackInfoAsync() else { return }\n        guard descriptor.numberOfItems >= 10 else { return }\n        \n        let isPlaying = descriptor.atIndex(1)?.booleanValue ?? false\n        let currentTrack = descriptor.atIndex(2)?.stringValue ?? \"Unknown\"\n        let currentTrackArtist = descriptor.atIndex(3)?.stringValue ?? \"Unknown\"\n        let currentTrackAlbum = descriptor.atIndex(4)?.stringValue ?? \"Unknown\"\n        let currentTime = descriptor.atIndex(5)?.doubleValue ?? 0\n        let duration = (descriptor.atIndex(6)?.doubleValue ?? 0)/1000\n        let isShuffled = descriptor.atIndex(7)?.booleanValue ?? false\n        let isRepeating = descriptor.atIndex(8)?.booleanValue ?? false\n        let volumePercentage = descriptor.atIndex(9)?.int32Value ?? 50\n        let artworkURL = descriptor.atIndex(10)?.stringValue ?? \"\"\n        \n        var state = PlaybackState(\n            bundleIdentifier: \"com.spotify.client\",\n            isPlaying: isPlaying,\n            title: currentTrack,\n            artist: currentTrackArtist,\n            album: currentTrackAlbum,\n            currentTime: currentTime,\n            duration: duration,\n            playbackRate: 1,\n            isShuffled: isShuffled,\n            repeatMode: isRepeating ? .all : .off,\n            lastUpdated: Date(),\n            artwork: nil,\n            volume: Double(volumePercentage) / 100.0\n        )\n\n        if artworkURL == lastArtworkURL, let existingArtwork = self.playbackState.artwork {\n            state.artwork = existingArtwork\n        }\n\n    playbackState = state\n\n        if !artworkURL.isEmpty, let url = URL(string: artworkURL) {\n            guard artworkURL != lastArtworkURL || state.artwork == nil else { return }\n            artworkFetchTask?.cancel()\n\n            let currentState = state\n\n            artworkFetchTask = Task {\n                do {\n                    let data = try await ImageService.shared.fetchImageData(from: url)\n\n                    await MainActor.run { [weak self] in\n                        guard let self = self else { return }\n                        var updatedState = currentState\n                        updatedState.artwork = data\n                        self.playbackState = updatedState\n                        self.lastArtworkURL = artworkURL\n                        self.artworkFetchTask = nil\n                    }\n                } catch {\n                    await MainActor.run { [weak self] in\n                        self?.artworkFetchTask = nil\n                    }\n                }\n            }\n        }\n    }\n    \n// MARK: - Private Methods\n    \n    private func executeCommand(_ command: String) async {\n        let script = \"tell application \\\"Spotify\\\" to \\(command)\"\n        try? await AppleScriptHelper.executeVoid(script)\n    }\n\n    private func executeAndRefresh(_ command: String) async {\n        await executeCommand(command)\n        try? await Task.sleep(for: commandUpdateDelay)\n        await updatePlaybackInfo()\n    }\n    \n    private func fetchPlaybackInfoAsync() async throws -> NSAppleEventDescriptor? {\n        let script = \"\"\"\n        tell application \"Spotify\"\n            set isRunning to true\n            try\n                set playerState to player state is playing\n                set currentTrackName to name of current track\n                set currentTrackArtist to artist of current track\n                set currentTrackAlbum to album of current track\n                set trackPosition to player position\n                set trackDuration to duration of current track\n                set shuffleState to shuffling\n                set repeatState to repeating\n                set currentVolume to sound volume\n                set artworkURL to artwork url of current track\n                return {playerState, currentTrackName, currentTrackArtist, currentTrackAlbum, trackPosition, trackDuration, shuffleState, repeatState, currentVolume, artworkURL}\n            on error\n                return {false, \"Unknown\", \"Unknown\", \"Unknown\", 0, 0, false, false, 50, \"\"}\n            end try\n        end tell\n        \"\"\"\n        \n        return try await AppleScriptHelper.execute(script)\n    }\n    \n}\n"
  },
  {
    "path": "boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicAuthentication.swift",
    "content": "//\n//  YouTubeMusicAuthentication.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-14.\n//\n\nimport Foundation\n\n// MARK: - Authentication Manager\nactor YouTubeMusicAuthManager {\n    private var accessToken: String?\n    private var authenticationTask: Task<String, Error>?\n    private let httpClient: YouTubeMusicHTTPClient\n    \n    init(httpClient: YouTubeMusicHTTPClient) {\n        self.httpClient = httpClient\n    }\n    \n    var currentToken: String? {\n        accessToken\n    }\n    \n    func authenticate() async throws -> String {\n        // Return existing token if valid\n        if let token = accessToken {\n            return token\n        }\n        \n        // Wait for ongoing authentication if in progress\n        if let task = authenticationTask {\n            return try await task.value\n        }\n        \n        // Start new authentication\n        let task = Task<String, Error> {\n            do {\n                let token = try await httpClient.authenticate()\n                await setToken(token)\n                return token\n            } catch {\n                await clearAuthenticationTask()\n                throw error\n            }\n        }\n        \n        authenticationTask = task\n        return try await task.value\n    }\n    \n    func invalidateToken() async {\n        accessToken = nil\n        authenticationTask?.cancel()\n        authenticationTask = nil\n    }\n    \n    private func setToken(_ token: String) async {\n        accessToken = token\n        authenticationTask = nil\n    }\n    \n    private func clearAuthenticationTask() async {\n        authenticationTask = nil\n    }\n}\n\n// MARK: - Authentication State\nenum AuthenticationState: Sendable {\n    case unauthenticated\n    case authenticating\n    case authenticated(String)\n    case failed(Error)\n    \n    var isAuthenticated: Bool {\n        if case .authenticated = self {\n            return true\n        }\n        return false\n    }\n    \n    var token: String? {\n        if case .authenticated(let token) = self {\n            return token\n        }\n        return nil\n    }\n}"
  },
  {
    "path": "boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicController.swift",
    "content": "//\n//  YouTubeMusicController.swift\n//  boringNotch\n//\n//  Created By Alexander on 2025-03-30.\n//  Modified by Pranav on 2025-06-16.\n//\n\nimport Foundation\nimport Combine\nimport SwiftUI\n\nfinal class YouTubeMusicController: MediaControllerProtocol {\n    // MARK: - Published Properties\n    @Published var playbackState = PlaybackState(\n        bundleIdentifier: YouTubeMusicConfiguration.default.bundleIdentifier\n    )\n\n    private var artworkFetchTask: Task<Void, Never>?\n    \n    var playbackStatePublisher: AnyPublisher<PlaybackState, Never> {\n        $playbackState.eraseToAnyPublisher()\n    }\n\n    var supportsVolumeControl: Bool {\n        return true\n    }\n\n    var supportsFavorite: Bool { true }\n\n    func setFavorite(_ favorite: Bool) async {\n        do {\n            let token = try await authManager.authenticate()\n            if favorite && !playbackState.isFavorite {\n                _ = try await httpClient.toggleLike(token: token)\n            } else if !favorite && playbackState.isFavorite {\n                _ = try await httpClient.toggleLike(token: token)\n            }\n            try? await Task.sleep(for: .milliseconds(150))\n            await updatePlaybackInfo()\n        } catch {\n            print(\"[YouTubeMusicController] Failed to set favorite: \\(error)\")\n        }\n    }\n\n    // MARK: - Private Properties\n    private let configuration: YouTubeMusicConfiguration\n    private let httpClient: YouTubeMusicHTTPClient\n    private let authManager: YouTubeMusicAuthManager\n    private var webSocketClient: YouTubeMusicWebSocketClient?\n    \n    private var updateTimer: Timer?\n    private var appStateObserver: Task<Void, Never>?\n    private var reconnectDelay: TimeInterval = 1.0\n    \n    // MARK: - Initialization\n    init(configuration: YouTubeMusicConfiguration = .default) {\n        self.configuration = configuration\n        self.httpClient = YouTubeMusicHTTPClient(baseURL: configuration.baseURL)\n        self.authManager = YouTubeMusicAuthManager(httpClient: httpClient)\n        \n        setupAppStateObserver()\n        \n        Task {\n            await initializeIfAppActive()\n        }\n    }\n    \n    // MARK: - MediaControllerProtocol Implementation\n    func play() async { await sendCommand(endpoint: \"/play\", method: \"POST\") }\n    \n    func pause() async { await sendCommand(endpoint: \"/pause\", method: \"POST\") }\n    \n    func togglePlay() async {\n        if !isActive() { launchApp() }\n        await sendCommand(endpoint: \"/toggle-play\", method: \"POST\")\n    }\n    \n    func nextTrack() async { await sendCommand(endpoint: \"/next\", method: \"POST\") }\n\n    func previousTrack() async { await sendCommand(endpoint: \"/previous\", method: \"POST\") }\n    \n    func seek(to time: Double) async {\n        let payload = [\"seconds\": time]\n        await sendCommand(endpoint: \"/seek-to\", method: \"POST\", body: payload)\n    }\n\n    func setVolume(_ level: Double) async {\n        let clampedLevel = max(0.0, min(1.0, level))\n        let volumePercentage = Int(clampedLevel * 100)\n        let payload = [\"volume\": volumePercentage]\n        await sendCommand(endpoint: \"/volume\", method: \"POST\", body: payload)\n    }\n    func fetchShuffleState() async { await sendCommand(endpoint: \"/shuffle\", method: \"GET\", refresh: false) }\n    func fetchRepeatMode() async { await sendCommand(endpoint: \"/repeat-mode\", method: \"GET\", refresh: false) }\n    \n    func toggleShuffle() async { await sendCommand(endpoint: \"/shuffle\", method: \"POST\") }\n    func toggleRepeat() async { await sendCommand(endpoint: \"/switch-repeat\", method: \"POST\") }\n\n    nonisolated func isActive() -> Bool {\n        NSWorkspace.shared.runningApplications.contains {\n            $0.bundleIdentifier == configuration.bundleIdentifier\n        }\n    }\n    \n    func updatePlaybackInfo() async {\n        guard isActive() else {\n            resetPlaybackState()\n            return\n        }\n        \n        do {\n            let token = try await authManager.authenticate()\n            let response = try await httpClient.getPlaybackInfo(token: token)\n            await updatePlaybackState(with: response)\n            // Fetch like state if supported\n            do {\n                let likeResp = try await httpClient.getLikeState(token: token)\n                var newState = playbackState\n                    if let state = likeResp.state {\n                        switch state.uppercased() {\n                        case \"LIKE\":\n                            newState.isFavorite = true\n                        case \"DISLIKE\":\n                            // We don't have a separate dislike UI yet, treat as not favorited\n                            newState.isFavorite = false\n                        default:\n                            newState.isFavorite = false\n                        }\n                    } else {\n                        newState.isFavorite = false\n                    }\n                playbackState = newState\n            } catch {\n                // Don't treat it as an error if the like endpoint doesn't exist — just skip\n            }\n        } catch YouTubeMusicError.authenticationRequired {\n            await authManager.invalidateToken()\n        } catch {\n            print(\"[YouTubeMusicController] Failed to update playback info: \\(error)\")\n        }\n    }\n    \n    // MARK: - Private Methods\n    private func setupAppStateObserver() {\n        appStateObserver = Task { [weak self] in\n            await withTaskGroup(of: Void.self) { group in\n                group.addTask {\n                    let launchNotifications = NSWorkspace.shared.notificationCenter.notifications(\n                        named: NSWorkspace.didLaunchApplicationNotification\n                    )\n                    \n                    for await notification in launchNotifications {\n                        await self?.handleAppLaunched(notification)\n                    }\n                }\n                \n                group.addTask {\n                    let terminateNotifications = NSWorkspace.shared.notificationCenter.notifications(\n                        named: NSWorkspace.didTerminateApplicationNotification\n                    )\n                    \n                    for await notification in terminateNotifications {\n                        await self?.handleAppTerminated(notification)\n                    }\n                }\n            }\n        }\n    }\n    \n    private func handleAppLaunched(_ notification: Notification) async {\n        guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,\n              app.bundleIdentifier == configuration.bundleIdentifier else {\n            return\n        }\n        \n        await initializeIfAppActive()\n    }\n    \n    private func handleAppTerminated(_ notification: Notification) async {\n        guard let app = notification.userInfo?[NSWorkspace.applicationUserInfoKey] as? NSRunningApplication,\n              app.bundleIdentifier == configuration.bundleIdentifier else {\n            return\n        }\n        \n        Task { @MainActor in\n            stopPeriodicUpdates()\n            appStateObserver?.cancel()\n        }\n        \n        Task {\n            await webSocketClient?.disconnect()\n            webSocketClient = nil\n        }\n        \n        resetPlaybackState()\n    }\n    \n    private func initializeIfAppActive() async {\n        guard isActive() else { return }\n        \n        do {\n            let token = try await authManager.authenticate()\n            await setupWebSocketIfPossible(token: token)\n            await startPeriodicUpdates()\n            await updatePlaybackInfo()\n        } catch {\n            print(\"[YouTubeMusicController] Failed to initialize: \\(error)\")\n            await scheduleReconnect()\n        }\n    }\n    \n    private func setupWebSocketIfPossible(token: String) async {\n        guard let wsURL = WebSocketURLBuilder.buildURL(from: configuration.baseURL) else {\n            print(\"[YouTubeMusicController] Failed to build WebSocket URL\")\n            return\n        }\n        \n        let client = YouTubeMusicWebSocketClient(\n            onMessage: { [weak self] data in\n                await self?.handleWebSocketMessage(data)\n            },\n            onDisconnect: { [weak self] in\n                await self?.handleWebSocketDisconnect()\n            }\n        )\n        \n        do {\n            try await client.connect(to: wsURL, with: token)\n            webSocketClient = client\n            stopPeriodicUpdates() // WebSocket will provide real-time updates\n            reconnectDelay = configuration.reconnectDelay.lowerBound\n        } catch {\n            print(\"[YouTubeMusicController] WebSocket connection failed: \\(error)\")\n            await scheduleReconnect()\n        }\n    }\n    \n    private func handleWebSocketMessage(_ data: Data) async {\n        guard let message = WebSocketMessage(from: data) else {\n            if let response = try? JSONDecoder().decode(PlaybackResponse.self, from: data) {\n                await updatePlaybackState(with: response)\n            }\n            return\n        }\n        switch message.type {\n        case .playerInfo, .videoChanged, .playerStateChanged:\n            if let data = message.extractData(),\n               let response = PlaybackResponse.from(websocketData: data) {\n                await updatePlaybackState(with: response)\n            }\n\n        case .positionChanged:\n            guard let data = message.extractData() else { return }\n\n            var position: Double? = nil\n            if let pos = data[\"position\"] as? Double {\n                position = pos\n            } else if let elapsed = data[\"elapsedSeconds\"] as? Double {\n                position = elapsed\n            }\n            guard let newPosition = position else { return }\n\n            var copied = playbackState\n            copied.currentTime = newPosition\n            copied.lastUpdated = Date()\n            if copied != playbackState { playbackState = copied }\n\n        case .repeatChanged:\n            guard let data = message.extractData() else { return }\n            var copy = playbackState\n\n            if let repeatStr = data[\"repeat\"] as? String {\n                switch repeatStr.uppercased() {\n                case \"NONE\": copy.repeatMode = .off\n                case \"ALL\": copy.repeatMode = .all\n                case \"ONE\": copy.repeatMode = .one\n                default: break\n                }\n            }\n            copy.lastUpdated = Date()\n            if copy != playbackState { playbackState = copy }\n\n        case .shuffleChanged:\n            guard let data = message.extractData() else { return }\n            var copy = playbackState\n            if let shuffle = data[\"shuffle\"] as? Bool { copy.isShuffled = shuffle }\n            else if let shuffle = data[\"isShuffled\"] as? Bool { copy.isShuffled = shuffle }\n            copy.lastUpdated = Date()\n            if copy != playbackState { playbackState = copy }\n\n        case .volumeChanged:\n            guard let data = message.extractData() else { return }\n            var copy = playbackState\n            if let volume = data[\"volume\"] as? Double {\n                copy.volume = volume / 100.0\n            } else if let volume = data[\"volume\"] as? Int {\n                copy.volume = Double(volume) / 100.0\n            }\n            copy.lastUpdated = Date()\n            if copy != playbackState { playbackState = copy }\n        }\n    }\n    \n    private func handleWebSocketDisconnect() async {\n        webSocketClient = nil\n        await startPeriodicUpdates() // Fallback to polling\n        await scheduleReconnect()\n    }\n    \n    private func scheduleReconnect() async {\n        try? await Task.sleep(for: .seconds(reconnectDelay))\n        reconnectDelay = min(reconnectDelay * 2, configuration.reconnectDelay.upperBound)\n        \n        if isActive() {\n            await initializeIfAppActive()\n        }\n    }\n    \n    private func startPeriodicUpdates() async {\n        guard isActive() && webSocketClient == nil else { return }\n        \n        stopPeriodicUpdates()\n        \n        updateTimer = Timer.scheduledTimer(withTimeInterval: configuration.updateInterval, repeats: true) { [weak self] _ in\n            Task { @MainActor in\n                await self?.updatePlaybackInfo()\n            }\n        }\n    }\n    \n    private func stopPeriodicUpdates() {\n        updateTimer?.invalidate()\n        updateTimer = nil\n    }\n\n    func pollPlaybackState() async {\n        if !isActive() {\n            return\n        }\n        \n        await fetchRepeatMode()\n        await fetchShuffleState()\n        await updatePlaybackInfo()\n    }\n    \n    private func sendCommand(\n        endpoint: String,\n        method: String = \"POST\",\n        body: (any Codable & Sendable)? = nil,\n        refresh: Bool = true\n    ) async {\n        do {\n            let token = try await authManager.authenticate()\n            \n            let data = try await httpClient.sendCommand(\n                endpoint: endpoint,\n                method: method,\n                body: body,\n                token: token\n            )\n            // Lightweight endpoint-specific parsing\n            if endpoint == \"/shuffle\" {\n                if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let shuffleState = json[\"state\"] as? Bool {\n                    playbackState.isShuffled = shuffleState\n                } else {\n                    playbackState.isShuffled = !playbackState.isShuffled\n                }\n            } else if endpoint == \"/repeat-mode\" {\n                if let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {\n                    if let mode = json[\"mode\"] as? String { updateRepeatMode(mode) }\n                }\n            }  else if endpoint == \"/switch-repeat\" {\n                // Find next repeat mode\n                let nextMode: RepeatMode\n                switch playbackState.repeatMode {\n                case .off: nextMode = .all\n                case .all: nextMode = .one\n                case .one: nextMode = .off\n                }\n                playbackState.repeatMode = nextMode\n            } else if refresh && webSocketClient == nil {\n                try? await Task.sleep(for: .milliseconds(100))\n                await updatePlaybackInfo()\n            }\n        } catch YouTubeMusicError.authenticationRequired {\n            await authManager.invalidateToken()\n        } catch {\n            print(\"[YouTubeMusicController] Command failed: \\(error)\")\n        }\n    }\n    \n    private func updatePlaybackState(with response: PlaybackResponse) async {\n        var newState = playbackState\n        \n        newState.isPlaying = !response.isPaused\n\n        if let title = response.title {\n            newState.title = title\n        }\n\n        if let artist = response.artist {\n            newState.artist = artist\n        }\n\n        if let album = response.album {\n            newState.album = album\n        }\n\n        if let elapsed = response.elapsedSeconds {\n            newState.currentTime = elapsed\n        }\n\n        if let duration = response.songDuration {\n            newState.duration = duration\n        }\n\n        newState.lastUpdated = Date()\n        \n        if let shuffled = response.isShuffled {\n            newState.isShuffled = shuffled\n        }\n        \n        if let mode = response.repeatMode {\n            switch mode {\n            case 0: newState.repeatMode = .off\n            case 1: newState.repeatMode = .all\n            case 2: newState.repeatMode = .one\n            default: break\n            }\n        }\n\n        if let volume = response.volume {\n            newState.volume = volume / 100.0\n        }\n\n        if newState != playbackState {\n            playbackState = newState\n\n            artworkFetchTask?.cancel()\n            artworkFetchTask = nil\n\n            if let artworkURL = response.imageSrc,\n               let url = URL(string: artworkURL) {\n                artworkFetchTask = Task {\n                    do {\n                        let data = try await ImageService.shared.fetchImageData(from: url)\n                        await MainActor.run { [weak self] in\n                            self?.playbackState.artwork = data\n\n                        }\n                    } catch { /* ignore */ }\n                }\n            }\n        }\n    }\n    \n    private func resetPlaybackState() {\n        playbackState = PlaybackState(\n            bundleIdentifier: configuration.bundleIdentifier,\n            isPlaying: false\n        )\n    }\n    \n    private func launchApp() {\n        guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: configuration.bundleIdentifier) else {\n            return\n        }\n        NSWorkspace.shared.open(url)\n    }\n\n     private func updateRepeatMode(_ mode: String) {\n        var target: RepeatMode? = nil\n        switch mode {\n            case \"NONE\": target = .off\n            case \"ALL\": target = .all\n            case \"ONE\": target = .one\n            default: break\n        }\n        if let target, target != playbackState.repeatMode { playbackState.repeatMode = target }\n    }\n    \n}\n"
  },
  {
    "path": "boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicModels.swift",
    "content": "//\n//  YouTubeMusicModels.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-14.\n//\n\nimport Foundation\n\n// MARK: - Configuration\nstruct YouTubeMusicConfiguration: Sendable {\n    let baseURL: String\n    let bundleIdentifier: String\n    let reconnectDelay: ClosedRange<TimeInterval>\n    let updateInterval: TimeInterval\n    \n    static let `default` = YouTubeMusicConfiguration(\n        baseURL: \"http://localhost:26538\",\n        bundleIdentifier: \"com.github.th-ch.youtube-music\",\n        reconnectDelay: 1...60,\n        updateInterval: 2.0\n    )\n}\n\n// MARK: - API Models\nstruct AuthResponse: Decodable, Sendable {\n    let accessToken: String\n}\n\nstruct PlaybackResponse: Decodable, Sendable {\n    let isPaused: Bool\n    let title: String?\n    let artist: String?\n    let album: String?\n    let elapsedSeconds: Double?\n    let songDuration: Double?\n    let imageSrc: String?\n    let repeatMode: Int?\n    let isShuffled: Bool?\n    let volume: Double?\n}\n\n// MARK: - WebSocket Message Types\nenum WebSocketMessageType: String, Sendable {\n    case playerInfo = \"PLAYER_INFO\"\n    case videoChanged = \"VIDEO_CHANGED\"\n    case playerStateChanged = \"PLAYER_STATE_CHANGED\"\n    case positionChanged = \"POSITION_CHANGED\"\n    case volumeChanged = \"VOLUME_CHANGED\"\n    case repeatChanged = \"REPEAT_CHANGED\"\n    case shuffleChanged = \"SHUFFLE_CHANGED\"\n}\n\nstruct WebSocketMessage {\n    let type: WebSocketMessageType\n    let rawData: Data\n    private let parsedJSON: [String: Any]?\n\n    init?(from data: Data) {\n        let json = (try? JSONSerialization.jsonObject(with: data) as? [String: Any])\n        guard let typeString = json?[\"type\"] as? String,\n              let messageType = WebSocketMessageType(rawValue: typeString) else {\n            return nil\n        }\n\n        self.type = messageType\n        self.rawData = data\n        self.parsedJSON = json\n    }\n\n    func extractData() -> [String: Any]? {\n        parsedJSON\n    }\n}\n\n// MARK: - Extensions\nextension PlaybackResponse {\n    static func from(websocketData: [String: Any]) -> PlaybackResponse? {\n        let songData = websocketData[\"song\"] as? [String: Any]\n        \n        let isPaused: Bool\n        if let paused = songData?[\"isPaused\"] as? Bool {\n            isPaused = paused\n        } else if let playing = websocketData[\"isPlaying\"] as? Bool {\n            isPaused = !playing\n        } else {\n            isPaused = true\n        }\n        \n        let title = (songData?[\"title\"] as? String) ??\n                   (songData?[\"alternativeTitle\"] as? String) ??\n                   (websocketData[\"title\"] as? String)\n        let artist = (songData?[\"artist\"] as? String) ?? (websocketData[\"artist\"] as? String)\n        let album = songData?[\"album\"] as? String\n\n        let elapsed = extractDouble(from: songData, key: \"elapsedSeconds\") ??\n                     extractDouble(from: websocketData, key: \"position\")\n\n        let duration = extractDouble(from: songData, key: \"songDuration\") ??\n                      extractDouble(from: websocketData, key: \"songDuration\")\n        \n        let imageSrc = (songData?[\"imageSrc\"] as? String) ?? (websocketData[\"imageSrc\"] as? String)\n        let isShuffled = (websocketData[\"shuffle\"] as? Bool) ?? (songData?[\"isShuffled\"] as? Bool)\n\n        var repeatModeInt: Int? = nil\n        if let repeatVal = websocketData[\"repeat\"] as? String {\n            switch repeatVal.uppercased() {\n            case \"NONE\": repeatModeInt = 0\n            case \"ALL\": repeatModeInt = 1\n            case \"ONE\": repeatModeInt = 2\n            default: break\n            }\n        } else if let repeatStr = songData?[\"repeat\"] as? String {\n            switch repeatStr.uppercased() {\n            case \"NONE\": repeatModeInt = 0\n            case \"ALL\": repeatModeInt = 1\n            case \"ONE\": repeatModeInt = 2\n            default: break\n            }\n        }\n        \n        let volume = extractDouble(from: websocketData, key: \"volume\") ?? extractDouble(from: songData, key: \"volume\")\n\n        return PlaybackResponse(\n            isPaused: isPaused,\n            title: title,\n            artist: artist,\n            album: album,\n            elapsedSeconds: elapsed,\n            songDuration: duration,\n            imageSrc: imageSrc,\n            repeatMode: repeatModeInt,\n            isShuffled: isShuffled,\n            volume: volume\n        )\n    }\n    \n    func with(elapsedSeconds: Double) -> PlaybackResponse {\n        PlaybackResponse(\n            isPaused: isPaused,\n            title: title,\n            artist: artist,\n            album: album,\n            elapsedSeconds: elapsedSeconds,\n            songDuration: songDuration,\n            imageSrc: imageSrc,\n            repeatMode: repeatMode,\n            isShuffled: isShuffled,\n            volume: volume\n        )\n    }\n}\n\nprivate func extractDouble(from dict: [String: Any]?, key: String) -> Double? {\n    guard let dict = dict else { return nil }\n    if let value = dict[key] as? Double {\n        return value\n    } else if let value = dict[key] as? Int {\n        return Double(value)\n    }\n    return nil\n}\n"
  },
  {
    "path": "boringNotch/MediaControllers/YouTube Music Controller/YouTubeMusicNetworking.swift",
    "content": "//\n//  YouTubeMusicNetworking.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-14.\n//\n\nimport Foundation\n\n// MARK: - HTTP Client\nfinal class YouTubeMusicHTTPClient: ObservableObject {\n    private let session: URLSession\n    private let baseURL: String\n    private static let decoder = JSONDecoder()\n    private static let encoder = JSONEncoder()\n    \n    init(baseURL: String) {\n        self.baseURL = baseURL\n        \n        let config = URLSessionConfiguration.default\n        config.requestCachePolicy = .reloadIgnoringLocalCacheData\n        config.urlCache = nil\n        config.timeoutIntervalForRequest = 5\n        config.timeoutIntervalForResource = 10\n        \n        self.session = URLSession(configuration: config)\n    }\n    \n    // MARK: - Authentication\n    func authenticate() async throws -> String {\n        guard let url = URL(string: \"\\(baseURL)/auth/boringNotch\") else {\n            throw YouTubeMusicError.invalidURL\n        }\n\n        var request = URLRequest(url: url)\n        request.httpMethod = \"POST\"\n\n        let (data, response) = try await session.data(for: request)\n        try validateResponse(response)\n\n        let authResponse: AuthResponse = try Self.decoder.decode(AuthResponse.self, from: data)\n        return authResponse.accessToken\n    }\n    \n    // MARK: - Playback Info\n    func getPlaybackInfo(token: String) async throws -> PlaybackResponse {\n        let data = try await sendCommand(\n            endpoint: \"/song\",\n            method: \"GET\",\n            token: token\n        )\n        return try Self.decoder.decode(PlaybackResponse.self, from: data)\n    }\n\n    // MARK: - Like / Favourites\n    struct LikeStateResponse: Decodable, Sendable {\n        let state: String?\n    }\n\n\n    func getLikeState(token: String) async throws -> LikeStateResponse {\n        let data = try await sendCommand(endpoint: \"/like-state\", method: \"GET\", token: token)\n        return try Self.decoder.decode(LikeStateResponse.self, from: data)\n    }\n\n    func toggleLike(token: String) async throws -> Data {\n        return try await sendCommand(endpoint: \"/like\", method: \"POST\", token: token)\n    }\n\n    func toggleDislike(token: String) async throws -> Data {\n        return try await sendCommand(endpoint: \"/dislike\", method: \"POST\", token: token)\n    }\n    \n    // MARK: - Commands\n    func sendCommand(\n        endpoint: String,\n        method: String = \"POST\",\n        body: (any Codable & Sendable)? = nil,\n        token: String\n    ) async throws -> Data {\n        let request = try createAuthenticatedRequest(\n            endpoint: \"/api/v1\\(endpoint)\",\n            method: method,\n            body: body,\n            token: token\n        )\n        \n        let (data, response) = try await session.data(for: request)\n        try validateResponse(response)\n        \n        return data\n    }\n    \n    // MARK: - Private Helpers\n    private func createAuthenticatedRequest(\n        endpoint: String,\n        method: String,\n        body: (any Codable & Sendable)? = nil,\n        token: String\n    ) throws -> URLRequest {\n        guard let url = URL(string: \"\\(baseURL)\\(endpoint)\") else {\n            throw YouTubeMusicError.invalidURL\n        }\n        \n        var request = URLRequest(url: url)\n        request.httpMethod = method\n        request.setValue(\"Bearer \\(token)\", forHTTPHeaderField: \"Authorization\")\n        \n        if let body = body {\n            request.httpBody = try Self.encoder.encode(body)\n            request.setValue(\"application/json\", forHTTPHeaderField: \"Content-Type\")\n        }\n        \n        return request\n    }\n    \n    private func validateResponse(_ response: URLResponse) throws {\n        guard let httpResponse = response as? HTTPURLResponse else {\n            throw YouTubeMusicError.invalidResponse\n        }\n        \n        switch httpResponse.statusCode {\n        case 200..<300:\n            break\n        case 401, 403:\n            throw YouTubeMusicError.authenticationRequired\n        default:\n            throw YouTubeMusicError.httpError(httpResponse.statusCode)\n        }\n    }\n}\n\n// MARK: - WebSocket Client\nactor YouTubeMusicWebSocketClient {\n    private var task: URLSessionWebSocketTask?\n    private let session: URLSession\n    private let onMessage: @Sendable (Data) async -> Void\n    private let onDisconnect: @Sendable () async -> Void\n    \n    var isConnected: Bool { task != nil }\n    \n    init(\n        onMessage: @escaping @Sendable (Data) async -> Void,\n        onDisconnect: @escaping @Sendable () async -> Void,\n        session: URLSession = .shared\n    ) {\n        self.onMessage = onMessage\n        self.onDisconnect = onDisconnect\n        self.session = session\n    }\n    \n    func connect(to url: URL, with token: String) async throws {\n        await disconnect()\n        \n        var request = URLRequest(url: url)\n        request.setValue(\"Bearer \\(token)\", forHTTPHeaderField: \"Authorization\")\n        \n        let newTask = session.webSocketTask(with: request)\n        task = newTask\n        newTask.resume()\n        \n        Task { await listenForMessages() }\n    }\n    \n    func disconnect() async {\n        task?.cancel(with: .goingAway, reason: nil)\n        task = nil\n    }\n    \n    private func listenForMessages() async {\n        guard let currentTask = task else { return }\n        \n        while !Task.isCancelled && task != nil {\n            do {\n                let message = try await currentTask.receive()\n                \n                let data: Data\n                switch message {\n                case .data(let d):\n                    data = d\n                case .string(let s):\n                    data = s.data(using: .utf8) ?? Data()\n                @unknown default:\n                    continue\n                }\n                \n                await onMessage(data)\n            } catch {\n                break\n            }\n        }\n        task = nil\n        await onDisconnect()\n    }\n}\n\n// MARK: - WebSocket URL Helper\nstruct WebSocketURLBuilder {\n    static func buildURL(from baseURL: String) -> URL? {\n        guard var components = URLComponents(string: baseURL) else { return nil }\n\n        switch components.scheme {\n        case \"http\":\n            components.scheme = \"ws\"\n        case \"https\":\n            components.scheme = \"wss\"\n        default:\n            break\n        }\n\n        components.path = \"/api/v1/ws\"\n        return components.url\n    }\n}\n\n// MARK: - Errors\nenum YouTubeMusicError: Error, LocalizedError, Sendable {\n    case invalidURL\n    case invalidResponse\n    case httpError(Int)\n    case authenticationRequired\n    case webSocketNotConnected\n    case encodingFailed\n    case decodingFailed\n    \n    var errorDescription: String? {\n        switch self {\n        case .invalidURL:\n            return \"Invalid URL\"\n        case .invalidResponse:\n            return \"Invalid response\"\n        case .httpError(let code):\n            return \"HTTP error: \\(code)\"\n        case .authenticationRequired:\n            return \"Authentication required\"\n        case .webSocketNotConnected:\n            return \"WebSocket not connected\"\n        case .encodingFailed:\n            return \"Failed to encode data\"\n        case .decodingFailed:\n            return \"Failed to decode data\"\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "boringNotch/Providers/CalendarServiceProviding.swift",
    "content": "//\n//  CalendarServiceProvider.swift\n//  Calendr\n//\n//  Created by Paker on 31/12/20.\n//  Original source: Original source: https://github.com/pakerwreah/Calendr\n//  Modified by Alexander on 08/06/25\n//\n\nimport Foundation\n@preconcurrency import EventKit\n\nprotocol CalendarServiceProviding {\n    func requestAccess(to type: EKEntityType) async throws -> Bool\n    func calendars() async -> [CalendarModel]\n    func events(from start: Date, to end: Date, calendars: [String]) async -> [EventModel]\n}\n\nclass CalendarService: CalendarServiceProviding {\n    private let store = EKEventStore()\n    \n    @MainActor\n    func requestAccess(to type: EKEntityType) async throws -> Bool {\n        if #available(macOS 14.0, *) {\n            switch type {\n            case .event:\n                return try await store.requestFullAccessToEvents()\n            case .reminder:\n                return try await store.requestFullAccessToReminders()\n            @unknown default:\n                return false\n            }\n        } else {\n            return try await store.requestAccess(to: type)\n        }\n    }\n    \n    private func hasAccess(to entityType: EKEntityType) -> Bool {\n        let status = EKEventStore.authorizationStatus(for: entityType)\n        if #available(macOS 14.0, *) {\n            return status == .fullAccess\n        } else {\n            return status == .authorized\n        }\n    }\n    \n    func calendars() async -> [CalendarModel] {\n        var calendars: [EKCalendar] = []\n        \n        for type in [EKEntityType.event, .reminder] where hasAccess(to: type) {\n            calendars.append(contentsOf: store.calendars(for: type))\n        }\n        \n        return calendars.map { CalendarModel(from: $0) }\n    }\n    \n    func events(from start: Date, to end: Date, calendars ids: [String]) async -> [EventModel] {\n        let allCalendars = await self.calendars()\n        let filteredCalendars = allCalendars.filter { ids.isEmpty || ids.contains($0.id) }\n        let ekCalendars = filteredCalendars.compactMap { calendarModel in\n            store.calendars(for: .event).first { $0.calendarIdentifier == calendarModel.id } ??\n            store.calendars(for: .reminder).first { $0.calendarIdentifier == calendarModel.id }\n        }\n        \n        var events: [EventModel] = []\n        \n        // Fetch regular events\n        if hasAccess(to: .event) {\n            let eventCalendars = ekCalendars.filter { store.calendars(for: .event).contains($0) }\n            let predicate = store.predicateForEvents(withStart: start, end: end, calendars: eventCalendars)\n            let ekEvents = store.events(matching: predicate)\n            events.append(contentsOf: ekEvents.compactMap { EventModel(from: $0) })\n        }\n        \n        // Fetch reminders\n        if hasAccess(to: .reminder) {\n            let reminderCalendars = ekCalendars.filter { store.calendars(for: .reminder).contains($0) }\n            events.append(contentsOf: await fetchReminders(from: start, to: end, calendars: reminderCalendars))\n        }\n        \n        return events.sorted { $0.start < $1.start }\n    }\n    \n    private func fetchReminders(from start: Date, to end: Date, calendars: [EKCalendar]) async -> [EventModel] {\n        return await withCheckedContinuation { continuation in\n            // Create predicate for reminders with due dates in the specified range\n            let predicate = store.predicateForReminders(in: calendars)\n            \n            store.fetchReminders(matching: predicate) { reminders in\n                \n                let filteredReminders = (reminders ?? []).filter { reminder in\n                    // Check if reminder has a due date within our range\n                    guard let dueDate = reminder.dueDateComponents?.date else {\n                        return false\n                    }\n                    \n                    return dueDate >= start && dueDate <= end\n                }\n                \n                // Convert to EventModel\n                let eventModels = filteredReminders.compactMap { reminder in\n                    EventModel(from: reminder)\n                }\n                \n                continuation.resume(returning: eventModels)\n            }\n        }\n    }\n    \n    func setReminderCompleted(reminderID: String, completed: Bool) async {\n        guard let reminder = store.calendarItem(withIdentifier: reminderID) as? EKReminder else { return }\n        reminder.isCompleted = completed\n        do {\n            try store.save(reminder, commit: true)\n        } catch {\n            print(\"Failed to update reminder completion: \\(error)\")\n        }\n    }\n}\n\n// MARK: - Model Extensions\n\nextension CalendarModel {\n    init(from calendar: EKCalendar) {\n        self.init(\n            id: calendar.calendarIdentifier,\n            account: calendar.accountTitle,\n            title: calendar.title,\n            color: calendar.color,\n            isSubscribed: calendar.isSubscribed || calendar.isDelegate,\n            isReminder: calendar.allowedEntityTypes.contains(.reminder)\n        )\n    }\n}\n\nextension EventModel {\n    init?(from event: EKEvent) {\n        guard let calendar = event.calendar else { return nil }\n        \n        self.init(\n            id: event.calendarItemIdentifier,\n            start: event.startDate,\n            end: event.endDate,\n            title: event.title ?? \"\",\n            location: event.location,\n            notes: event.notes,\n            url: event.url,\n            isAllDay: event.shouldBeAllDay,\n            type: .init(from: event),\n            calendar: .init(from: calendar),\n            participants: .init(from: event),\n            timeZone: calendar.isSubscribed || calendar.isDelegate ? nil : event.timeZone,\n            hasRecurrenceRules: event.hasRecurrenceRules || event.isDetached,\n            priority: nil\n        )\n    }\n    \n    init?(from reminder: EKReminder) {\n        guard let calendar = reminder.calendar,\n              let dueDateComponents = reminder.dueDateComponents,\n              let date = Calendar.current.date(from: dueDateComponents)\n        else { return nil }\n        \n        self.init(\n            id: reminder.calendarItemIdentifier,\n            start: date,\n            end: Calendar.current.endOfDay(for: date),\n            title: reminder.title ?? \"\",\n            location: reminder.location,\n            notes: reminder.notes,\n            url: reminder.url,\n            isAllDay: dueDateComponents.hour == nil,\n            type: .reminder(completed: reminder.isCompleted),\n            calendar: .init(from: calendar),\n            participants: [],\n            timeZone: calendar.isSubscribed || calendar.isDelegate ? nil : reminder.timeZone,\n            hasRecurrenceRules: reminder.hasRecurrenceRules,\n            priority: .init(from: reminder.priority)\n        )\n    }\n}\n\nextension EventType {\n    init(from event: EKEvent) {\n        self = event.birthdayContactIdentifier != nil ? .birthday : .event(.init(from: event.currentUser?.participantStatus))\n    }\n}\n\nextension AttendanceStatus {\n    init(from status: EKParticipantStatus?) {\n        switch status {\n        case .accepted:\n            self = .accepted\n        case .tentative:\n            self = .maybe\n        case .declined:\n            self = .declined\n        case .pending:\n            self = .pending\n        default:\n            self = .unknown\n        }\n    }\n}\n\nextension Array where Element == Participant {\n    init(from event: EKEvent) {\n        var participants = event.attendees ?? []\n        if let organizer = event.organizer, !participants.contains(where: { $0.url == organizer.url }) {\n            participants.append(organizer)\n        }\n        self.init(\n            participants.map { .init(from: $0, isOrganizer: $0.url == event.organizer?.url) }\n        )\n    }\n}\n\nextension Participant {\n    init(from participant: EKParticipant, isOrganizer: Bool) {\n        self.init(\n            name: participant.name ?? participant.url.absoluteString.replacingOccurrences(of: \"mailto:\", with: \"\"),\n            status: .init(from: participant.participantStatus),\n            isOrganizer: isOrganizer,\n            isCurrentUser: participant.isCurrentUser\n        )\n    }\n}\n\nextension Priority {\n    init?(from p: Int) {\n        switch p {\n        case 1...4:\n            self = .high\n        case 5:\n            self = .medium\n        case 6...9:\n            self = .low\n        default:\n            return nil\n        }\n    }\n}\n\n// MARK: - Helper Extensions\n\nprivate extension EKCalendar {\n    var accountTitle: String {\n        switch source.sourceType {\n        case .local, .subscribed, .birthdays:\n            return \"Other\"\n        default:\n            return source.title\n        }\n    }\n    \n    var isDelegate: Bool {\n        if #available(macOS 13.0, *) {\n            return source.isDelegate\n        } else {\n            return false\n        }\n    }\n}\n\nprivate extension EKEvent {\n    var currentUser: EKParticipant? {\n        attendees?.first(where: \\.isCurrentUser)\n    }\n    \n    var shouldBeAllDay: Bool {\n        guard !isAllDay else { return true }\n        let calendar = Calendar.current\n        let startOfDay = calendar.startOfDay(for: startDate)\n        let endOfDay = calendar.dateInterval(of: .day, for: endDate)?.end\n        return startDate == startOfDay && endDate == endOfDay\n    }\n}\n\nprivate extension Calendar {\n    func endOfDay(for date: Date) -> Date {\n        dateInterval(of: .day, for: date)?.end ?? date\n    }\n}\n"
  },
  {
    "path": "boringNotch/Shortcuts/ShortcutConstants.swift",
    "content": "//\n//  Constants.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 16/08/2024.\n//\n\nimport KeyboardShortcuts\nimport SwiftUI\n\nextension KeyboardShortcuts.Name {\n    static let clipboardHistoryPanel = Self(\"clipboardHistoryPanel\", default: .init(.c, modifiers: [.shift, .command]))\n    static let toggleMicrophone = Self(\"toggleMicrophone\", default: .init(.f5, modifiers: [.function]))\n    static let decreaseBacklight = Self(\"decreaseBacklight\", default: .init(.f1, modifiers: [.command]))\n    static let increaseBacklight = Self(\"increaseBacklight\", default: .init(.f2, modifiers: [.command]))\n    static let toggleSneakPeek = Self(\"toggleSneakPeek\", default: .init(.h, modifiers: [.command, .shift]))\n    static let toggleNotchOpen = Self(\"toggleNotchOpen\", default: .init(.i, modifiers: [.command, .shift]))\n}\n"
  },
  {
    "path": "boringNotch/XPCHelperClient/BoringNotchXPCHelperProtocol.swift",
    "content": "//\n//  BoringNotchXPCHelperProtocol.swift\n//  BoringNotchXPCHelper\n//\n//  Created by Alexander on 2025-11-16.\n//\n\nimport Foundation\n\n/// The protocol that this service will vend as its API. This protocol will also need to be visible to the process hosting the service.\n@objc protocol BoringNotchXPCHelperProtocol {\n    func isAccessibilityAuthorized(with reply: @escaping (Bool) -> Void)\n    func requestAccessibilityAuthorization()\n    func ensureAccessibilityAuthorization(_ promptIfNeeded: Bool, with reply: @escaping (Bool) -> Void)\n    // Keyboard backlight / CoreBrightness access (performed by the helper)\n    func isKeyboardBrightnessAvailable(with reply: @escaping (Bool) -> Void)\n    func currentKeyboardBrightness(with reply: @escaping (NSNumber?) -> Void)\n    func setKeyboardBrightness(_ value: Float, with reply: @escaping (Bool) -> Void)\n    // Screen brightness access (performed by the helper)\n    func isScreenBrightnessAvailable(with reply: @escaping (Bool) -> Void)\n    func currentScreenBrightness(with reply: @escaping (NSNumber?) -> Void)\n    func setScreenBrightness(_ value: Float, with reply: @escaping (Bool) -> Void)\n}\n\n"
  },
  {
    "path": "boringNotch/XPCHelperClient/XPCHelperClient.swift",
    "content": "import Foundation\nimport Cocoa\nimport AsyncXPCConnection\n\nfinal class XPCHelperClient: NSObject {\n    nonisolated static let shared = XPCHelperClient()\n    \n    private let serviceName = \"theboringteam.boringnotch.BoringNotchXPCHelper\"\n    \n    private var remoteService: RemoteXPCService<BoringNotchXPCHelperProtocol>?\n    private var connection: NSXPCConnection?\n    private var lastKnownAuthorization: Bool?\n    private var monitoringTask: Task<Void, Never>?\n    \n    deinit {\n        connection?.invalidate()\n        stopMonitoringAccessibilityAuthorization()\n    }\n    \n    // MARK: - Connection Management (Main Actor Isolated)\n    \n    @MainActor\n    private func ensureRemoteService() -> RemoteXPCService<BoringNotchXPCHelperProtocol> {\n        if let existing = remoteService {\n            return existing\n        }\n        \n        let conn = NSXPCConnection(serviceName: serviceName)\n        \n        conn.interruptionHandler = { [weak self] in\n            Task { @MainActor in\n                self?.connection = nil\n                self?.remoteService = nil\n            }\n        }\n        \n        conn.invalidationHandler = { [weak self] in\n            Task { @MainActor in\n                self?.connection = nil\n                self?.remoteService = nil\n            }\n        }\n        \n        conn.resume()\n        \n        let service = RemoteXPCService<BoringNotchXPCHelperProtocol>(\n            connection: conn,\n            remoteInterface: BoringNotchXPCHelperProtocol.self\n        )\n        \n        connection = conn\n        remoteService = service\n        return service\n    }\n    \n    @MainActor\n    private func getRemoteService() -> RemoteXPCService<BoringNotchXPCHelperProtocol>? {\n        remoteService\n    }\n    \n    @MainActor\n    private func notifyAuthorizationChange(_ granted: Bool) {\n        guard lastKnownAuthorization != granted else { return }\n        lastKnownAuthorization = granted\n        NotificationCenter.default.post(\n            name: .accessibilityAuthorizationChanged,\n            object: nil,\n            userInfo: [\"granted\": granted]\n        )\n    }\n\n    // MARK: - Monitoring\n    nonisolated func startMonitoringAccessibilityAuthorization(every interval: TimeInterval = 3.0) {\n        // Ensure only one monitor exists\n        stopMonitoringAccessibilityAuthorization()\n        monitoringTask = Task.detached { [weak self] in\n            guard let self = self else { return }\n            while !Task.isCancelled {\n                // Call the helper method periodically which will notify on change\n                _ = await self.isAccessibilityAuthorized()\n                do {\n                    try await Task.sleep(for: .seconds(interval))\n                } catch { break }\n            }\n        }\n    }\n\n    nonisolated func stopMonitoringAccessibilityAuthorization() {\n        monitoringTask?.cancel()\n        monitoringTask = nil\n    }\n\n    // Expose whether the client is actively monitoring (useful for tests/debug)\n    var isMonitoring: Bool {\n        return monitoringTask != nil\n    }\n    \n    // MARK: - Accessibility\n    \n    nonisolated func requestAccessibilityAuthorization() {\n        Task {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            try? await service.withService { service in\n                service.requestAccessibilityAuthorization()\n            }\n        }\n    }\n    \n    nonisolated func isAccessibilityAuthorized() async -> Bool {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            let result: Bool = try await service.withContinuation { service, continuation in\n                service.isAccessibilityAuthorized { authorized in\n                    continuation.resume(returning: authorized)\n                }\n            }\n            await MainActor.run {\n                notifyAuthorizationChange(result)\n            }\n            return result\n        } catch {\n            return false\n        }\n    }\n    \n    nonisolated func ensureAccessibilityAuthorization(promptIfNeeded: Bool) async -> Bool {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            let result: Bool = try await service.withContinuation { service, continuation in\n                service.ensureAccessibilityAuthorization(promptIfNeeded) { authorized in\n                    continuation.resume(returning: authorized)\n                }\n            }\n            await MainActor.run {\n                notifyAuthorizationChange(result)\n            }\n            return result\n        } catch {\n            return false\n        }\n    }\n    \n    // MARK: - Keyboard Brightness\n    \n    nonisolated func isKeyboardBrightnessAvailable() async -> Bool {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            return try await service.withContinuation { service, continuation in\n                service.isKeyboardBrightnessAvailable { available in\n                    continuation.resume(returning: available)\n                }\n            }\n        } catch {\n            return false\n        }\n    }\n    \n    nonisolated func currentKeyboardBrightness() async -> Float? {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            let result: NSNumber? = try await service.withContinuation { service, continuation in\n                service.currentKeyboardBrightness { value in\n                    continuation.resume(returning: value)\n                }\n            }\n            return result?.floatValue\n        } catch {\n            return nil\n        }\n    }\n    \n    nonisolated func setKeyboardBrightness(_ value: Float) async -> Bool {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            return try await service.withContinuation { service, continuation in\n                service.setKeyboardBrightness(value) { success in\n                    continuation.resume(returning: success)\n                }\n            }\n        } catch {\n            return false\n        }\n    }\n    \n    // MARK: - Screen Brightness\n    \n    nonisolated func isScreenBrightnessAvailable() async -> Bool {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            return try await service.withContinuation { service, continuation in\n                service.isScreenBrightnessAvailable { available in\n                    continuation.resume(returning: available)\n                }\n            }\n        } catch {\n            return false\n        }\n    }\n    \n    nonisolated func currentScreenBrightness() async -> Float? {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            let result: NSNumber? = try await service.withContinuation { service, continuation in\n                service.currentScreenBrightness { value in\n                    continuation.resume(returning: value)\n                }\n            }\n            return result?.floatValue\n        } catch {\n            return nil\n        }\n    }\n    \n    nonisolated func setScreenBrightness(_ value: Float) async -> Bool {\n        do {\n            let service = await MainActor.run {\n                ensureRemoteService()\n            }\n            return try await service.withContinuation { service, continuation in\n                service.setScreenBrightness(value) { success in\n                    continuation.resume(returning: success)\n                }\n            }\n        } catch {\n            return false\n        }\n    }\n}\n\nextension Notification.Name {\n    static let accessibilityAuthorizationChanged = Notification.Name(\"accessibilityAuthorizationChanged\")\n}\n\n\n"
  },
  {
    "path": "boringNotch/animations/HelloAnimation.swift",
    "content": "//\n//  HelloAnimation.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 08/08/24.\n//\n\nimport SwiftUI\n\nstruct HelloShape: Shape {\n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        let width = rect.size.width\n        let height = rect.size.height\n        path.move(to: CGPoint(x: 0.00095*width, y: 0.88718*height))\n        path.addCurve(to: CGPoint(x: 0.19536*width, y: 0.31015*height), control1: CGPoint(x: 0.00993*width, y: 0.87738*height), control2: CGPoint(x: 0.16556*width, y: 0.56785*height))\n        path.addCurve(to: CGPoint(x: 0.15043*width, y: 0.04964*height), control1: CGPoint(x: 0.22517*width, y: 0.05245*height), control2: CGPoint(x: 0.1859*width, y: -0.068*height))\n        path.addCurve(to: CGPoint(x: 0.10028*width, y: 0.932*height), control1: CGPoint(x: 0.11495*width, y: 0.16729*height), control2: CGPoint(x: 0.09792*width, y: 1.02023*height))\n        path.addCurve(to: CGPoint(x: 0.18354*width, y: 0.47822*height), control1: CGPoint(x: 0.10265*width, y: 0.84376*height), control2: CGPoint(x: 0.12157*width, y: 0.47822*height))\n        path.addCurve(to: CGPoint(x: 0.22327*width, y: 0.88718*height), control1: CGPoint(x: 0.25733*width, y: 0.51463*height), control2: CGPoint(x: 0.19915*width, y: 0.81575*height))\n        path.addCurve(to: CGPoint(x: 0.38553*width, y: 0.71351*height), control1: CGPoint(x: 0.2474*width, y: 0.95861*height), control2: CGPoint(x: 0.33586*width, y: 0.89978*height))\n        path.addCurve(to: CGPoint(x: 0.35998*width, y: 0.45441*height), control1: CGPoint(x: 0.43519*width, y: 0.52724*height), control2: CGPoint(x: 0.38978*width, y: 0.4306*height))\n        path.addCurve(to: CGPoint(x: 0.35478*width, y: 0.87317*height), control1: CGPoint(x: 0.33018*width, y: 0.47822*height), control2: CGPoint(x: 0.27956*width, y: 0.71631*height))\n        path.addCurve(to: CGPoint(x: 0.53453*width, y: 0.62808*height), control1: CGPoint(x: 0.42999*width, y: 1.03004*height), control2: CGPoint(x: 0.51892*width, y: 0.6939*height))\n        path.addCurve(to: CGPoint(x: 0.57332*width, y: 0.00623*height), control1: CGPoint(x: 0.55014*width, y: 0.56225*height), control2: CGPoint(x: 0.63955*width, y: 0.05805*height))\n        path.addCurve(to: CGPoint(x: 0.48723*width, y: 0.60146*height), control1: CGPoint(x: 0.5071*width, y: -0.04559*height), control2: CGPoint(x: 0.48486*width, y: 0.50623*height))\n        path.addCurve(to: CGPoint(x: 0.54588*width, y: 0.91239*height), control1: CGPoint(x: 0.48959*width, y: 0.6967*height), control2: CGPoint(x: 0.50378*width, y: 0.87597*height))\n        path.addCurve(to: CGPoint(x: 0.70719*width, y: 0.51043*height), control1: CGPoint(x: 0.58798*width, y: 0.9488*height), control2: CGPoint(x: 0.6807*width, y: 0.64768*height))\n        path.addCurve(to: CGPoint(x: 0.73273*width, y: 0.01323*height), control1: CGPoint(x: 0.73368*width, y: 0.37317*height), control2: CGPoint(x: 0.76679*width, y: 0.03984*height))\n        path.addCurve(to: CGPoint(x: 0.67171*width, y: 0.15048*height), control1: CGPoint(x: 0.69868*width, y: -0.01338*height), control2: CGPoint(x: 0.68259*width, y: 0.07205*height))\n        path.addCurve(to: CGPoint(x: 0.69678*width, y: 0.92639*height), control1: CGPoint(x: 0.66083*width, y: 0.22892*height), control2: CGPoint(x: 0.62204*width, y: 0.86057*height))\n        path.addCurve(to: CGPoint(x: 0.87275*width, y: 0.47822*height), control1: CGPoint(x: 0.77152*width, y: 0.99222*height), control2: CGPoint(x: 0.78855*width, y: 0.42997*height))\n        path.addCurve(to: CGPoint(x: 0.91438*width, y: 0.89776*height), control1: CGPoint(x: 0.9734*width, y: 0.51043*height), control2: CGPoint(x: 0.92329*width, y: 0.85998*height))\n        path.addCurve(to: CGPoint(x: 0.79943*width, y: 0.69608*height), control1: CGPoint(x: 0.87047*width, y: 1.08403*height), control2: CGPoint(x: 0.77956*width, y: height))\n        path.addCurve(to: CGPoint(x: 0.92006*width, y: 0.53081*height), control1: CGPoint(x: 0.81523*width, y: 0.45436*height), control2: CGPoint(x: 0.86282*width, y: 0.43277*height))\n        path.addCurve(to: CGPoint(x: 0.99905*width, y: 0.432*height), control1: CGPoint(x: 0.95979*width, y: 0.57703*height), control2: CGPoint(x: 0.98959*width, y: 0.4944*height))\n        return path\n    }\n}\n\nextension ShapeStyle where Self == AngularGradient {\n    static var hello: some ShapeStyle {\n        LinearGradient(\n            stops: [\n                .init(color: .blue, location: 0.0),\n                .init(color: .purple, location: 0.2),\n                .init(color: .red, location: 0.4),\n                .init(color: .mint, location: 0.5),\n                .init(color: .indigo, location: 0.7),\n                .init(color: .pink, location: 0.9),\n                .init(color: .blue, location: 1.0)\n            ],\n            startPoint: .leading,\n            endPoint: .trailing\n        )\n    }\n}\n\nstruct GlowingSnake<\n    Content: Shape,\n    Fill: ShapeStyle\n>: View, Animatable {\n    \n    var progress: Double\n    var delay: Double = 1.0\n    var fill: Fill\n    var lineWidth = 4.0\n    var blurRadius = 8.0\n    \n    @ViewBuilder var shape: Content\n    \n    var animatableData: Double {\n        get { progress }\n        set { progress = newValue }\n    }\n    \n    var body: some View {\n        shape\n            .trim(\n                from: {\n                    if progress > 1 - delay {\n                        2 * progress - 1.0\n                    } else if progress > delay {\n                        progress - delay\n                    } else {\n                        .zero\n                    }\n                }(),\n                to: progress\n            )\n            .glow(\n                fill: fill,\n                lineWidth: lineWidth,\n                blurRadius: blurRadius\n            )\n    }\n}\n\nstruct HelloAnimation: View {\n    @State private var progress: Double = 0.0\n    \n    var onFinish: () -> Void\n    \n    var body: some View {\n        GlowingSnake(\n            progress: progress,\n            fill: .hello,\n            lineWidth: 8,\n            blurRadius: 8.0,\n            shape: { HelloShape() }\n        )\n        .task {\n            // Wait for the \"opening\" animation (notch expansion) to complete before starting the snake\n            try? await Task.sleep(for: .seconds(0.6))\n            \n            withAnimation(\n                .easeInOut(duration: 4.0)\n            ) {\n                progress = 1.0\n            }\n            \n            // Wait for the animation to complete\n            try? await Task.sleep(for: .seconds(4.0))\n            \n            onFinish()\n        }\n    }\n}\n\nextension View where Self: Shape {\n    func glow(\n        fill: some ShapeStyle,\n        lineWidth: Double,\n        blurRadius: Double = 8.0,\n        lineCap: CGLineCap = .round\n    ) -> some View {\n        self\n            .stroke(style: StrokeStyle(lineWidth: lineWidth / 2, lineCap: lineCap))\n            .fill(fill)\n            .overlay {\n                self\n                    .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: lineCap))\n                    .fill(fill)\n                    .blur(radius: blurRadius)\n            }\n            .overlay {\n                self\n                    .stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: lineCap))\n                    .fill(fill)\n                    .blur(radius: blurRadius / 2)\n            }\n    }\n}\n\n#Preview {\n    HelloAnimation(onFinish: {})\n        .frame(width: 300, height: 100)\n}\n"
  },
  {
    "path": "boringNotch/animations/drop.swift",
    "content": "//\n//  drop.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on  04/08/24.\n//\n\nimport Foundation\nimport SwiftUI\n\n\npublic class BoringAnimations {\n    @Published var notchStyle: Style = .notch\n    \n    init() {\n        self.notchStyle = .notch\n    }\n    \n    var animation: Animation {\n        if #available(macOS 14.0, *), notchStyle == .notch {\n            Animation.spring(.bouncy(duration: 0.4))\n        } else {\n            Animation.timingCurve(0.16, 1, 0.3, 1, duration: 0.7)\n        }\n    }\n    \n    // TODO: Move all animations to this file\n    \n}\n"
  },
  {
    "path": "boringNotch/boringNotch.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>com.apple.security.app-sandbox</key>\n\t<true/>\n\t<key>com.apple.security.automation.apple-events</key>\n\t<true/>\n\t<key>com.apple.security.device.camera</key>\n\t<true/>\n\t<key>com.apple.security.personal-information.calendars</key>\n\t<true/>\n\t<key>com.apple.security.files.bookmarks.app-scope</key>\n\t<true/>\n\t<key>com.apple.security.files.bookmarks.document-scope</key>\n\t<true/>\n\t<key>com.apple.security.files.user-selected.read-write</key>\n\t<true/>\n\t<key>com.apple.security.network.client</key>\n\t<true/>\n\t<key>com.apple.security.network.server</key>\n\t<true/>\n\t<key>com.apple.security.temporary-exception.apple-events</key>\n\t<array>\n\t\t<string>com.spotify.client</string>\n\t\t<string>com.apple.Music</string>\n\t</array>\n\t<key>com.apple.security.temporary-exception.mach-lookup.global-name</key>\n\t<array>\n\t\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spks</string>\n\t\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)-spki</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "boringNotch/boringNotchApp.swift",
    "content": "//\n//  boringNotchApp.swift\n//  boringNotchApp\n//\n//  Created by Harsh Vardhan  Goswami  on 02/08/24.\n//\n\nimport AVFoundation\nimport Combine\nimport Defaults\nimport KeyboardShortcuts\nimport Sparkle\nimport SwiftUI\n\n@main\nstruct DynamicNotchApp: App {\n    @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate\n    @Default(.menubarIcon) var showMenuBarIcon\n    @Environment(\\.openWindow) var openWindow\n\n    let updaterController: SPUStandardUpdaterController\n\n    init() {\n        updaterController = SPUStandardUpdaterController(\n            startingUpdater: true, updaterDelegate: nil, userDriverDelegate: nil)\n\n        // Initialize the settings window controller with the updater controller\n        SettingsWindowController.shared.setUpdaterController(updaterController)\n    }\n\n    var body: some Scene {\n        MenuBarExtra(\"boring.notch\", systemImage: \"sparkle\", isInserted: $showMenuBarIcon) {\n            Button(\"Settings\") {\n                SettingsWindowController.shared.showWindow()\n            }\n            .keyboardShortcut(KeyEquivalent(\",\"), modifiers: .command)\n            CheckForUpdatesView(updater: updaterController.updater)\n            Divider()\n            Button(\"Restart Boring Notch\") {\n                ApplicationRelauncher.restart()\n            }\n            Button(\"Quit\", role: .destructive) {\n                NSApplication.shared.terminate(self)\n            }\n            .keyboardShortcut(KeyEquivalent(\"Q\"), modifiers: .command)\n        }\n    }\n}\n\nclass AppDelegate: NSObject, NSApplicationDelegate {\n    var statusItem: NSStatusItem?\n    var windows: [String: NSWindow] = [:] // UUID -> NSWindow\n    var viewModels: [String: BoringViewModel] = [:] // UUID -> BoringViewModel\n    var window: NSWindow?\n    let vm: BoringViewModel = .init()\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    var quickShareService = QuickShareService.shared\n    var whatsNewWindow: NSWindow?\n    var timer: Timer?\n    var closeNotchTask: Task<Void, Never>?\n    private var previousScreens: [NSScreen]?\n    private var onboardingWindowController: NSWindowController?\n    private var screenLockedObserver: Any?\n    private var screenUnlockedObserver: Any?\n    private var isScreenLocked: Bool = false\n    private var windowScreenDidChangeObserver: Any?\n    private var dragDetectors: [String: DragDetector] = [:] // UUID -> DragDetector\n\n    func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {\n        return false\n    }\n\n    func applicationWillTerminate(_ notification: Notification) {\n        NotificationCenter.default.removeObserver(self)\n        if let observer = screenLockedObserver {\n            DistributedNotificationCenter.default().removeObserver(observer)\n            screenLockedObserver = nil\n        }\n        if let observer = screenUnlockedObserver {\n            DistributedNotificationCenter.default().removeObserver(observer)\n            screenUnlockedObserver = nil\n        }\n        MusicManager.shared.destroy()\n        cleanupDragDetectors()\n        cleanupWindows()\n        XPCHelperClient.shared.stopMonitoringAccessibilityAuthorization()\n    }\n\n    @MainActor\n    func onScreenLocked(_ notification: Notification) {\n        isScreenLocked = true\n        if !Defaults[.showOnLockScreen] {\n            cleanupWindows()\n        } else {\n            enableSkyLightOnAllWindows()\n        }\n    }\n\n    @MainActor\n    func onScreenUnlocked(_ notification: Notification) {\n        isScreenLocked = false\n        if !Defaults[.showOnLockScreen] {\n            adjustWindowPosition(changeAlpha: true)\n        } else {\n            disableSkyLightOnAllWindows()\n        }\n    }\n    \n    @MainActor\n    private func enableSkyLightOnAllWindows() {\n        if Defaults[.showOnAllDisplays] {\n            windows.values.forEach { window in\n                if let skyWindow = window as? BoringNotchSkyLightWindow {\n                    skyWindow.enableSkyLight()\n                }\n            }\n        } else {\n            if let skyWindow = window as? BoringNotchSkyLightWindow {\n                skyWindow.enableSkyLight()\n            }\n        }\n    }\n    \n    @MainActor\n    private func disableSkyLightOnAllWindows() {\n        // Delay disabling SkyLight to avoid flicker during unlock transition\n        Task {\n            try? await Task.sleep(for: .milliseconds(150))\n            await MainActor.run {\n                if Defaults[.showOnAllDisplays] {\n                    self.windows.values.forEach { window in\n                        if let skyWindow = window as? BoringNotchSkyLightWindow {\n                            skyWindow.disableSkyLight()\n                        }\n                    }\n                } else {\n                    if let skyWindow = self.window as? BoringNotchSkyLightWindow {\n                        skyWindow.disableSkyLight()\n                    }\n                }\n            }\n        }\n    }\n\n    private func cleanupWindows(shouldInvert: Bool = false) {\n        let shouldCleanupMulti = shouldInvert ? !Defaults[.showOnAllDisplays] : Defaults[.showOnAllDisplays]\n        \n        if shouldCleanupMulti {\n            windows.values.forEach { window in\n                window.close()\n                NotchSpaceManager.shared.notchSpace.windows.remove(window)\n            }\n            windows.removeAll()\n            viewModels.removeAll()\n        } else if let window = window {\n            window.close()\n            NotchSpaceManager.shared.notchSpace.windows.remove(window)\n            if let obs = windowScreenDidChangeObserver {\n                NotificationCenter.default.removeObserver(obs)\n                windowScreenDidChangeObserver = nil\n            }\n            self.window = nil\n        }\n    }\n\n    private func cleanupDragDetectors() {\n        dragDetectors.values.forEach { detector in\n            detector.stopMonitoring()\n        }\n        dragDetectors.removeAll()\n    }\n\n    private func setupDragDetectors() {\n        cleanupDragDetectors()\n\n        guard Defaults[.expandedDragDetection] else { return }\n\n        if Defaults[.showOnAllDisplays] {\n            for screen in NSScreen.screens {\n                setupDragDetectorForScreen(screen)\n            }\n        } else {\n            let preferredScreen: NSScreen? = window?.screen\n                ?? NSScreen.screen(withUUID: coordinator.selectedScreenUUID)\n                ?? NSScreen.main\n\n            if let screen = preferredScreen {\n                setupDragDetectorForScreen(screen)\n            }\n        }\n    }\n\n    private func setupDragDetectorForScreen(_ screen: NSScreen) {\n        guard let uuid = screen.displayUUID else { return }\n        \n        let screenFrame = screen.frame\n        let notchHeight = openNotchSize.height\n        let notchWidth = openNotchSize.width\n        \n        // Create notch region at the top-center of the screen where an open notch would occupy\n        let notchRegion = CGRect(\n            x: screenFrame.midX - notchWidth / 2,\n            y: screenFrame.maxY - notchHeight,\n            width: notchWidth,\n            height: notchHeight\n        )\n        \n        let detector = DragDetector(notchRegion: notchRegion)\n        \n        detector.onDragEntersNotchRegion = { [weak self] in\n            Task { @MainActor in\n                self?.handleDragEntersNotchRegion(onScreen: screen)\n            }\n        }\n        \n        dragDetectors[uuid] = detector\n        detector.startMonitoring()\n    }\n\n    private func handleDragEntersNotchRegion(onScreen screen: NSScreen) {\n        guard let uuid = screen.displayUUID else { return }\n        \n        if Defaults[.showOnAllDisplays], let viewModel = viewModels[uuid] {\n            viewModel.open()\n            coordinator.currentView = .shelf\n        } else if !Defaults[.showOnAllDisplays], let windowScreen = window?.screen, screen == windowScreen {\n            vm.open()\n            coordinator.currentView = .shelf\n        }\n    }\n\n    private func createBoringNotchWindow(for screen: NSScreen, with viewModel: BoringViewModel) -> NSWindow {\n        let rect = NSRect(x: 0, y: 0, width: windowSize.width, height: windowSize.height)\n        let styleMask: NSWindow.StyleMask = [.borderless, .nonactivatingPanel, .utilityWindow, .hudWindow]\n        \n        let window = BoringNotchSkyLightWindow(contentRect: rect, styleMask: styleMask, backing: .buffered, defer: false)\n        \n        // Enable SkyLight only when screen is locked\n        if isScreenLocked {\n            window.enableSkyLight()\n        } else {\n            window.disableSkyLight()\n        }\n\n        window.contentView = NSHostingView(\n            rootView: ContentView()\n                .environmentObject(viewModel)\n        )\n\n        window.orderFrontRegardless()\n        NotchSpaceManager.shared.notchSpace.windows.insert(window)\n\n        // Observe when the window's screen changes so we can update drag detectors\n        windowScreenDidChangeObserver = NotificationCenter.default.addObserver(\n            forName: NSWindow.didChangeScreenNotification,\n            object: window,\n            queue: .main) { [weak self] _ in\n                Task { @MainActor in\n                    self?.setupDragDetectors()\n                }\n        }\n        return window\n    }\n\n    @MainActor\n    private func positionWindow(_ window: NSWindow, on screen: NSScreen, changeAlpha: Bool = false) {\n        if changeAlpha {\n            window.alphaValue = 0\n        }\n\n        let screenFrame = screen.frame\n        window.setFrameOrigin(\n            NSPoint(\n                x: screenFrame.origin.x + (screenFrame.width / 2) - window.frame.width / 2,\n                y: screenFrame.origin.y + screenFrame.height - window.frame.height\n            ))\n        window.alphaValue = 1\n    }\n\n    func applicationDidFinishLaunching(_ notification: Notification) {\n\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(screenConfigurationDidChange),\n            name: NSApplication.didChangeScreenParametersNotification,\n            object: nil\n        )\n\n        NotificationCenter.default.addObserver(\n            forName: Notification.Name.selectedScreenChanged, object: nil, queue: nil\n        ) { [weak self] _ in\n            Task { @MainActor in\n                self?.adjustWindowPosition(changeAlpha: true)\n                self?.setupDragDetectors()\n            }\n        }\n\n        NotificationCenter.default.addObserver(\n            forName: Notification.Name.notchHeightChanged, object: nil, queue: nil\n        ) { [weak self] _ in\n            Task { @MainActor in\n                self?.adjustWindowPosition()\n                self?.setupDragDetectors()\n            }\n        }\n\n        NotificationCenter.default.addObserver(\n            forName: Notification.Name.automaticallySwitchDisplayChanged, object: nil, queue: nil\n        ) { [weak self] _ in\n            guard let self = self, let window = self.window else { return }\n            Task { @MainActor in\n                window.alphaValue = self.coordinator.selectedScreenUUID == self.coordinator.preferredScreenUUID ? 1 : 0\n            }\n        }\n\n        NotificationCenter.default.addObserver(\n            forName: Notification.Name.showOnAllDisplaysChanged, object: nil, queue: nil\n        ) { [weak self] _ in\n            Task { @MainActor in\n                guard let self = self else { return }\n                self.cleanupWindows(shouldInvert: true)\n                self.adjustWindowPosition(changeAlpha: true)\n                self.setupDragDetectors()\n            }\n        }\n\n        NotificationCenter.default.addObserver(\n            forName: Notification.Name.expandedDragDetectionChanged, object: nil, queue: nil\n        ) { [weak self] _ in\n            Task { @MainActor in\n                self?.setupDragDetectors()\n            }\n        }\n\n        // Use closure-based observers for DistributedNotificationCenter and keep tokens for removal\n        screenLockedObserver = DistributedNotificationCenter.default().addObserver(\n            forName: NSNotification.Name(rawValue: \"com.apple.screenIsLocked\"),\n            object: nil, queue: .main) { [weak self] notification in\n                Task { @MainActor in\n                    self?.onScreenLocked(notification)\n                }\n        }\n\n        screenUnlockedObserver = DistributedNotificationCenter.default().addObserver(\n            forName: NSNotification.Name(rawValue: \"com.apple.screenIsUnlocked\"),\n            object: nil, queue: .main) { [weak self] notification in\n                Task { @MainActor in\n                    self?.onScreenUnlocked(notification)\n                }\n        }\n\n        KeyboardShortcuts.onKeyDown(for: .toggleSneakPeek) { [weak self] in\n            guard let self = self else { return }\n            if Defaults[.sneakPeekStyles] == .inline {\n                let newStatus = !self.coordinator.expandingView.show\n                self.coordinator.toggleExpandingView(status: newStatus, type: .music)\n            } else {\n                self.coordinator.toggleSneakPeek(\n                    status: !self.coordinator.sneakPeek.show,\n                    type: .music,\n                    duration: 3.0\n                )\n            }\n        }\n\n        KeyboardShortcuts.onKeyDown(for: .toggleNotchOpen) { [weak self] in\n            Task { [weak self] in\n                guard let self = self else { return }\n\n                let mouseLocation = NSEvent.mouseLocation\n\n                var viewModel = self.vm\n\n                if Defaults[.showOnAllDisplays] {\n                    for screen in NSScreen.screens {\n                        if screen.frame.contains(mouseLocation) {\n                            if let uuid = screen.displayUUID, let screenViewModel = self.viewModels[uuid] {\n                                viewModel = screenViewModel\n                                break\n                            }\n                        }\n                    }\n                }\n\n                self.closeNotchTask?.cancel()\n                self.closeNotchTask = nil\n\n                switch viewModel.notchState {\n                case .closed:\n                    await MainActor.run {\n                        viewModel.open()\n                    }\n\n                    let task = Task { [weak viewModel] in\n                        do {\n                            try await Task.sleep(for: .seconds(3))\n                            await MainActor.run {\n                                viewModel?.close()\n                            }\n                        } catch { }\n                    }\n                    self.closeNotchTask = task\n                case .open:\n                    await MainActor.run {\n                        viewModel.close()\n                    }\n                }\n            }\n        }\n\n        if !Defaults[.showOnAllDisplays] {\n            let viewModel = self.vm\n            let window = createBoringNotchWindow(\n                for: NSScreen.main ?? NSScreen.screens.first!, with: viewModel)\n            self.window = window\n            adjustWindowPosition(changeAlpha: true)\n        } else {\n            adjustWindowPosition(changeAlpha: true)\n        }\n\n        setupDragDetectors()\n\n        if coordinator.firstLaunch {\n            DispatchQueue.main.async {\n                self.showOnboardingWindow()\n            }\n            playWelcomeSound()\n        } else if MusicManager.shared.isNowPlayingDeprecated\n            && Defaults[.mediaController] == .nowPlaying\n        {\n            DispatchQueue.main.async {\n                self.showOnboardingWindow(step: .musicPermission)\n            }\n        }\n\n        previousScreens = NSScreen.screens\n    }\n\n    func playWelcomeSound() {\n        let audioPlayer = AudioPlayer()\n        audioPlayer.play(fileName: \"boring\", fileExtension: \"m4a\")\n    }\n\n    func deviceHasNotch() -> Bool {\n        if #available(macOS 12.0, *) {\n            for screen in NSScreen.screens {\n                if screen.safeAreaInsets.top > 0 {\n                    return true\n                }\n            }\n        }\n        return false\n    }\n\n    @objc func screenConfigurationDidChange() {\n        let currentScreens = NSScreen.screens\n\n        let screensChanged =\n            currentScreens.count != previousScreens?.count\n            || Set(currentScreens.compactMap { $0.displayUUID })\n                != Set(previousScreens?.compactMap { $0.displayUUID } ?? [])\n            || Set(currentScreens.map { $0.frame }) != Set(previousScreens?.map { $0.frame } ?? [])\n\n        previousScreens = currentScreens\n\n        if screensChanged {\n            DispatchQueue.main.async { [weak self] in\n                self?.cleanupWindows()\n                self?.adjustWindowPosition()\n                self?.setupDragDetectors()\n            }\n        }\n    }\n\n    @objc func adjustWindowPosition(changeAlpha: Bool = false) {\n        if Defaults[.showOnAllDisplays] {\n            let currentScreenUUIDs = Set(NSScreen.screens.compactMap { $0.displayUUID })\n\n            // Remove windows for screens that no longer exist\n            for uuid in windows.keys where !currentScreenUUIDs.contains(uuid) {\n                if let window = windows[uuid] {\n                    window.close()\n                    NotchSpaceManager.shared.notchSpace.windows.remove(window)\n                    windows.removeValue(forKey: uuid)\n                    viewModels.removeValue(forKey: uuid)\n                }\n            }\n\n            // Create or update windows for all screens\n            for screen in NSScreen.screens {\n                guard let uuid = screen.displayUUID else { continue }\n                \n                if windows[uuid] == nil {\n                    let viewModel = BoringViewModel(screenUUID: uuid)\n                    let window = createBoringNotchWindow(for: screen, with: viewModel)\n\n                    windows[uuid] = window\n                    viewModels[uuid] = viewModel\n                }\n\n                if let window = windows[uuid], let viewModel = viewModels[uuid] {\n                    positionWindow(window, on: screen, changeAlpha: changeAlpha)\n\n                    if viewModel.notchState == .closed {\n                        viewModel.close()\n                    }\n                }\n            }\n        } else {\n            let selectedScreen: NSScreen\n\n            if let preferredScreen = NSScreen.screen(withUUID: coordinator.preferredScreenUUID ?? \"\") {\n                coordinator.selectedScreenUUID = coordinator.preferredScreenUUID ?? \"\"\n                selectedScreen = preferredScreen\n            } else if Defaults[.automaticallySwitchDisplay], let mainScreen = NSScreen.main,\n                      let mainUUID = mainScreen.displayUUID {\n                coordinator.selectedScreenUUID = mainUUID\n                selectedScreen = mainScreen\n            } else {\n                if let window = window {\n                    window.alphaValue = 0\n                }\n                return\n            }\n\n            vm.screenUUID = selectedScreen.displayUUID\n            vm.notchSize = getClosedNotchSize(screenUUID: selectedScreen.displayUUID)\n\n            if window == nil {\n                window = createBoringNotchWindow(for: selectedScreen, with: vm)\n            }\n\n            if let window = window {\n                positionWindow(window, on: selectedScreen, changeAlpha: changeAlpha)\n\n                if vm.notchState == .closed {\n                    vm.close()\n                }\n            }\n        }\n    }\n\n    @objc func togglePopover(_ sender: Any?) {\n        if window?.isVisible == true {\n            window?.orderOut(nil)\n        } else {\n            window?.orderFrontRegardless()\n        }\n    }\n\n    @objc func showMenu() {\n        statusItem?.menu?.popUp(positioning: nil, at: NSEvent.mouseLocation, in: nil)\n    }\n\n    @objc func quitAction() {\n        NSApplication.shared.terminate(self)\n    }\n\n    private func showOnboardingWindow(step: OnboardingStep = .welcome) {\n        if onboardingWindowController == nil {\n            let window = NSWindow(\n                contentRect: NSRect(x: 0, y: 0, width: 400, height: 600),\n                styleMask: [.titled, .fullSizeContentView],\n                backing: .buffered,\n                defer: false\n            )\n            window.center()\n            window.title = \"Onboarding\"\n            window.titlebarAppearsTransparent = true\n            window.titleVisibility = .hidden\n            window.contentView = NSHostingView(\n                rootView: OnboardingView(\n                    step: step,\n                    onFinish: {\n                        window.orderOut(nil)\n//                        NSApp.setActivationPolicy(.accessory)\n                        window.close()\n                        NSApp.deactivate()\n                    },\n                    onOpenSettings: {\n                        window.close()\n                        SettingsWindowController.shared.showWindow()\n                    }\n                ))\n            window.isRestorable = false\n            window.identifier = NSUserInterfaceItemIdentifier(\"OnboardingWindow\")\n\n            onboardingWindowController = NSWindowController(window: window)\n        }\n\n//        NSApp.setActivationPolicy(.regular)\n        NSApp.activate(ignoringOtherApps: true)\n        onboardingWindowController?.window?.makeKeyAndOrderFront(nil)\n        onboardingWindowController?.window?.orderFrontRegardless()\n    }\n}\n\nextension Notification.Name {\n    static let selectedScreenChanged = Notification.Name(\"SelectedScreenChanged\")\n    static let notchHeightChanged = Notification.Name(\"NotchHeightChanged\")\n    static let showOnAllDisplaysChanged = Notification.Name(\"showOnAllDisplaysChanged\")\n    static let automaticallySwitchDisplayChanged = Notification.Name(\"automaticallySwitchDisplayChanged\")\n    static let expandedDragDetectionChanged = Notification.Name(\"expandedDragDetectionChanged\")\n}\n\nextension CGRect: @retroactive Hashable {\n    public func hash(into hasher: inout Hasher) {\n        hasher.combine(origin.x)\n        hasher.combine(origin.y)\n        hasher.combine(size.width)\n        hasher.combine(size.height)\n    }\n\n    public static func == (lhs: CGRect, rhs: CGRect) -> Bool {\n        return lhs.origin == rhs.origin && lhs.size == rhs.size\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/AnimatedFace.swift",
    "content": "//\n//  AnimatedFace.swift\n//\n// Created by Harsh Vardhan  Goswami  on  04/08/24.\n//\n\nimport SwiftUI\n\nstruct MinimalFaceFeatures: View {\n    @State private var isBlinking = false\n    @State var height:CGFloat = 20;\n    @State var width:CGFloat = 30;\n    \n    var body: some View {\n        VStack(spacing: 4) { // Adjusted spacing to fit within 30x30\n            // Eyes\n            HStack(spacing: 4) { // Adjusted spacing to fit within 30x30\n                Eye(isBlinking: $isBlinking)\n                Eye(isBlinking: $isBlinking)\n            }\n            \n            // Nose and mouth combined\n            VStack(spacing: 2) { // Adjusted spacing to fit within 30x30\n                // Nose\n                RoundedRectangle(cornerRadius: 2)\n                    .fill(Color.white)\n                    .frame(width: 3, height: 4)\n                \n                // Mouth (happy)\n                GeometryReader { geometry in\n                    Path { path in\n                        let width = geometry.size.width\n                        let height = geometry.size.height\n                        path.move(to: CGPoint(x: 0, y: height / 2))\n                        path.addQuadCurve(to: CGPoint(x: width, y: height / 2), control: CGPoint(x: width / 2, y: height))\n                    }\n                    .stroke(Color.white, lineWidth: 2)\n                }\n                .frame(width: 14, height: 10)\n            }\n        }\n        .frame(width: self.width, height: self.height) // Maximum size of face\n        .onAppear {\n            startBlinking()\n        }\n    }\n    \n    func startBlinking() {\n        Timer.scheduledTimer(withTimeInterval: 3, repeats: true) { _ in\n            withAnimation(.spring(duration: 0.2)) {\n                isBlinking = true\n            }\n            DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n                withAnimation(.spring(duration: 0.2)) {\n                    isBlinking = false\n                }\n            }\n        }\n    }\n}\n\nstruct Eye: View {\n    @Binding var isBlinking: Bool\n    \n    var body: some View {\n        RoundedRectangle(cornerRadius: 10)\n            .fill(Color.white)\n            .frame(width: 4, height: isBlinking ? 1 : 4)\n            .frame(maxWidth: 15, maxHeight: 15) // Adjusted max size\n            .animation(.easeInOut(duration: 0.1), value: isBlinking)\n    }\n}\n\nstruct MinimalFaceFeatures_Previews: PreviewProvider {\n    static var previews: some View {\n        ZStack {\n            Color.black\n            MinimalFaceFeatures()\n        }\n        .previewLayout(.fixed(width: 60, height: 60)) // Adjusted preview size for better visibility\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/BottomRoundedRectangle.swift",
    "content": "\n\nimport SwiftUI\n\n\nstruct BottomRoundedRectangle: Shape {\n    var radius: CGFloat\n    \n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        \n        // Top left corner\n        path.move(to: CGPoint(x: rect.minX, y: rect.minY))\n        \n        // Top right corner\n        path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))\n        \n        // Bottom right corner (rounded)\n        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - radius))\n        path.addArc(center: CGPoint(x: rect.maxX - radius, y: rect.maxY - radius),\n                    radius: radius,\n                    startAngle: Angle(degrees: 0),\n                    endAngle: Angle(degrees: 90),\n                    clockwise: false)\n        \n        // Bottom left corner (rounded)\n        path.addLine(to: CGPoint(x: rect.minX + radius, y: rect.maxY))\n        path.addArc(center: CGPoint(x: rect.minX + radius, y: rect.maxY - radius),\n                    radius: radius,\n                    startAngle: Angle(degrees: 90),\n                    endAngle: Angle(degrees: 180),\n                    clockwise: false)\n        \n        // Back to top left to close the path\n        path.closeSubpath()\n        \n        return path\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Calendar/BoringCalendar.swift",
    "content": "//\n//  BoringCalendar.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 08/09/24.\n//\n\nimport Defaults\nimport SwiftUI\n\nstruct Config: Equatable {\n    //    var count: Int = 10  // 3 days past + today + 7 days future\n    var past: Int = 7\n    var future: Int = 14\n    var steps: Int = 1  // Each step is one day\n    var spacing: CGFloat = 0\n    var showsText: Bool = true\n    var offset: Int = 2  // Number of dates to the left of the selected date\n}\n\nstruct WheelPicker: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @Binding var selectedDate: Date\n    @State private var scrollPosition: Int?\n    @State private var haptics: Bool = false\n    @State private var byClick: Bool = false\n    let config: Config\n\n    var body: some View {\n        ScrollView(.horizontal, showsIndicators: false) {\n            HStack(spacing: config.spacing) {\n                let spacerNum = config.offset\n                let dateCount = totalDateItems()\n                let totalItems = dateCount + 2 * spacerNum\n                ForEach(0..<totalItems, id: \\.self) { index in\n                    if index < spacerNum || index >= spacerNum + dateCount {\n                        // Leading/trailing spacers sized to match a date cell\n                        Spacer()\n                            .frame(width: 24, height: 24)\n                            .id(index)\n                    } else {\n                        let date = dateForItemIndex(index: index, spacerNum: spacerNum)\n                        let isSelected = Calendar.current.isDate(date, inSameDayAs: selectedDate)\n                        dateButton(date: date, isSelected: isSelected, id: index) {\n                            selectedDate = date\n                            byClick = true\n                            withAnimation {\n                                scrollPosition = index\n                            }\n                            if Defaults[.enableHaptics] {\n                                haptics.toggle()\n                            }\n                        }\n                    }\n                }\n            }\n            .frame(height: 50)\n            .scrollTargetLayout()\n        }\n        .scrollIndicators(.never)\n        .scrollPosition(id: $scrollPosition, anchor: .center)\n        .scrollTargetBehavior(.viewAligned)  // Ensures scroll view snaps the centered view\n        .safeAreaPadding(.horizontal)\n        .sensoryFeedback(.alignment, trigger: haptics)\n        .onChange(of: scrollPosition) { oldValue, newValue in\n            if !byClick {\n                handleScrollChange(newValue: newValue, config: config)\n            } else {\n                byClick = false\n            }\n        }\n        .onAppear {\n            scrollToToday(config: config)\n        }\n        // When parent updates the bound selectedDate (e.g., view reopen), center the wheel on it\n        .onChange(of: selectedDate) { _, newValue in\n            let targetIndex = indexForDate(newValue)\n            if scrollPosition != targetIndex {\n                byClick = true\n                withAnimation {\n                    scrollPosition = targetIndex\n                }\n            }\n        }\n    }\n\n    private func dateButton(\n        date: Date, isSelected: Bool, id: Int, onClick: @escaping () -> Void\n    ) -> some View {\n        let isToday = Calendar.current.isDateInToday(date)\n        return Button(action: onClick) {\n            VStack(spacing: 8) {\n                dayText(date: dateToString(for: date), isToday: isToday, isSelected: isSelected)\n                dateCircle(date: date, isToday: isToday, isSelected: isSelected)\n            }\n            .padding(.vertical, 4)\n            .padding(.horizontal, 4)\n            .background(isSelected ? Color.effectiveAccentBackground : Color.clear)\n            .cornerRadius(8)\n        }\n        .buttonStyle(PlainButtonStyle())\n        .id(id)\n    }\n\n    private func dayText(date: String, isToday: Bool, isSelected: Bool) -> some View {\n        Text(date)\n            .font(.caption)\n            .foregroundColor(isSelected ? .white : Color(white: 0.65))\n    }\n\n    private func dateCircle(date: Date, isToday: Bool, isSelected: Bool) -> some View {\n        ZStack {\n            Circle()\n                .fill(isToday ? Color.effectiveAccent : .clear)\n                .frame(width: 20, height: 20)\n                .overlay(\n                    Circle()\n                        .stroke(Color.gray.opacity(0.3), lineWidth: 0)\n                )\n            Text(\"\\(date.date)\")\n                .font(.body)\n                .fontWeight(.medium)\n                .foregroundColor(isSelected ? .white : Color(white: isToday ? 0.9 : 0.65))\n        }\n    }\n\n    func handleScrollChange(newValue: Int?, config: Config) {\n        guard let newIndex = newValue else { return }\n        let spacerNum = config.offset\n        let dateCount = totalDateItems()\n        guard (spacerNum..<(spacerNum + dateCount)).contains(newIndex) else { return }\n        let date = dateForItemIndex(index: newIndex, spacerNum: spacerNum)\n        if !Calendar.current.isDate(date, inSameDayAs: selectedDate) {\n            selectedDate = date\n            if Defaults[.enableHaptics] {\n                haptics.toggle()\n            }\n        }\n    }\n\n    private func scrollToToday(config: Config) {\n        let today = Date()\n        byClick = true\n        scrollPosition = indexForDate(today)\n        selectedDate = today\n    }\n\n    // MARK: - Index/Date mapping with steps and spacers\n    private func indexForDate(_ date: Date) -> Int {\n        let spacerNum = config.offset\n        let cal = Calendar.current\n        let today = cal.startOfDay(for: Date())\n        let startDate = cal.startOfDay(for: cal.date(byAdding: .day, value: -config.past, to: today) ?? today)\n        let target = cal.startOfDay(for: date)\n        let days = cal.dateComponents([.day], from: startDate, to: target).day ?? 0\n        let stepIndex = max(0, min(days / max(config.steps, 1), totalDateItems() - 1))\n        return spacerNum + stepIndex\n    }\n\n    private func dateForItemIndex(index: Int, spacerNum: Int) -> Date {\n        let cal = Calendar.current\n        let today = cal.startOfDay(for: Date())\n        let startDate = cal.date(byAdding: .day, value: -config.past, to: today) ?? today\n        let stepIndex = index - spacerNum\n        return cal.date(byAdding: .day, value: stepIndex * max(config.steps, 1), to: startDate) ?? today\n    }\n\n    private func totalDateItems() -> Int {\n        let range = config.past + config.future\n        let step = max(config.steps, 1)\n        return Int(ceil(Double(range) / Double(step))) + 1\n    }\n\n    private func dateToString(for date: Date) -> String {\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"E\"\n        return formatter.string(from: date)\n    }\n}\n\nstruct CalendarView: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject private var calendarManager = CalendarManager.shared\n    @State private var selectedDate = Date()\n\n    var body: some View {\n        VStack(spacing: 0) {\n            HStack(alignment: .top, spacing: 8) {\n                VStack(alignment: .leading) {\n                    Text(selectedDate.formatted(.dateTime.month(.abbreviated)))\n                        .font(.title3)\n                        .fontWeight(.semibold)\n                        .foregroundColor(.white)\n                    Text(selectedDate.formatted(.dateTime.year()))\n                        .font(.title3)\n                        .fontWeight(.light)\n                        .foregroundColor(Color(white: 0.65))\n                }\n\n                ZStack(alignment: .top) {\n                    WheelPicker(selectedDate: $selectedDate, config: Config())\n                    HStack(alignment: .top) {\n                        LinearGradient(\n                            colors: [Color.black, .clear], startPoint: .leading, endPoint: .trailing\n                        )\n                        .frame(width: 20)\n                        Spacer()\n                        LinearGradient(\n                            colors: [.clear, Color.black], startPoint: .leading, endPoint: .trailing\n                        )\n                        .frame(width: 20)\n                    }\n                }\n            }\n\n            let filteredEvents = EventListView.filteredEvents(\n                events: calendarManager.events\n            )\n            if filteredEvents.isEmpty {\n                EmptyEventsView(selectedDate: selectedDate)\n                Spacer(minLength: 0)\n            } else {\n                EventListView(events: calendarManager.events)\n            }\n        }\n        .listRowBackground(Color.clear)\n        .frame(height: 120)\n        .onChange(of: selectedDate) {\n            Task {\n                await calendarManager.updateCurrentDate(selectedDate)\n            }\n        }\n        .onChange(of: vm.notchState) { _, _ in\n            Task {\n                await calendarManager.updateCurrentDate(Date.now)\n                selectedDate = Date.now\n            }\n        }\n        .onAppear {\n            Task {\n                await calendarManager.updateCurrentDate(Date.now)\n                selectedDate = Date.now\n            }\n        }\n    }\n}\n\nstruct EmptyEventsView: View {\n    let selectedDate: Date\n    \n    var body: some View {\n        VStack {\n            Image(systemName: \"calendar.badge.checkmark\")\n                .font(.title)\n                .foregroundColor(Color(white: 0.65))\n            Text(Calendar.current.isDateInToday(selectedDate) ? \"No events today\" : \"No events\")\n                .font(.subheadline)\n                .foregroundColor(.white)\n            Text(\"Enjoy your free time!\")\n                .font(.caption)\n                .foregroundColor(Color(white: 0.65))\n        }\n    }\n}\n\nstruct EventListView: View {\n    @Environment(\\.openURL) private var openURL\n    @ObservedObject private var calendarManager = CalendarManager.shared\n    let events: [EventModel]\n    @Default(.autoScrollToNextEvent) private var autoScrollToNextEvent\n    @Default(.showFullEventTitles) private var showFullEventTitles\n\n\n    static func filteredEvents(events: [EventModel]) -> [EventModel] {\n        events.filter { event in\n            if event.type.isReminder {\n                if case .reminder(let completed) = event.type {\n                    return !completed || !Defaults[.hideCompletedReminders]\n                }\n            }\n            // Filter out all-day events if setting is enabled\n            if event.isAllDay && Defaults[.hideAllDayEvents] {\n                return false\n            }\n            return true\n        }\n    }\n\n    private var filteredEvents: [EventModel] {\n        Self.filteredEvents(events: events)\n    }\n\n    private func scrollToRelevantEvent(proxy: ScrollViewProxy) {\n        let now = Date()\n        // Determine a single target using preferred search order:\n        // 1) first non-all-day upcoming/in-progress event\n        // 2) first all-day event\n        // 3) last event (fallback)\n        let nonAllDayUpcoming = filteredEvents.first(where: { !$0.isAllDay && $0.end > now })\n        let firstAllDay = filteredEvents.first(where: { $0.isAllDay })\n        let lastEvent = filteredEvents.last\n        guard let target = nonAllDayUpcoming ?? firstAllDay ?? lastEvent else { return }\n\n        Task { @MainActor in\n            withTransaction(Transaction(animation: nil)) {\n                proxy.scrollTo(target.id, anchor: .top)\n            }\n        }\n    }\n\n    var body: some View {\n        ScrollViewReader { proxy in\n            List {\n                ForEach(filteredEvents) { event in\n                    Button(action: {\n                        if let url = event.calendarAppURL() {\n                            openURL(url)\n                        }\n                    }) {\n                        eventRow(event)\n                    }\n                    .id(event.id)\n                    .padding(.leading, -5)\n                    .buttonStyle(PlainButtonStyle())\n                    .listRowSeparator(.automatic)\n                    .listRowSeparatorTint(.gray.opacity(0.2))\n                    .listRowBackground(Color.clear)\n                }\n            }\n            .listStyle(.plain)\n            .scrollIndicators(.never)\n            .scrollContentBackground(.hidden)\n            .background(Color.clear)\n            .onAppear {\n                scrollToRelevantEvent(proxy: proxy)\n            }\n            .onChange(of: filteredEvents) { _, _ in\n                scrollToRelevantEvent(proxy: proxy)\n            }\n        }\n        Spacer(minLength: 0)\n    }\n\n    private func eventRow(_ event: EventModel) -> some View {\n        if event.type.isReminder {\n            let isCompleted: Bool\n            if case .reminder(let completed) = event.type {\n                isCompleted = completed\n            } else {\n                isCompleted = false\n            }\n            return AnyView(\n                HStack(spacing: 8) {\n                    ReminderToggle(\n                        isOn: Binding(\n                            get: { isCompleted },\n                            set: { newValue in\n                                Task {\n                                    await calendarManager.setReminderCompleted(\n                                        reminderID: event.id, completed: newValue\n                                    )\n                                }\n                            }\n                        ),\n                        color: Color(event.calendar.color)\n                    )\n                    .opacity(1.0)  // Ensure the toggle is always fully opaque\n                    HStack {\n                        Text(event.title)\n                            .font(.callout)\n                            .foregroundColor(.white)\n                            .lineLimit(showFullEventTitles ? nil : 1)\n                        Spacer(minLength: 0)\n                        VStack(alignment: .trailing, spacing: 4) {\n                            if event.isAllDay {\n                                Text(\"All-day\")\n                                    .font(.caption)\n                                    .fontWeight(.medium)\n                                    .foregroundColor(.white)\n                                    .lineLimit(1)\n                            } else {\n                                Text(event.start, style: .time)\n                                    .foregroundColor(.white)\n                                    .font(.caption)\n                            }\n                        }\n                    }\n                    .opacity(\n                        isCompleted\n                            ? 0.4\n                            : event.start < Date.now && Calendar.current.isDateInToday(event.start)\n                                ? 0.6 : 1.0\n                    )\n                }\n                .padding(.vertical, 4)\n            )\n        } else {\n            return AnyView(\n                HStack(alignment: .top, spacing: 4) {\n                    Rectangle()\n                        .fill(Color(event.calendar.color))\n                        .frame(width: 3)\n                        .cornerRadius(1.5)\n\n                    VStack(alignment: .leading, spacing: 2) {\n                        Text(event.title)\n                            .font(.callout)\n                            .fontWeight(.medium)\n                            .foregroundColor(.white)\n                            .lineLimit(showFullEventTitles ? nil : 2)\n\n                        if let location = event.location, !location.isEmpty {\n                            Text(location)\n                                .font(.caption)\n                                .foregroundColor(Color(white: 0.65))\n                                .lineLimit(1)\n                        }\n                    }\n                    Spacer(minLength: 0)\n                    VStack(alignment: .trailing, spacing: 4) {\n                        if event.isAllDay {\n                            Text(\"All-day\")\n                                .font(.caption)\n                                .fontWeight(.medium)\n                                .foregroundColor(.white)\n                                .lineLimit(1)\n                        } else {\n                            Text(event.start, style: .time)\n                                .foregroundColor(.white)\n                            Text(event.end, style: .time)\n                                .foregroundColor(Color(white: 0.65))\n                        }\n                    }\n                    .font(.caption)\n                    .frame(minWidth: 44, alignment: .trailing)\n                }\n                .opacity(\n                    event.eventStatus == .ended && Calendar.current.isDateInToday(event.start)\n                        ? 0.6 : 1.0)\n            )\n        }\n    }\n}\n\nstruct ReminderToggle: View {\n    @Binding var isOn: Bool\n    var color: Color\n\n    var body: some View {\n        Button(action: {\n            isOn.toggle()\n        }) {\n            ZStack {\n                // Outer ring\n                Circle()\n                    .strokeBorder(color, lineWidth: 2)\n                    .frame(width: 14, height: 14)\n                // Inner fill\n                if isOn {\n                    Circle()\n                        .fill(color)\n                        .frame(width: 8, height: 8)\n                }\n                Circle()\n                    .fill(Color.black.opacity(0.001))\n                    .frame(width: 14, height: 14)\n            }\n        }\n        .buttonStyle(PlainButtonStyle())\n        .padding(0)\n        .accessibilityLabel(isOn ? \"Mark as incomplete\" : \"Mark as complete\")\n    }\n}\n\n#Preview {\n    CalendarView()\n        .frame(width: 215, height: 130)\n        .background(.black)\n        .environmentObject(BoringViewModel())\n}\n"
  },
  {
    "path": "boringNotch/components/EmptyState.swift",
    "content": "//\n//  EmptyState.swift\n//\n// Created by Harsh Vardhan  Goswami  on  04/08/24.\n//\n\nimport SwiftUI\n\nstruct EmptyStateView: View {\n    var message: String\n    @State private var isVisible = true\n    \n    var body: some View {\n        HStack {\n            MinimalFaceFeatures(\n                height: 70, width: 80)\n            Text(message)\n                .font(.system(size:14))\n                .foregroundColor(.gray)\n        }.transition(.blurReplace.animation(.spring(.bouncy(duration: 0.3)))) // Smooth animation\n    }\n}\n\n#Preview {\n    EmptyStateView(message: \"Play some music babies\")\n}\n"
  },
  {
    "path": "boringNotch/components/HoverButton.swift",
    "content": "//\n//  HoverButton.swift\n//  boringNotch\n//\n//  Created by Kraigo on 04.09.2024.\n//\n\nimport SwiftUI\n\nstruct HoverButton: View {\n    var icon: String\n    var iconColor: Color = .primary\n    var scale: Image.Scale = .medium\n    var action: () -> Void\n    var contentTransition: ContentTransition = .symbolEffect;\n    \n    @State private var isHovering = false\n\n    var body: some View {\n        let size = CGFloat(scale == .large ? 40 : 30)\n        \n        Button(action: action) {\n            Rectangle()\n                .fill(.clear)\n                .contentShape(Rectangle())\n                .frame(width: size, height: size)\n                .overlay {\n                    Capsule()\n                        .fill(isHovering ? Color.gray.opacity(0.2) : .clear)\n                        .frame(width: size, height: size)\n                        .overlay {\n                            Image(systemName: icon)\n                                .foregroundColor(iconColor)\n                                .contentTransition(contentTransition)\n                                .font(scale == .large ? .largeTitle : .body)\n                        }\n                }\n        }\n        .buttonStyle(PlainButtonStyle())\n        .onHover { hovering in\n            withAnimation(.smooth(duration: 0.3)) {\n                isHovering = hovering\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/BoringBattery.swift",
    "content": "import SwiftUI\nimport Defaults\n\n/// A view that displays the battery status with an icon and charging indicator.\nstruct BatteryView: View {\n\n    var levelBattery: Float\n    var isPluggedIn: Bool\n    var isCharging: Bool\n    var isInLowPowerMode: Bool\n    var batteryWidth: CGFloat = 26\n    var isForNotification: Bool\n\n    var icon: String = \"battery.0\"\n\n    /// Determines the icon to display when charging.\n    var iconStatus: String {\n        if isCharging {\n            return \"bolt\"\n        }\n        else if isPluggedIn {\n            return \"plug\"\n        }\n        else {\n            return \"\"\n        }\n    }\n\n    /// Determines the color of the battery based on its status.\n    var batteryColor: Color {\n        if isInLowPowerMode {\n            return .yellow\n        } else if levelBattery <= 20 && !isCharging && !isPluggedIn {\n            return .red\n        } else if isCharging || isPluggedIn || levelBattery == 100 {\n            return .green\n        } else {\n            return .white\n        }\n    }\n\n    var body: some View {\n        ZStack(alignment: .leading) {\n\n            Image(systemName: icon)\n                .resizable()\n                .fontWeight(.thin)\n                .aspectRatio(contentMode: .fit)\n                .foregroundColor(.white.opacity(0.5))\n                .frame(\n                    width: batteryWidth + 1\n                )\n\n            RoundedRectangle(cornerRadius: 2.5)\n                .fill(batteryColor)\n                .frame(\n                    width: CGFloat(((CGFloat(CFloat(levelBattery)) / 100) * (batteryWidth - 6))),\n                    height: (batteryWidth - 2.75) - 18\n                )\n                .padding(.leading, 2)\n\n            if iconStatus != \"\" && (isForNotification || Defaults[.showPowerStatusIcons]) {\n                ZStack {\n                    Image(iconStatus)\n                        .resizable()\n                        .aspectRatio(contentMode: .fit)\n                        .foregroundColor(.white)\n                        .frame(\n                            width: 17,\n                            height: 17\n                        )\n                }\n                .frame(width: batteryWidth, height: batteryWidth)\n            }\n        }\n    }\n}\n\nstruct ScaleButtonStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .scaleEffect(configuration.isPressed ? 0.95 : 1.0)\n            .animation(.easeInOut(duration: 0.2), value: configuration.isPressed)\n    }\n}\n\n/// A view that displays detailed battery information and settings.\nstruct BatteryMenuView: View {\n    \n    var isPluggedIn: Bool\n    var isCharging: Bool\n    var levelBattery: Float\n    var maxCapacity: Float\n    var timeToFullCharge: Int\n    var isInLowPowerMode: Bool\n    var onDismiss: () -> Void\n\n    @Environment(\\.openURL) private var openURL\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 16) {\n\n            HStack {\n                Text(\"Battery Status\")\n                    .font(.headline)\n                    .fontWeight(.semibold)\n                Spacer()\n                Text(\"\\(Int(levelBattery))%\")\n                    .font(.headline)\n                    .fontWeight(.semibold)\n            }\n            \n            VStack(alignment: .leading, spacing: 8) {\n                Text(\"Max Capacity: \\(Int(maxCapacity))%\")\n                    .font(.subheadline)\n                    .fontWeight(.regular)\n                if isInLowPowerMode {\n                    Label(\"Low Power Mode\", systemImage: \"bolt.circle\")\n                        .font(.subheadline)\n                        .fontWeight(.regular)\n                }\n                if isCharging {\n                    Label(\"Charging\", systemImage: \"bolt.fill\")\n                        .font(.subheadline)\n                        .fontWeight(.regular)\n                }\n                if isPluggedIn {\n                    Label(\"Plugged In\", systemImage: \"powerplug.fill\")\n                        .font(.subheadline)\n                        .fontWeight(.regular)\n                }\n                if timeToFullCharge > 0 {\n                    Label(\"Time to Full Charge: \\(timeToFullCharge) min\", systemImage: \"clock\")\n                        .font(.subheadline)\n                        .fontWeight(.regular)\n                }\n                if !isCharging && isPluggedIn && levelBattery >= 80 {\n                    Label(\"Charging on Hold: Desktop Mode\", systemImage: \"desktopcomputer\")\n                        .font(.subheadline)\n                        .fontWeight(.regular)\n                }\n                    \n            }\n            .padding(.vertical, 8)\n\n            Divider().background(Color.white)\n\n            Button(action: openBatteryPreferences) {\n                Label(\"Battery Settings\", systemImage: \"gearshape\")\n                    .fontWeight(.regular)\n            }\n            .frame(maxWidth: .infinity)\n            .buttonStyle(.plain)\n            .padding(.vertical, 8)\n        }\n        .padding()\n        .frame(width: 280)\n        .foregroundColor(.white)\n    }\n\n    private func openBatteryPreferences() {\n        if let url = URL(string: \"x-apple.systempreferences:com.apple.preference.battery\") {\n            openURL(url)\n            onDismiss()\n        }\n    }\n}\n\n/// A view that displays the battery status and allows interaction to show detailed information.\nstruct BoringBatteryView: View {\n    \n    @State var batteryWidth: CGFloat = 26\n    var isCharging: Bool = false\n    var isInLowPowerMode: Bool = false\n    var isPluggedIn: Bool = false\n    var levelBattery: Float = 0\n    var maxCapacity: Float = 0\n    var timeToFullCharge: Int = 0\n    @State var isForNotification: Bool = false\n    \n    @State private var showPopupMenu: Bool = false\n    @State private var isPressed: Bool = false\n    @State private var isHoveringButton: Bool = false\n    @State private var isHoveringPopover: Bool = false\n    @State private var hideTask: Task<Void, Never>? = nil\n\n    @EnvironmentObject var vm: BoringViewModel\n\n    var body: some View {\n        Button(action: {\n            withAnimation {\n                showPopupMenu.toggle()\n            }\n        }) {\n            HStack {\n                if Defaults[.showBatteryPercentage] {\n                    Text(\"\\(Int32(levelBattery))%\")\n                        .font(.callout)\n                        .foregroundStyle(.white)\n                }\n                BatteryView(\n                    levelBattery: levelBattery,\n                    isPluggedIn: isPluggedIn,\n                    isCharging: isCharging,\n                    isInLowPowerMode: isInLowPowerMode,\n                    batteryWidth: batteryWidth,\n                    isForNotification: isForNotification\n                )\n            }\n        }\n        .buttonStyle(ScaleButtonStyle())\n        .popover(\n            isPresented: $showPopupMenu,\n            arrowEdge: .bottom) {\n            BatteryMenuView(\n                isPluggedIn: isPluggedIn,\n                isCharging: isCharging,\n                levelBattery: levelBattery,\n                maxCapacity: maxCapacity,\n                timeToFullCharge: timeToFullCharge,\n                isInLowPowerMode: isInLowPowerMode,\n                onDismiss: { \n                    showPopupMenu = false\n                }\n            )\n            .onHover { hovering in\n                isHoveringPopover = hovering\n                if hovering {\n                    hideTask?.cancel()\n                    hideTask = nil\n                } else {\n                    scheduleHideIfNeeded()\n                }\n            }\n        }\n        .onChange(of: showPopupMenu) {\n            vm.isBatteryPopoverActive = showPopupMenu\n        }\n        .onDisappear {\n            hideTask?.cancel()\n            hideTask = nil\n        }\n    }\n\n    private func scheduleHideIfNeeded() {\n        if isHoveringButton || isHoveringPopover { return }\n        hideTask?.cancel()\n        hideTask = Task {\n            try? await Task.sleep(for: .milliseconds(350))\n            guard !Task.isCancelled else { return }\n            await MainActor.run { withAnimation { showPopupMenu = false } }\n        }\n    }\n}\n\n#Preview {\n    BoringBatteryView(\n        batteryWidth: 30,\n        isCharging: false,\n        isInLowPowerMode: false,\n        isPluggedIn: true,\n        levelBattery: 80,\n        maxCapacity: 100,\n        timeToFullCharge: 10,\n        isForNotification: false\n    ).frame(width: 200, height: 200)\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/DownloadView.swift",
    "content": "//\n//  DownloadView.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 17/08/24.\n//\n\nimport Foundation\nimport SwiftUI\n\nenum Browser {\n    case safari\n    case chrome\n}\n\nstruct DownloadFile {\n    var name: String\n    var size: Int\n    var formattedSize: String\n    var browser: Browser\n}\n\nclass DownloadWatcher: ObservableObject {\n    @Published var downloadFiles: [DownloadFile] = []\n}\n\nstruct DownloadArea: View {\n    @EnvironmentObject var watcher: DownloadWatcher\n\n    var body: some View {\n        HStack(alignment: .center) {\n            HStack {\n                if watcher.downloadFiles.first!.browser == .safari {\n                    AppIcon(for: \"com.apple.safari\")\n                } else {\n                    Image(.chrome).resizable().scaledToFit().frame(width: 30, height: 30)\n                }\n                VStack(alignment: .leading) {\n                    Text(\"Download\")\n                    Text(\"In progress\").font(.system(.footnote)).foregroundStyle(.gray)\n                }\n            }\n            Spacer()\n            HStack(spacing: 12) {\n                VStack(alignment: .trailing) {\n                    Text(watcher.downloadFiles.first!.formattedSize)\n                    Text(watcher.downloadFiles.first!.name).font(.caption2).foregroundStyle(.gray)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/InlineHUD.swift",
    "content": "//\n//  InlineHUDs.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 14/09/2024.\n//\n\nimport SwiftUI\nimport Defaults\n\nstruct InlineHUD: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @Binding var type: SneakContentType\n    @Binding var value: CGFloat\n    @Binding var icon: String\n    @Binding var hoverAnimation: Bool\n    @Binding var gestureProgress: CGFloat\n    var body: some View {\n        HStack {\n            HStack(spacing: 5) {\n                Group {\n                    switch (type) {\n                        case .volume:\n                            if icon.isEmpty {\n                                Image(systemName: SpeakerSymbol(value))\n                                    .contentTransition(.interpolate)\n                                    .symbolVariant(value > 0 ? .none : .slash)\n                                    .frame(width: 20, height: 15, alignment: .leading)\n                            } else {\n                                Image(systemName: icon)\n                                    .contentTransition(.interpolate)\n                                    .opacity(value.isZero ? 0.6 : 1)\n                                    .scaleEffect(value.isZero ? 0.85 : 1)\n                                    .frame(width: 20, height: 15, alignment: .leading)\n                            }\n                        case .brightness:\n                            Image(systemName: BrightnessSymbol(value))\n                                .contentTransition(.interpolate)\n                                .frame(width: 20, height: 15, alignment: .center)\n                        case .backlight:\n                            Image(systemName: value > 0.5 ? \"light.max\" : \"light.min\")\n                                .contentTransition(.interpolate)\n                                .frame(width: 20, height: 15, alignment: .center)\n                        case .mic:\n                            Image(systemName: \"mic\")\n                                .symbolRenderingMode(.hierarchical)\n                                .symbolVariant(value > 0 ? .none : .slash)\n                                .contentTransition(.interpolate)\n                                .frame(width: 20, height: 15, alignment: .center)\n                        default:\n                            EmptyView()\n                    }\n                }\n                .foregroundStyle(.white)\n                .symbolVariant(.fill)\n                \n                Text(Type2Name(type))\n                    .font(.subheadline)\n                    .fontWeight(.medium)\n                    .lineLimit(1)\n                    .allowsTightening(true)\n                    .contentTransition(.numericText())\n            }\n            .frame(width: 100 - (hoverAnimation ? 0 : 12) + gestureProgress / 2, height: vm.notchSize.height - (hoverAnimation ? 0 : 12), alignment: .leading)\n            \n            Rectangle()\n                .fill(.black)\n                .frame(width: vm.closedNotchSize.width - 20)\n            \n            HStack {\n                if (type == .mic) {\n                    Text(value.isZero ? \"muted\" : \"unmuted\")\n                        .foregroundStyle(.gray)\n                        .lineLimit(1)\n                        .allowsTightening(true)\n                        .multilineTextAlignment(.trailing)\n                        .frame(maxWidth: .infinity, alignment: .trailing)\n                        .contentTransition(.interpolate)\n                } else {\n                        HStack {\n                        DraggableProgressBar(value: $value, onChange: { v in\n                            if type == .volume {\n                                VolumeManager.shared.setAbsolute(Float32(v))\n                            } else if type == .brightness {\n                                BrightnessManager.shared.setAbsolute(value: Float32(v))\n                            }\n                        })\n                        if (type == .volume && value.isZero) {\n                            Text(\"muted\")\n                                .font(.caption)\n                                .fontWeight(.medium)\n                                .foregroundStyle(.gray)\n                                .lineLimit(1)\n                                .allowsTightening(true)\n                                .multilineTextAlignment(.trailing)\n                        } else if Defaults[.showClosedNotchHUDPercentage] {\n                            Text(\"\\(Int(value * 100))%\")\n                                .font(.caption)\n                                .fontWeight(.medium)\n                                .foregroundStyle(.gray)\n                                .lineLimit(1)\n                                .allowsTightening(true)\n                                .multilineTextAlignment(.trailing)\n                        }\n                    }\n                }\n            }\n            .padding(.trailing, 4)\n            .frame(width: 100 - (hoverAnimation ? 0 : 12) + gestureProgress / 2, height: vm.closedNotchSize.height - (hoverAnimation ? 0 : 12), alignment: .center)\n        }\n        .frame(height: vm.closedNotchSize.height + (hoverAnimation ? 8 : 0), alignment: .center)\n    }\n    \n    func SpeakerSymbol(_ value: CGFloat) -> String {\n        switch(value) {\n            case 0:\n                return \"speaker\"\n            case 0...0.3:\n                return \"speaker.wave.1\"\n            case 0.3...0.8:\n                return \"speaker.wave.2\"\n            case 0.8...1:\n                return \"speaker.wave.3\"\n            default:\n                return \"speaker.wave.2\"\n        }\n    }\n    \n    func BrightnessSymbol(_ value: CGFloat) -> String {\n        switch(value) {\n            case 0...0.6:\n                return \"sun.min\"\n            case 0.6...1:\n                return \"sun.max\"\n            default:\n                return \"sun.min\"\n        }\n    }\n    \n    func Type2Name(_ type: SneakContentType) -> String {\n        switch(type) {\n            case .volume:\n                return \"Volume\"\n            case .brightness:\n                return \"Brightness\"\n            case .backlight:\n                return \"Backlight\"\n            case .mic:\n                return \"Mic\"\n            default:\n                return \"\"\n        }\n    }\n}\n\n#Preview {\n    InlineHUD(type: .constant(.brightness), value: .constant(0.4), icon: .constant(\"\"), hoverAnimation: .constant(false), gestureProgress: .constant(0))\n        .padding(.horizontal, 8)\n        .background(Color.black)\n        .padding()\n        .environmentObject(BoringViewModel())\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/LiveActivityModifier.swift",
    "content": "//\n//  LiveActivityModifier.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 12/08/2024.\n//\n\nimport SwiftUI\n\nenum ActivityType {\n    case mediaPlayback\n    case charging\n    case download\n}\n\nstruct LiveActivityModifier<Left: View, Right: View>: ViewModifier {\n    let `for`: ActivityType\n    let leftContent: () -> Left\n    let rightContent: () -> Right\n    \n    func body(content: Content) -> some View {\n        content\n            .overlay(\n                HStack {\n                    leftContent()\n                    Spacer()\n                        //.frame(minWidth: vm.closedNotchSize.width)\n                    rightContent()\n                }\n                .padding()\n            )\n    }\n}\n\nextension View {\n    func liveActivity<Left: View, Right: View>(\n        for activityId: ActivityType,\n        @ViewBuilder left: @escaping () -> Left,\n        @ViewBuilder right: @escaping () -> Right\n    ) -> some View {\n        self.modifier(LiveActivityModifier(for: activityId, leftContent: left, rightContent: right))\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/MarqueeTextView.swift",
    "content": "//\n//  MarqueeTextView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 08/08/2024.\n//\n\nimport SwiftUI\n\nstruct SizePreferenceKey: PreferenceKey {\n    static var defaultValue: CGSize = .zero\n    static func reduce(value: inout CGSize, nextValue: () -> CGSize) {\n        value = nextValue()\n    }\n}\n\nstruct MeasureSizeModifier: ViewModifier {\n    func body(content: Content) -> some View {\n        content.background(GeometryReader { geometry in\n            Color.clear.preference(key: SizePreferenceKey.self, value: geometry.size)\n        })\n    }\n}\n\nstruct MarqueeText: View {\n    @Binding var text: String\n    let font: Font\n    let nsFont: NSFont.TextStyle\n    let textColor: Color\n    let backgroundColor: Color\n    let minDuration: Double\n    let frameWidth: CGFloat\n    \n    @State private var animate = false\n    @State private var textSize: CGSize = .zero\n    @State private var offset: CGFloat = 0\n    \n    init(_ text: Binding<String>, font: Font = .body, nsFont: NSFont.TextStyle = .body, textColor: Color = .primary, backgroundColor: Color = .clear, minDuration: Double = 3.0, frameWidth: CGFloat = 200) {\n        _text = text\n        self.font = font\n        self.nsFont = nsFont\n        self.textColor = textColor\n        self.backgroundColor = backgroundColor\n        self.minDuration = minDuration\n        self.frameWidth = frameWidth\n    }\n    \n    private var needsScrolling: Bool {\n        textSize.width > frameWidth\n    }\n    \n    var body: some View {\n        GeometryReader { geometry in\n            ZStack(alignment: .leading) {\n                HStack(spacing: 20) {\n                    Text(text)\n                    Text(text)\n                        .opacity(needsScrolling ? 1 : 0)\n                }\n                .id(text)\n                .font(font)\n                .foregroundColor(textColor)\n                .fixedSize(horizontal: true, vertical: false)\n                .offset(x: self.animate ? offset : 0)\n                .animation(\n                    self.animate ?\n                        .linear(duration: Double(textSize.width / 30))\n                        .delay(minDuration)\n                        .repeatForever(autoreverses: false) : .none,\n                    value: self.animate\n                )\n                .background(backgroundColor)\n                .modifier(MeasureSizeModifier())\n                .onPreferenceChange(SizePreferenceKey.self) { size in\n                    self.textSize = CGSize(width: size.width / 2, height: NSFont.preferredFont(forTextStyle: nsFont).pointSize)\n                    self.animate = false\n                    self.offset = 0\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.01){\n                        if needsScrolling {\n                            self.animate = true\n                            self.offset = -(textSize.width + 10)\n                            \n                        }\n                    }\n                }\n            }\n            .frame(width: frameWidth, alignment: .leading)\n            .clipped()\n        }\n        .frame(height: textSize.height * 1.3)\n        \n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/OpenNotchHUD.swift",
    "content": "//\n//  OpenNotchHUD.swift\n//  boringNotch\n//\n//  Created by Alexander on 2024-11-23.\n//\n\nimport SwiftUI\nimport Defaults\n\nstruct OpenNotchHUD: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @Binding var type: SneakContentType\n    @Binding var value: CGFloat\n    @Binding var icon: String\n    @Default(.showOpenNotchHUDPercentage) var showPercentage\n    \n    var body: some View {\n        HStack(spacing: 8) {\n            // Icon\n            Group {\n                switch type {\n                case .volume:\n                    if icon.isEmpty {\n                        Image(systemName: SpeakerSymbol(value))\n                            .contentTransition(.interpolate)\n                    } else {\n                        Image(systemName: icon)\n                            .contentTransition(.interpolate)\n                    }\n                case .brightness:\n                    Image(systemName: \"sun.max.fill\")\n                        .contentTransition(.symbolEffect)\n                case .backlight:\n                    Image(systemName: value > 0.5 ? \"light.max\" : \"light.min\")\n                        .contentTransition(.interpolate)\n                case .mic:\n                    Image(systemName: \"mic\")\n                        .symbolVariant(value > 0 ? .none : .slash)\n                        .contentTransition(.interpolate)\n                default:\n                    EmptyView()\n                }\n            }\n            .font(.system(size: 14, weight: .medium))\n            .foregroundStyle(.white)\n            .frame(width: 20, alignment: .center)\n            \n            // Slider or Status Text\n            if type != .mic {\n                DraggableProgressBar(value: $value, onChange: { newVal in\n                     updateSystemValue(newVal)\n                })\n                .frame(width: showPercentage ? 65 : 108) // Fixed width for consistency\n            } else {\n                Text(value > 0 ? \"Unmuted\" : \"Muted\")\n                    .font(.system(size: 13, weight: .medium))\n                    .foregroundStyle(.white)\n                    .fixedSize()\n            }\n            \n            // Percentage Text\n            if type != .mic && showPercentage {\n                Text(\"\\(Int(value * 100))%\")\n                    .font(.system(size: 12, weight: .medium))\n                    .foregroundStyle(.gray)\n                    .monospacedDigit()\n                    .frame(width: 35, alignment: .trailing)\n            }\n        }\n        .padding(.horizontal, 10)\n        .padding(.vertical, 6)\n        .background(\n            Capsule()\n                .fill(Color.black)\n                .stroke(Color.white.opacity(0.1), lineWidth: 1)\n        )\n    }\n    \n    func SpeakerSymbol(_ value: CGFloat) -> String {\n        switch(value) {\n            case 0: return \"speaker.slash\"\n            case 0...0.33: return \"speaker.wave.1\"\n            case 0.33...0.66: return \"speaker.wave.2\"\n            default: return \"speaker.wave.3\"\n        }\n    }\n    \n    func updateSystemValue(_ newVal: CGFloat) {\n        switch type {\n        case .volume:\n            VolumeManager.shared.setAbsolute(Float32(newVal))\n        case .brightness:\n            BrightnessManager.shared.setAbsolute(value: Float32(newVal))\n        default:\n            break\n        }\n    }\n}\n\n#Preview {\n    OpenNotchHUD(type: .constant(.volume), value: .constant(0.5), icon: .constant(\"\"))\n        .environmentObject(BoringViewModel())\n        .padding()\n        .background(Color.gray)\n}\n"
  },
  {
    "path": "boringNotch/components/Live activities/SystemEventIndicatorModifier.swift",
    "content": "    //\n    //  SystemEventIndicatorModifier.swift\n    //  boringNotch\n    //\n    //  Created by Richard Kunkli on 12/08/2024.\n    //\n\nimport SwiftUI\nimport Defaults\n\nstruct SystemEventIndicatorModifier: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @Binding var eventType: SneakContentType\n    @Binding var value: CGFloat {\n        didSet {\n            DispatchQueue.main.async {\n                self.sendEventBack(value)\n                self.vm.objectWillChange.send()\n            }\n        }\n    }\n    @Binding var icon: String\n    let showSlider: Bool = false\n    var sendEventBack: (CGFloat) -> Void\n    \n    var body: some View {\n        HStack(spacing: 14) {\n            switch (eventType) {\n                case .volume:\n                    if icon.isEmpty {\n                        Image(systemName: SpeakerSymbol(value))\n                            .contentTransition(.interpolate)\n                            .symbolVariant(value > 0 ? .none : .slash)\n                            .frame(width: 20, height: 15, alignment: .leading)\n                    } else {\n                        Image(systemName: icon)\n                            .contentTransition(.interpolate)\n                            .opacity(value.isZero ? 0.6 : 1)\n                            .scaleEffect(value.isZero ? 0.85 : 1)\n                            .frame(width: 20, height: 15, alignment: .leading)\n                    }\n                case .brightness:\n                    Image(systemName: \"sun.max.fill\")\n                        .contentTransition(.symbolEffect)\n                        .frame(width: 20, height: 15)\n                        .foregroundStyle(.white)\n                case .backlight:\n                    Image(systemName: value > 0.5 ? \"light.max\" : \"light.min\")\n                        .contentTransition(.interpolate)\n                        .frame(width: 20, height: 15)\n                        .foregroundStyle(.white)\n                case .mic:\n                    Image(systemName: \"mic\")\n                        .symbolVariant(value > 0 ? .none : .slash)\n                        .contentTransition(.interpolate)\n                        .frame(width: 20, height: 15)\n                        .foregroundStyle(.white)\n                default:\n                    EmptyView()\n            }\n            if (eventType != .mic) {\n                DraggableProgressBar(value: $value)\n                if Defaults[.showClosedNotchHUDPercentage] {\n                    Text(\"\\(Int(value * 100))%\")\n                        .font(.system(size: 12, weight: .medium))\n                        .foregroundStyle(.white)\n                        .monospacedDigit()\n                        .frame(width: 35, alignment: .trailing)\n                }\n            } else {\n                Text(\"Mic \\(value > 0 ? \"unmuted\" : \"muted\")\")\n                    .foregroundStyle(.gray)\n                    .lineLimit(1)\n                    .allowsTightening(true)\n            }\n        }\n        .frame(maxWidth: .infinity, alignment: .leading)\n        .symbolVariant(.fill)\n        .imageScale(.large)\n    }\n    \n    func SpeakerSymbol(_ value: CGFloat) -> String {\n        switch(value) {\n            case 0:\n                return \"speaker.slash\"\n            case 0...0.3:\n                return \"speaker.wave.1\"\n            case 0.3...0.8:\n                return \"speaker.wave.2\"\n            case 0.8...1:\n                return \"speaker.wave.3\"\n            default:\n                return \"speaker.wave.2\"\n        }\n    }\n}\n\nstruct DraggableProgressBar: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @Binding var value: CGFloat\n    var onChange: ((CGFloat) -> Void)? = nil\n    \n    @State private var isDragging = false\n    @State private var dragOffset: CGFloat = 0\n    \n    var body: some View {\n        VStack {\n            GeometryReader { geo in\n                ZStack(alignment: .leading) {\n                    Capsule()\n                        .fill(.tertiary)\n                    Capsule()\n                        .fill(\n                            Defaults[.enableGradient] ?\n                                AnyShapeStyle(LinearGradient(\n                                    colors: Defaults[.systemEventIndicatorUseAccent] ?\n                                        [Color.effectiveAccent, Color.effectiveAccent.ensureMinimumBrightness(factor: 0.2)] :\n                                        [Color.white, Color.white.opacity(0.2)],\n                                    startPoint: .trailing,\n                                    endPoint: .leading\n                                )) :\n                                AnyShapeStyle(Defaults[.systemEventIndicatorUseAccent] ? Color.effectiveAccent : Color.white)\n                        )\n                        .frame(width: max(0, min(geo.size.width * value, geo.size.width)))\n                        .shadow(color: Defaults[.systemEventIndicatorShadow] ?\n                            (Defaults[.systemEventIndicatorUseAccent] ?\n                                Color.effectiveAccent.ensureMinimumBrightness(factor: 0.7) :\n                                Color.white) :\n                            Color.clear,\n                            radius: 8, x: 3)\n                        .opacity(value.isZero ? 0 : 1)\n                }\n                .gesture(\n                    DragGesture(minimumDistance: 0)\n                        .onChanged { gesture in\n                            withAnimation(.smooth(duration: 0.3)) {\n                                isDragging = true\n                                updateValue(gesture: gesture, in: geo)\n                            }\n                        }\n                        .onEnded { _ in\n                            withAnimation(.smooth(duration: 0.3)) {\n                                isDragging = false\n                            }\n                        }\n                )\n            }\n            .frame(height: Defaults[.inlineHUD] ? isDragging ? 8 : 5 : isDragging ? 9 : 6)\n        }\n    }\n    \n    private func updateValue(gesture: DragGesture.Value, in geometry: GeometryProxy) {\n        let dragPosition = gesture.location.x\n        let newValue = dragPosition / geometry.size.width\n        \n        value = max(0, min(newValue, 1))\n        onChange?(value)\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/LottieView.swift",
    "content": "//\n//  LottieView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-11-14.\n//\n\nimport SwiftUI\nimport Lottie\nimport ObjectiveC\n\nstruct LottieView: NSViewRepresentable {\n    let url: URL\n    let speed: Double\n    let loopMode: LottieLoopMode\n\n    private static var associatedURLKey: UInt8 = 0\n\n    func makeNSView(context: Context) -> NSView {\n        let animationView = LottieAnimationView()\n        animationView.translatesAutoresizingMaskIntoConstraints = false\n        let container = NSView()\n        container.addSubview(animationView)\n        NSLayoutConstraint.activate([\n            animationView.leadingAnchor.constraint(equalTo: container.leadingAnchor),\n            animationView.trailingAnchor.constraint(equalTo: container.trailingAnchor),\n            animationView.topAnchor.constraint(equalTo: container.topAnchor),\n            animationView.bottomAnchor.constraint(equalTo: container.bottomAnchor)\n        ])\n        return container\n    }\n\n    func updateNSView(_ nsView: NSView, context: Context) {\n        guard let animationView = nsView.subviews.first as? LottieAnimationView else { return }\n        let lastURL = objc_getAssociatedObject(animationView, &Self.associatedURLKey) as? URL\n        if lastURL != url {\n            LottieAnimation.loadedFrom(url: url) { animation in\n                animationView.animation = animation\n                animationView.loopMode = loopMode\n                animationView.animationSpeed = CGFloat(speed)\n                animationView.play()\n                objc_setAssociatedObject(animationView, &Self.associatedURLKey, url, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)\n            }\n        } else {\n            animationView.loopMode = loopMode\n            animationView.animationSpeed = CGFloat(speed)\n            if !animationView.isAnimationPlaying {\n                animationView.play()\n            }\n        }\n    }\n}"
  },
  {
    "path": "boringNotch/components/Music/LottieAnimationView.swift",
    "content": "//\n//  LottieAnimationContainer.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 2024. 10. 29..\n//\n\nimport SwiftUI\nimport Defaults\n\nstruct LottieAnimationContainer: View {\n    @Default(.selectedVisualizer) var selectedVisualizer\n    var body: some View {\n        if selectedVisualizer == nil {\n            LottieView(url: URL(string: \"https://assets9.lottiefiles.com/packages/lf20_mniampqn.json\")!, speed: 1.0, loopMode: .loop)\n        } else {\n            LottieView(url: selectedVisualizer!.url, speed: selectedVisualizer!.speed, loopMode: .loop)\n        }\n    }\n}\n\n#Preview {\n    LottieAnimationContainer()\n}\n"
  },
  {
    "path": "boringNotch/components/Music/MusicVisualizer.swift",
    "content": "//\n//  MusicVisualizer.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 02/08/24.\n//\nimport AppKit\nimport Cocoa\nimport SwiftUI\n\nclass AudioSpectrum: NSView {\n    private var barLayers: [CAShapeLayer] = []\n    private var barScales: [CGFloat] = []\n    private var isPlaying: Bool = true\n    private var animationTimer: Timer?\n    \n    override init(frame frameRect: NSRect) {\n        super.init(frame: frameRect)\n        wantsLayer = true\n        setupBars()\n    }\n    \n    required init?(coder: NSCoder) {\n        super.init(coder: coder)\n        wantsLayer = true\n        setupBars()\n    }\n\n    private func setupBars() {\n        let barWidth: CGFloat = 2\n        let barCount = 4\n        let spacing: CGFloat = barWidth\n        let totalWidth = CGFloat(barCount) * (barWidth + spacing)\n        let totalHeight: CGFloat = 14\n        frame.size = CGSize(width: totalWidth, height: totalHeight)\n\n        for i in 0 ..< barCount {\n            let xPosition = CGFloat(i) * (barWidth + spacing)\n            let barLayer = CAShapeLayer()\n            barLayer.frame = CGRect(x: xPosition, y: 0, width: barWidth, height: totalHeight)\n            barLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)\n            barLayer.position = CGPoint(x: xPosition + barWidth / 2, y: totalHeight / 2)\n            barLayer.fillColor = NSColor.white.cgColor\n            barLayer.backgroundColor = NSColor.white.cgColor\n            barLayer.allowsGroupOpacity = false\n            barLayer.masksToBounds = true\n            let path = NSBezierPath(roundedRect: CGRect(x: 0, y: 0, width: barWidth, height: totalHeight),\n                                    xRadius: barWidth / 2,\n                                    yRadius: barWidth / 2)\n            barLayer.path = path.cgPath\n            barLayers.append(barLayer)\n            barScales.append(0.35)\n            layer?.addSublayer(barLayer)\n        }\n    }\n    \n    private func startAnimating() {\n        guard animationTimer == nil else { return }\n        animationTimer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { [weak self] _ in\n            self?.updateBars()\n        }\n    }\n    \n    private func stopAnimating() {\n        animationTimer?.invalidate()\n        animationTimer = nil\n        resetBars()\n    }\n    \n    private func updateBars() {\n        for (i, barLayer) in barLayers.enumerated() {\n            let currentScale = barScales[i]\n            let targetScale = CGFloat.random(in: 0.35 ... 1.0)\n            barScales[i] = targetScale\n            let animation = CABasicAnimation(keyPath: \"transform.scale.y\")\n            animation.fromValue = currentScale\n            animation.toValue = targetScale\n            animation.duration = 0.3\n            animation.autoreverses = true\n            animation.fillMode = .forwards\n            animation.isRemovedOnCompletion = false\n            if #available(macOS 13.0, *) {\n                animation.preferredFrameRateRange = CAFrameRateRange(minimum: 24, maximum: 24, preferred: 24)\n            }\n            barLayer.add(animation, forKey: \"scaleY\")\n        }\n    }\n    \n    private func resetBars() {\n        for (i, barLayer) in barLayers.enumerated() {\n            barLayer.removeAllAnimations()\n            barLayer.transform = CATransform3DMakeScale(1, 0.35, 1)\n            barScales[i] = 0.35\n        }\n    }\n    \n    func setPlaying(_ playing: Bool) {\n        isPlaying = playing\n        if isPlaying {\n            startAnimating()\n        } else {\n            stopAnimating()\n        }\n    }\n}\n\nstruct AudioSpectrumView: NSViewRepresentable {\n    @Binding var isPlaying: Bool\n    \n    func makeNSView(context: Context) -> AudioSpectrum {\n        let spectrum = AudioSpectrum()\n        spectrum.setPlaying(isPlaying)\n        return spectrum\n    }\n    \n    func updateNSView(_ nsView: AudioSpectrum, context: Context) {\n        nsView.setPlaying(isPlaying)\n    }\n}\n\n#Preview {\n    AudioSpectrumView(isPlaying: .constant(true))\n        .frame(width: 16, height: 20)\n        .padding()\n}\n"
  },
  {
    "path": "boringNotch/components/Notch/BoringExtrasMenu.swift",
    "content": "//\n//  BoringExtrasMenu.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 04/08/24.\n//\n\nimport SwiftUI\n\nstruct BoringLargeButtons: View {\n    var action: () -> Void\n    var icon: Image\n    var title: String\n    var body: some View {\n        Button (\n            action:action,\n            label: {\n                ZStack {\n                    RoundedRectangle(cornerRadius: 12.0).fill(.black).frame(width: 70, height: 70)\n                    VStack(spacing: 8) {\n                        icon.resizable()\n                            .aspectRatio(contentMode: .fit).frame(width:20)\n                        Text(title).font(.body)\n                    }\n                }\n            }).buttonStyle(PlainButtonStyle()).shadow(color: .black.opacity(0.5), radius: 10)\n    }\n}\n\nstruct BoringExtrasMenu : View {\n    @ObservedObject var vm: BoringViewModel\n    \n    var body: some View {\n        VStack{\n            HStack(spacing: 20)  {\n                hide\n                settings\n                close\n            }\n        }\n    }\n    \n    var github: some View {\n        BoringLargeButtons(\n            action: {\n                if let url = URL(string: \"https://github.com/TheBoredTeam/boring.notch\") {\n                    NSWorkspace.shared.open(url)\n                }\n            },\n            icon: Image(.github),\n            title: \"Checkout\"\n        )\n    }\n    \n    var settings: some View {\n        Button(action: {\n            SettingsWindowController.shared.showWindow()\n        }) {\n            ZStack {\n                RoundedRectangle(cornerRadius: 12.0).fill(.black).frame(width: 70, height: 70)\n                VStack(spacing: 8) {\n                    Image(systemName: \"gear\").resizable()\n                        .aspectRatio(contentMode: .fit).frame(width:20)\n                    Text(\"Settings\").font(.body)\n                }\n            }\n        }\n        .buttonStyle(PlainButtonStyle()).shadow(color: .black.opacity(0.5), radius: 10)\n    }\n    \n    var hide: some View {\n        BoringLargeButtons(\n            action: {\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {\n                    //vm.openMusic()\n                }\n            },\n            icon: Image(systemName: \"arrow.down.forward.and.arrow.up.backward\"),\n            title: \"Hide\"\n        )\n    }\n    \n    var close: some View {\n        BoringLargeButtons(\n            action: {\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {\n                    DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {\n                        NSApp.terminate(nil)\n                    }\n                }\n            },\n            icon: Image(systemName: \"xmark\"),\n            title: \"Exit\"\n        )\n    }\n}\n\n\n#Preview {\n    BoringExtrasMenu(vm: .init())\n}\n"
  },
  {
    "path": "boringNotch/components/Notch/BoringHeader.swift",
    "content": "//\n//  BoringHeader.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 04/08/24.\n//\n\nimport Defaults\nimport SwiftUI\n\nstruct BoringHeader: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject var batteryModel = BatteryStatusViewModel.shared\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @StateObject var tvm = ShelfStateViewModel.shared\n    var body: some View {\n        HStack(spacing: 0) {\n            HStack {\n                if (!tvm.isEmpty || coordinator.alwaysShowTabs) && Defaults[.boringShelf] {\n                    TabSelectionView()\n                } else if vm.notchState == .open {\n                    EmptyView()\n                }\n            }\n            .frame(maxWidth: .infinity, alignment: .leading)\n            .opacity(vm.notchState == .closed ? 0 : 1)\n            .blur(radius: vm.notchState == .closed ? 20 : 0)\n            .zIndex(2)\n\n            if vm.notchState == .open {\n                Rectangle()\n                    .fill(NSScreen.screen(withUUID: coordinator.selectedScreenUUID)?.safeAreaInsets.top ?? 0 > 0 ? .black : .clear)\n                    .frame(width: vm.closedNotchSize.width)\n                    .mask {\n                        NotchShape()\n                    }\n            }\n\n            HStack(spacing: 4) {\n                if vm.notchState == .open {\n                    if isHUDType(coordinator.sneakPeek.type) && coordinator.sneakPeek.show && Defaults[.showOpenNotchHUD] {\n                        OpenNotchHUD(type: $coordinator.sneakPeek.type, value: $coordinator.sneakPeek.value, icon: $coordinator.sneakPeek.icon)\n                            .transition(.scale(scale: 0.8).combined(with: .opacity))\n                    } else {\n                        if Defaults[.showMirror] {\n                            Button(action: {\n                                vm.toggleCameraPreview()\n                            }) {\n                                Capsule()\n                                    .fill(.black)\n                                    .frame(width: 30, height: 30)\n                                    .overlay {\n                                        Image(systemName: \"web.camera\")\n                                            .foregroundColor(.white)\n                                            .padding()\n                                            .imageScale(.medium)\n                                    }\n                            }\n                            .buttonStyle(PlainButtonStyle())\n                        }\n                        if Defaults[.settingsIconInNotch] {\n                            Button(action: {\n                                SettingsWindowController.shared.showWindow()\n                            }) {\n                                Capsule()\n                                    .fill(.black)\n                                    .frame(width: 30, height: 30)\n                                    .overlay {\n                                        Image(systemName: \"gear\")\n                                            .foregroundColor(.white)\n                                            .padding()\n                                            .imageScale(.medium)\n                                    }\n                            }\n                            .buttonStyle(PlainButtonStyle())\n                        }\n                        if Defaults[.showBatteryIndicator] {\n                            BoringBatteryView(\n                                batteryWidth: 30,\n                                isCharging: batteryModel.isCharging,\n                                isInLowPowerMode: batteryModel.isInLowPowerMode,\n                                isPluggedIn: batteryModel.isPluggedIn,\n                                levelBattery: batteryModel.levelBattery,\n                                maxCapacity: batteryModel.maxCapacity,\n                                timeToFullCharge: batteryModel.timeToFullCharge,\n                                isForNotification: false\n                            )\n                        }\n                    }\n                }\n            }\n            .font(.system(.headline, design: .rounded))\n            .frame(maxWidth: .infinity, alignment: .trailing)\n            .opacity(vm.notchState == .closed ? 0 : 1)\n            .blur(radius: vm.notchState == .closed ? 20 : 0)\n            .zIndex(2)\n        }\n        .foregroundColor(.gray)\n        .environmentObject(vm)\n    }\n\n    func isHUDType(_ type: SneakContentType) -> Bool {\n        switch type {\n        case .volume, .brightness, .backlight, .mic:\n            return true\n        default:\n            return false\n        }\n    }\n}\n\n#Preview {\n    BoringHeader().environmentObject(BoringViewModel())\n}\n"
  },
  {
    "path": "boringNotch/components/Notch/BoringNotchSkyLightWindow.swift",
    "content": "//\n//  BoringNotchSkyLightWindow.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-20.\n//\n\nimport Cocoa\nimport SkyLightWindow\nimport Defaults\nimport Combine\n\nextension SkyLightOperator {\n    func undelegateWindow(_ window: NSWindow) {\n        typealias F_SLSRemoveWindowsFromSpaces = @convention(c) (Int32, CFArray, CFArray) -> Int32\n        \n        let handler = dlopen(\"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight\", RTLD_NOW)\n        guard let SLSRemoveWindowsFromSpaces = unsafeBitCast(\n            dlsym(handler, \"SLSRemoveWindowsFromSpaces\"),\n            to: F_SLSRemoveWindowsFromSpaces?.self\n        ) else {\n            return\n        }\n        \n        // Remove the window from the SkyLight space\n        _ = SLSRemoveWindowsFromSpaces(\n            connection,\n            [window.windowNumber] as CFArray,\n            [space] as CFArray\n        )\n    }\n}\n\nclass BoringNotchSkyLightWindow: NSPanel {\n    private var isSkyLightEnabled: Bool = false\n    \n    override init(\n        contentRect: NSRect,\n        styleMask: NSWindow.StyleMask,\n        backing: NSWindow.BackingStoreType,\n        defer flag: Bool\n    ) {\n        super.init(\n            contentRect: contentRect,\n            styleMask: styleMask,\n            backing: backing,\n            defer: flag\n        )\n        \n        configureWindow()\n        setupObservers()\n    }\n    \n    private func configureWindow() {\n        isFloatingPanel = true\n        isOpaque = false\n        titleVisibility = .hidden\n        titlebarAppearsTransparent = true\n        backgroundColor = .clear\n        isMovable = false\n        level = .mainMenu + 3\n        hasShadow = false\n        isReleasedWhenClosed = false\n        \n        // Force dark appearance regardless of system setting\n        appearance = NSAppearance(named: .darkAqua)\n        \n        collectionBehavior = [\n            .fullScreenAuxiliary,\n            .stationary,\n            .canJoinAllSpaces,\n            .ignoresCycle,\n        ]\n        \n        // Apply initial sharing type setting\n        updateSharingType()\n    }\n    \n    private func setupObservers() {\n        // Listen for changes to the hideFromScreenRecording setting\n        Defaults.publisher(.hideFromScreenRecording)\n            .sink { [weak self] _ in\n                self?.updateSharingType()\n            }\n            .store(in: &observers)\n    }\n    \n    private func updateSharingType() {\n        if Defaults[.hideFromScreenRecording] {\n            sharingType = .none\n        } else {\n            sharingType = .readWrite\n        }\n    }\n    \n    func enableSkyLight() {\n        if !isSkyLightEnabled {\n            SkyLightOperator.shared.delegateWindow(self)\n            isSkyLightEnabled = true\n        }\n    }\n    \n    func disableSkyLight() {\n        if isSkyLightEnabled {\n            SkyLightOperator.shared.undelegateWindow(self)\n            isSkyLightEnabled = false\n        }\n    }\n    \n    private var observers: Set<AnyCancellable> = []\n    \n    override var canBecomeKey: Bool { false }\n    override var canBecomeMain: Bool { false }\n}\n"
  },
  {
    "path": "boringNotch/components/Notch/BoringNotchWindow.swift",
    "content": "//\n//  BoringNotchWindow.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 06/08/24.\n//\n\nimport Cocoa\n\nclass BoringNotchWindow: NSPanel {\n    override init(\n        contentRect: NSRect,\n        styleMask: NSWindow.StyleMask,\n        backing: NSWindow.BackingStoreType,\n        defer flag: Bool\n    ) {\n        super.init(\n            contentRect: contentRect,\n            styleMask: styleMask,\n            backing: backing,\n            defer: flag\n        )\n        \n        isFloatingPanel = true\n        isOpaque = false\n        titleVisibility = .hidden\n        titlebarAppearsTransparent = true\n        backgroundColor = .clear\n        isMovable = false\n        \n        collectionBehavior = [\n            .fullScreenAuxiliary,\n            .stationary,\n            .canJoinAllSpaces,\n            .ignoresCycle,\n        ]\n        \n        isReleasedWhenClosed = false\n        level = .mainMenu + 3\n        hasShadow = false\n    }\n    \n    override var canBecomeKey: Bool {\n        false\n    }\n    \n    override var canBecomeMain: Bool {\n        false\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Notch/NotchHomeView.swift",
    "content": "//\n//  NotchHomeView.swift\n//  boringNotch\n//\n//  Created by Hugo Persson on 2024-08-18.\n//  Modified by Harsh Vardhan Goswami & Richard Kunkli & Mustafa Ramadan\n//\n\nimport Combine\nimport Defaults\nimport SwiftUI\n\n// MARK: - Music Player Components\n\nstruct MusicPlayerView: View {\n    @EnvironmentObject var vm: BoringViewModel\n    let albumArtNamespace: Namespace.ID\n\n    var body: some View {\n        HStack {\n            AlbumArtView(vm: vm, albumArtNamespace: albumArtNamespace).padding(.all, 5)\n            MusicControlsView().drawingGroup().compositingGroup()\n        }\n    }\n}\n\nstruct AlbumArtView: View {\n    @ObservedObject var musicManager = MusicManager.shared\n    @ObservedObject var vm: BoringViewModel\n    let albumArtNamespace: Namespace.ID\n\n    var body: some View {\n        ZStack(alignment: .bottomTrailing) {\n            if Defaults[.lightingEffect] {\n                albumArtBackground\n            }\n            albumArtButton\n        }\n    }\n\n    private var albumArtBackground: some View {\n        Image(nsImage: musicManager.albumArt)\n            .resizable()\n            .clipped()\n            .clipShape(\n                RoundedRectangle(\n                    cornerRadius: Defaults[.cornerRadiusScaling]\n                        ? MusicPlayerImageSizes.cornerRadiusInset.opened\n                        : MusicPlayerImageSizes.cornerRadiusInset.closed)\n            )\n            .aspectRatio(1, contentMode: .fit)\n            .scaleEffect(x: 1.3, y: 1.4)\n            .rotationEffect(.degrees(92))\n            .blur(radius: 40)\n            .opacity(musicManager.isPlaying ? 0.5 : 0)\n    }\n\n    private var albumArtButton: some View {\n        ZStack {\n            Button {\n                musicManager.openMusicApp()\n            } label: {\n                ZStack(alignment:.bottomTrailing) {\n                    albumArtImage\n                    appIconOverlay\n                }\n            }\n            .buttonStyle(PlainButtonStyle())\n            .scaleEffect(musicManager.isPlaying ? 1 : 0.85)\n            \n            albumArtDarkOverlay\n        }\n    }\n\n    private var albumArtDarkOverlay: some View {\n        Rectangle()\n            .aspectRatio(1, contentMode: .fit)\n            .foregroundColor(Color.black)\n            .opacity(musicManager.isPlaying ? 0 : 0.8)\n            .blur(radius: 50)\n    }\n                \n\n    private var albumArtImage: some View {\n        Image(nsImage: musicManager.albumArt)\n            .resizable()\n            .aspectRatio(1, contentMode: .fit)\n            .matchedGeometryEffect(id: \"albumArt\", in: albumArtNamespace)\n            .clipped()\n            .clipShape(\n                RoundedRectangle(\n                    cornerRadius: Defaults[.cornerRadiusScaling]\n                        ? MusicPlayerImageSizes.cornerRadiusInset.opened\n                        : MusicPlayerImageSizes.cornerRadiusInset.closed)\n            )\n    }\n\n    @ViewBuilder\n    private var appIconOverlay: some View {\n        if vm.notchState == .open && !musicManager.usingAppIconForArtwork {\n            AppIcon(for: musicManager.bundleIdentifier ?? \"com.apple.Music\")\n                .resizable()\n                .aspectRatio(contentMode: .fill)\n                .frame(width: 30, height: 30)\n                .offset(x: 10, y: 10)\n                .transition(.scale.combined(with: .opacity))\n                .zIndex(2)\n        }\n    }\n}\n\nstruct MusicControlsView: View {\n    @ObservedObject var musicManager = MusicManager.shared\n        @EnvironmentObject var vm: BoringViewModel\n        @ObservedObject var webcamManager = WebcamManager.shared\n    @State private var sliderValue: Double = 0\n    @State private var dragging: Bool = false\n    @State private var lastDragged: Date = .distantPast\n    @Default(.musicControlSlots) private var slotConfig\n    @Default(.musicControlSlotLimit) private var slotLimit\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            songInfoAndSlider\n            slotToolbar\n        }\n        .buttonStyle(PlainButtonStyle())\n    }\n\n    private var songInfoAndSlider: some View {\n        GeometryReader { geo in\n            VStack(alignment: .leading, spacing: 4) {\n                songInfo(width: geo.size.width)\n                musicSlider\n            }\n        }\n        .padding(.top, 10)\n        .padding(.leading, 5)\n    }\n\n    private func songInfo(width: CGFloat) -> some View {\n        VStack(alignment: .leading, spacing: 0) {\n            MarqueeText(\n                $musicManager.songTitle, font: .headline, nsFont: .headline, textColor: .white,\n                frameWidth: width)\n            MarqueeText(\n                $musicManager.artistName,\n                font: .headline,\n                nsFont: .headline,\n                textColor: Defaults[.playerColorTinting]\n                    ? Color(nsColor: musicManager.avgColor)\n                        .ensureMinimumBrightness(factor: 0.6) : .gray,\n                frameWidth: width\n            )\n            .fontWeight(.medium)\n            if Defaults[.enableLyrics] {\n                TimelineView(.animation(minimumInterval: 0.25)) { timeline in\n                    let currentElapsed: Double = {\n                        guard musicManager.isPlaying else { return musicManager.elapsedTime }\n                        let delta = timeline.date.timeIntervalSince(musicManager.timestampDate)\n                        let progressed = musicManager.elapsedTime + (delta * musicManager.playbackRate)\n                        return min(max(progressed, 0), musicManager.songDuration)\n                    }()\n                    let line: String = {\n                        if musicManager.isFetchingLyrics { return \"Loading lyrics…\" }\n                        if !musicManager.syncedLyrics.isEmpty {\n                            return musicManager.lyricLine(at: currentElapsed)\n                        }\n                        let trimmed = musicManager.currentLyrics.trimmingCharacters(in: .whitespacesAndNewlines)\n                        return trimmed.isEmpty ? \"No lyrics found\" : trimmed.replacingOccurrences(of: \"\\n\", with: \" \")\n                    }()\n                    let isPersian = line.unicodeScalars.contains { scalar in\n                        let v = scalar.value\n                        return v >= 0x0600 && v <= 0x06FF\n                    }\n                    MarqueeText(\n                        .constant(line),\n                        font: .subheadline,\n                        nsFont: .subheadline,\n                        textColor: musicManager.isFetchingLyrics ? .gray.opacity(0.7) : .gray,\n                        frameWidth: width\n                    )\n                    .font(isPersian ? .custom(\"Vazirmatn-Regular\", size: NSFont.preferredFont(forTextStyle: .subheadline).pointSize) : .subheadline)\n                    .lineLimit(1)\n                    .opacity(musicManager.isPlaying ? 1 : 0)\n                    .transition(.opacity.combined(with: .move(edge: .top)))\n                }\n            }\n        }\n    }\n\n    private var musicSlider: some View {\n        TimelineView(.animation(minimumInterval: musicManager.playbackRate > 0 ? 0.1 : nil)) { timeline in\n            MusicSliderView(\n                sliderValue: $sliderValue,\n                duration: $musicManager.songDuration,\n                lastDragged: $lastDragged,\n                color: musicManager.avgColor,\n                dragging: $dragging,\n                currentDate: timeline.date,\n                timestampDate: musicManager.timestampDate,\n                elapsedTime: musicManager.elapsedTime,\n                playbackRate: musicManager.playbackRate,\n                isPlaying: musicManager.isPlaying\n            ) { newValue in\n                MusicManager.shared.seek(to: newValue)\n            }\n            .padding(.top, 5)\n            .frame(height: 36)\n        }\n    }\n\n    private var slotToolbar: some View {\n        let slots = activeSlots\n        return HStack(spacing: 6) {\n            ForEach(Array(slots.enumerated()), id: \\.offset) { index, slot in\n                slotView(for: slot)\n                    .frame(alignment: .center)\n            }\n        }\n        .frame(maxWidth: .infinity, alignment: .center)\n    }\n\n    private var activeSlots: [MusicControlButton] {\n        let sanitizedLimit = min(\n            max(slotLimit, MusicControlButton.minSlotCount),\n            MusicControlButton.maxSlotCount\n        )\n        let padded = slotConfig.padded(to: sanitizedLimit, filler: .none)\n        let result = Array(padded.prefix(sanitizedLimit))\n        // If calendar and camera are both visible alongside music, hide the edge slots\n        let shouldHideEdges = Defaults[.showCalendar] && Defaults[.showMirror] && webcamManager.cameraAvailable && vm.isCameraExpanded\n        if shouldHideEdges && result.count >= 5 {\n            return Array(result.dropFirst().dropLast())\n        }\n\n        return result\n    }\n\n    @ViewBuilder\n    private func slotView(for slot: MusicControlButton) -> some View {\n        switch slot {\n        case .shuffle:\n            HoverButton(icon: \"shuffle\", iconColor: musicManager.isShuffled ? .red : .primary, scale: .medium) {\n                MusicManager.shared.toggleShuffle()\n            }\n        case .previous:\n            HoverButton(icon: \"backward.fill\", scale: .medium) {\n                MusicManager.shared.previousTrack()\n            }\n        case .playPause:\n            HoverButton(icon: musicManager.isPlaying ? \"pause.fill\" : \"play.fill\", scale: .large) {\n                MusicManager.shared.togglePlay()\n            }\n        case .next:\n            HoverButton(icon: \"forward.fill\", scale: .medium) {\n                MusicManager.shared.nextTrack()\n            }\n        case .repeatMode:\n            HoverButton(icon: repeatIcon, iconColor: repeatIconColor, scale: .medium) {\n                MusicManager.shared.toggleRepeat()\n            }\n        case .volume:\n            VolumeControlView()\n        case .favorite:\n            FavoriteControlButton()\n        case .goBackward:\n            HoverButton(icon: \"gobackward.15\", scale: .medium) {\n                MusicManager.shared.skip(seconds: -15)\n            }\n        case .goForward:\n            HoverButton(icon: \"goforward.15\", scale: .medium) {\n                MusicManager.shared.skip(seconds: 15)\n            }\n        case .none:\n            Color.clear.frame(height: 1)\n        }\n    }\n\n    private var repeatIcon: String {\n        switch musicManager.repeatMode {\n        case .off:\n            return \"repeat\"\n        case .all:\n            return \"repeat\"\n        case .one:\n            return \"repeat.1\"\n        }\n    }\n\n    private var repeatIconColor: Color {\n        switch musicManager.repeatMode {\n        case .off:\n            return .primary\n        case .all, .one:\n            return .red\n        }\n    }\n}\n\nstruct FavoriteControlButton: View {\n    @ObservedObject var musicManager = MusicManager.shared\n\n    var body: some View {\n        HoverButton(icon: iconName, iconColor: iconColor, scale: .medium) {\n            MusicManager.shared.toggleFavoriteTrack()\n        }\n        .disabled(!musicManager.canFavoriteTrack)\n        .opacity(musicManager.canFavoriteTrack ? 1 : 0.35)\n    }\n\n    private var iconName: String {\n        musicManager.isFavoriteTrack ? \"heart.fill\" : \"heart\"\n    }\n\n    private var iconColor: Color {\n        musicManager.isFavoriteTrack ? .red : .primary\n    }\n}\n\nprivate extension Array where Element == MusicControlButton {\n    func padded(to length: Int, filler: MusicControlButton) -> [MusicControlButton] {\n        if count >= length { return self }\n        return self + Array(repeating: filler, count: length - count)\n    }\n}\n\n// MARK: - Volume Control View\n\nstruct VolumeControlView: View {\n    @ObservedObject var musicManager = MusicManager.shared\n    @State private var volumeSliderValue: Double = 0.5\n    @State private var dragging: Bool = false\n    @State private var showVolumeSlider: Bool = false\n    @State private var lastVolumeUpdateTime: Date = Date.distantPast\n    private let volumeUpdateThrottle: TimeInterval = 0.1\n    \n    var body: some View {\n        HStack(spacing: 4) {\n            Button(action: {\n                if musicManager.volumeControlSupported {\n                    withAnimation(.easeInOut(duration: 0.12)) {\n                        showVolumeSlider.toggle()\n                    }\n                }\n            }) {\n                Image(systemName: volumeIcon)\n                    .font(.system(size: 14, weight: .medium))\n                    .foregroundColor(musicManager.volumeControlSupported ? .white : .gray)\n            }\n            .buttonStyle(PlainButtonStyle())\n            .disabled(!musicManager.volumeControlSupported)\n            .frame(width: 24)\n\n            if showVolumeSlider && musicManager.volumeControlSupported {\n                CustomSlider(\n                    value: $volumeSliderValue,\n                    range: 0.0...1.0,\n                    color: .white,\n                    dragging: $dragging,\n                    lastDragged: .constant(Date.distantPast),\n                    onValueChange: { newValue in\n                        MusicManager.shared.setVolume(to: newValue)\n                    },\n                    onDragChange: { newValue in\n                        let now = Date()\n                        if now.timeIntervalSince(lastVolumeUpdateTime) > volumeUpdateThrottle {\n                            MusicManager.shared.setVolume(to: newValue)\n                            lastVolumeUpdateTime = now\n                        }\n                    }\n                )\n                .frame(width: 48, height: 8)\n                .transition(.scale.combined(with: .opacity))\n            }\n        }\n        .clipped()\n        .onReceive(musicManager.$volume) { volume in\n            if !dragging {\n                volumeSliderValue = volume\n            }\n        }\n        .onReceive(musicManager.$volumeControlSupported) { supported in\n            if !supported {\n                withAnimation(.easeInOut(duration: 0.2)) {\n                    showVolumeSlider = false\n                }\n            }\n        }\n        .onChange(of: showVolumeSlider) { _, isShowing in\n            if isShowing {\n                // Sync volume from app when slider appears\n                Task {\n                    await MusicManager.shared.syncVolumeFromActiveApp()\n                }\n            }\n        }\n        .onDisappear {\n            // volumeUpdateTask?.cancel() // No longer needed\n        }\n    }\n    \n    \n    private var volumeIcon: String {\n        if !musicManager.volumeControlSupported {\n            return \"speaker.slash\"\n        } else if volumeSliderValue == 0 {\n            return \"speaker.slash.fill\"\n        } else if volumeSliderValue < 0.33 {\n            return \"speaker.1.fill\"\n        } else if volumeSliderValue < 0.66 {\n            return \"speaker.2.fill\"\n        } else {\n            return \"speaker.3.fill\"\n        }\n    }\n}\n\n// MARK: - Main View\n\nstruct NotchHomeView: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject var webcamManager = WebcamManager.shared\n    @ObservedObject var batteryModel = BatteryStatusViewModel.shared\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    let albumArtNamespace: Namespace.ID\n\n    var body: some View {\n        Group {\n            if !coordinator.firstLaunch {\n                mainContent\n            }\n        }\n        // simplified: use a straightforward opacity transition\n        .transition(.opacity)\n    }\n\n    private var shouldShowCamera: Bool {\n        Defaults[.showMirror] && webcamManager.cameraAvailable && vm.isCameraExpanded\n    }\n\n    private var mainContent: some View {\n        HStack(alignment: .top, spacing: (shouldShowCamera && Defaults[.showCalendar]) ? 10 : 15) {\n            MusicPlayerView(albumArtNamespace: albumArtNamespace)\n\n            if Defaults[.showCalendar] {\n                CalendarView()\n                    .frame(width: shouldShowCamera ? 170 : 215)\n                    .onHover { isHovering in\n                        vm.isHoveringCalendar = isHovering\n                    }\n                    .environmentObject(vm)\n                    .transition(.opacity)\n            }\n\n            if shouldShowCamera {\n                CameraPreviewView(webcamManager: webcamManager)\n                    .scaledToFit()\n                    .opacity(vm.notchState == .closed ? 0 : 1)\n                    .blur(radius: vm.notchState == .closed ? 20 : 0)\n                    .animation(.interactiveSpring(response: 0.32, dampingFraction: 0.76, blendDuration: 0), value: shouldShowCamera)\n            }\n        }\n        .transition(.asymmetric(insertion: .opacity.combined(with: .move(edge: .top)), removal: .opacity))\n        .blur(radius: vm.notchState == .closed ? 30 : 0)\n    }\n}\n\nstruct MusicSliderView: View {\n    @Binding var sliderValue: Double\n    @Binding var duration: Double\n    @Binding var lastDragged: Date\n    var color: NSColor\n    @Binding var dragging: Bool\n    let currentDate: Date\n    let timestampDate: Date\n    let elapsedTime: Double\n    let playbackRate: Double\n    let isPlaying: Bool\n    var onValueChange: (Double) -> Void\n\n\n    var body: some View {\n        VStack {\n            CustomSlider(\n                value: $sliderValue,\n                range: 0...duration,\n                color: Defaults[.sliderColor] == SliderColorEnum.albumArt\n                    ? Color(nsColor: color).ensureMinimumBrightness(factor: 0.8)\n                    : Defaults[.sliderColor] == SliderColorEnum.accent ? .effectiveAccent : .white,\n                dragging: $dragging,\n                lastDragged: $lastDragged,\n                onValueChange: onValueChange\n            )\n            .frame(height: 10, alignment: .center)\n\n            HStack {\n                Text(timeString(from: sliderValue))\n                Spacer()\n                Text(timeString(from: duration))\n            }\n            .fontWeight(.medium)\n            .foregroundColor(\n                Defaults[.playerColorTinting]\n                    ? Color(nsColor: color).ensureMinimumBrightness(factor: 0.6) : .gray\n            )\n            .font(.caption)\n        }\n        .onChange(of: currentDate) {\n           guard !dragging, timestampDate.timeIntervalSince(lastDragged) > -1 else { return }\n            sliderValue = MusicManager.shared.estimatedPlaybackPosition(at: currentDate)\n        }\n    }\n\n    func timeString(from seconds: Double) -> String {\n        let totalMinutes = Int(seconds) / 60\n        let remainingSeconds = Int(seconds) % 60\n        let hours = totalMinutes / 60\n        let minutes = totalMinutes % 60\n\n        if hours > 0 {\n            return String(format: \"%d:%02d:%02d\", hours, minutes, remainingSeconds)\n        } else {\n            return String(format: \"%d:%02d\", minutes, remainingSeconds)\n        }\n    }\n}\n\nstruct CustomSlider: View {\n    @Binding var value: Double\n    var range: ClosedRange<Double>\n    var color: Color = .white\n    @Binding var dragging: Bool\n    @Binding var lastDragged: Date\n    var onValueChange: ((Double) -> Void)?\n    var onDragChange: ((Double) -> Void)?\n\n    var body: some View {\n        GeometryReader { geometry in\n            let width = geometry.size.width\n            let height = CGFloat(dragging ? 9 : 5)\n            let rangeSpan = range.upperBound - range.lowerBound\n\n            let progress = rangeSpan == .zero ? 0 : (value - range.lowerBound) / rangeSpan\n            let filledTrackWidth = min(max(progress, 0), 1) * width\n\n            ZStack(alignment: .leading) {\n                Rectangle()\n                    .fill(.gray.opacity(0.3))\n                    .frame(height: height)\n\n                Rectangle()\n                    .fill(color)\n                    .frame(width: filledTrackWidth, height: height)\n            }\n            .cornerRadius(height / 2)\n            .frame(height: 10)\n            .contentShape(Rectangle())\n            .gesture(\n                DragGesture(minimumDistance: 0)\n                    .onChanged { gesture in\n                        withAnimation {\n                            dragging = true\n                        }\n                        let newValue = range.lowerBound + Double(gesture.location.x / width) * rangeSpan\n                        value = min(max(newValue, range.lowerBound), range.upperBound)\n                        onDragChange?(value)\n                    }\n                    .onEnded { _ in\n                        onValueChange?(value)\n                        dragging = false\n                        lastDragged = Date()\n                    }\n            )\n            .animation(.spring(response: 0.35, dampingFraction: 0.7), value: dragging)\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Notch/NotchShape.swift",
    "content": "//\n//  NotchShape.swift\n//  boringNotch\n//\n// Created by Kai Azim on 2023-08-24.\n// Original source: https://github.com/MrKai77/DynamicNotchKit\n// Modified by Alexander on 2025-05-18.\n\nimport SwiftUI\n\nstruct NotchShape: Shape {\n    private var topCornerRadius: CGFloat\n    private var bottomCornerRadius: CGFloat\n\n    init(\n        topCornerRadius: CGFloat? = nil,\n        bottomCornerRadius: CGFloat? = nil\n    ) {\n        self.topCornerRadius = topCornerRadius ?? 6\n        self.bottomCornerRadius = bottomCornerRadius ?? 14\n    }\n\n    var animatableData: AnimatablePair<CGFloat, CGFloat> {\n        get {\n            .init(\n                topCornerRadius,\n                bottomCornerRadius\n            )\n        }\n        set {\n            topCornerRadius = newValue.first\n            bottomCornerRadius = newValue.second\n        }\n    }\n\n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n\n        path.move(\n            to: CGPoint(\n                x: rect.minX,\n                y: rect.minY\n            )\n        )\n\n        path.addQuadCurve(\n            to: CGPoint(\n                x: rect.minX + topCornerRadius,\n                y: rect.minY + topCornerRadius\n            ),\n            control: CGPoint(\n                x: rect.minX + topCornerRadius,\n                y: rect.minY\n            )\n        )\n\n        path.addLine(\n            to: CGPoint(\n                x: rect.minX + topCornerRadius,\n                y: rect.maxY - bottomCornerRadius\n            )\n        )\n\n        path.addQuadCurve(\n            to: CGPoint(\n                x: rect.minX + topCornerRadius + bottomCornerRadius,\n                y: rect.maxY\n            ),\n            control: CGPoint(\n                x: rect.minX + topCornerRadius,\n                y: rect.maxY\n            )\n        )\n\n        path.addLine(\n            to: CGPoint(\n                x: rect.maxX - topCornerRadius - bottomCornerRadius,\n                y: rect.maxY\n            )\n        )\n\n        path.addQuadCurve(\n            to: CGPoint(\n                x: rect.maxX - topCornerRadius,\n                y: rect.maxY - bottomCornerRadius\n            ),\n            control: CGPoint(\n                x: rect.maxX - topCornerRadius,\n                y: rect.maxY\n            )\n        )\n\n        path.addLine(\n            to: CGPoint(\n                x: rect.maxX - topCornerRadius,\n                y: rect.minY + topCornerRadius\n            )\n        )\n\n        path.addQuadCurve(\n            to: CGPoint(\n                x: rect.maxX,\n                y: rect.minY\n            ),\n            control: CGPoint(\n                x: rect.maxX - topCornerRadius,\n                y: rect.minY\n            )\n        )\n\n        path.addLine(\n            to: CGPoint(\n                x: rect.minX,\n                y: rect.minY\n            )\n        )\n\n        return path\n    }\n}\n\n#Preview {\n    NotchShape(topCornerRadius: 6, bottomCornerRadius: 14)\n        .frame(width: 200, height: 32)\n        .padding(10)\n}\n"
  },
  {
    "path": "boringNotch/components/Onboarding/MusicControllerSelectionView.swift",
    "content": "//\n//  MusicControllerSelectionView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-06-23.\n//\n\nimport SwiftUI\nimport Defaults\n\n\nstruct MusicControllerSelectionView: View {\n    let onContinue: () -> Void\n\n    @Default(.mediaController) var mediaController\n    \n    private var availableMediaControllers: [MediaControllerType] {\n        if MusicManager.shared.isNowPlayingDeprecated {\n            return MediaControllerType.allCases.filter { $0 != .nowPlaying }\n        } else {\n            return MediaControllerType.allCases\n        }\n    }\n    \n    @State private var selectedMediaController: MediaControllerType = Defaults[.mediaController]\n    \n    var body: some View {\n        VStack(spacing: 20) {\n            Text(\"Choose a Music Source\")\n                .font(.title)\n                .fontWeight(.bold)\n                .padding(.top, 24)\n\n            Text(\"Select the music source you want to use. You can change this later in the app settings.\")\n                .multilineTextAlignment(.center)\n                .font(.body)\n                .foregroundColor(.secondary)\n                .padding(.horizontal)\n\n            ScrollView {\n                VStack(spacing: 12) {\n                    ForEach(availableMediaControllers) { controller in\n                        ControllerOptionView(\n                            controller: controller,\n                            isSelected: self.selectedMediaController == controller\n                        )\n                        .onTapGesture {\n                            self.selectedMediaController = controller\n                        }\n                    }\n                }\n                .padding()\n            }\n            //Disable scroll if there are 4 or fewer to avoid unnecessary scroll behavior\n            .scrollDisabled(availableMediaControllers.count <= 4)\n\n//            Spacer()\n\n            Button(\"Continue\", action: {\n                self.mediaController = self.selectedMediaController\n                NotificationCenter.default.post(\n                    name: Notification.Name.mediaControllerChanged,\n                    object: nil\n                )\n                onContinue()\n            })\n                .buttonStyle(.borderedProminent)\n                .controlSize(.large)\n                .padding(.bottom, 24)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(\n            VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)\n                .ignoresSafeArea()\n        )\n    }\n}\n\nstruct ControllerOptionView: View {\n    let controller: MediaControllerType\n    let isSelected: Bool\n\n    var body: some View {\n        HStack(spacing: 16) {\n            Image(systemName: isSelected ? \"checkmark.circle.fill\" : \"circle\")\n                .font(.title2)\n                .foregroundColor(isSelected ? .effectiveAccent : .secondary.opacity(0.5))\n                .animation(.spring(response: 0.3, dampingFraction: 0.6), value: isSelected)\n\n            VStack(alignment: .leading, spacing: 4) {\n                Text(controller.rawValue)\n                    .font(.headline)\n                    .fontWeight(.semibold)\n\n                Text(controller.description)\n                    .font(.subheadline)\n                    .foregroundColor(.secondary)\n                \n                if controller == .youtubeMusic, let url = URL(string: \"https://github.com/pear-devs/pear-desktop\") {\n                    Link(\"View on GitHub: pear-devs/pear-desktop\", destination: url)\n                        .font(.subheadline)\n                        .padding(.top, 2)\n                }\n            }\n            \n            Spacer()\n        }\n        .padding()\n        .background(\n            RoundedRectangle(cornerRadius: 12, style: .continuous)\n                .fill(isSelected ? Color.effectiveAccent.opacity(0.15) : Color.clear)\n        )\n        .overlay(\n            RoundedRectangle(cornerRadius: 12, style: .continuous)\n                .stroke(isSelected ? Color.effectiveAccent : Color.secondary.opacity(0.3), lineWidth: 1.5)\n        )\n        .contentShape(Rectangle())\n    }\n}\n\n\nextension MediaControllerType {\n    var description: String {\n        switch self {\n        case .nowPlaying:\n            return \"Works with most media apps, including browsers, to detect what's playing. Note: This may be removed in a future macOS version.\"\n        case .spotify:\n            return \"Connects directly to the Spotify app.\"\n        case .appleMusic:\n            return \"Connects directly to the Apple Music app.\"\n        case .youtubeMusic:\n            return \"Requires a third-party client with API plugin enabled.\"\n        }\n    }\n}\n\n#Preview {\n    MusicControllerSelectionView(onContinue: {})\n        .frame(width: 400, height: 600)\n}\n"
  },
  {
    "path": "boringNotch/components/Onboarding/OnboardingFinishView.swift",
    "content": "//\n//  OnboardingFinishView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-06-23.\n//\n\n\nimport SwiftUI\n\nstruct OnboardingFinishView: View {\n    let onFinish: () -> Void\n    let onOpenSettings: () -> Void\n\n    var body: some View {\n        VStack(spacing: 20) {\n            Spacer()\n\n            Image(systemName: \"sparkles\")\n                .font(.system(size: 60))\n                .foregroundColor(.effectiveAccent)\n                .padding()\n\n            Text(\"You're All Set!\")\n                .font(.largeTitle)\n                .fontWeight(.bold)\n\n            Text(\"You can now enjoy the app. If you want to tweak things further, you can always visit the settings.\")\n                .font(.body)\n                .foregroundColor(.secondary)\n                .multilineTextAlignment(.center)\n                .padding(.horizontal, 40)\n            \n            Spacer()\n            Spacer()\n\n            VStack(spacing: 12) {\n                Button(action: onOpenSettings) {\n                    Label(\"Customize in Settings\", systemImage: \"gear\")\n                        .controlSize(.large)\n                }\n                .controlSize(.large)\n\n                Button(\"Finish\", action: onFinish)\n                    .buttonStyle(.borderedProminent)\n                    .controlSize(.large)\n                    .keyboardShortcut(.defaultAction)\n            }\n            .padding(24)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .background(\n            VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)\n                .ignoresSafeArea()\n        )\n    }\n}\n\n#Preview {\n    OnboardingFinishView(onFinish: { }, onOpenSettings: { })\n}\n"
  },
  {
    "path": "boringNotch/components/Onboarding/OnboardingView.swift",
    "content": "//\n//  OnboardingView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-06-23.\n//\n\nimport SwiftUI\nimport AVFoundation\n\nenum OnboardingStep {\n    case welcome\n    case cameraPermission\n    case calendarPermission\n    case remindersPermission\n    case accessibilityPermission\n    case musicPermission\n    case finished\n}\n\nprivate let calendarService = CalendarService()\n\nstruct OnboardingView: View {\n    @State var step: OnboardingStep = .welcome\n    let onFinish: () -> Void\n    let onOpenSettings: () -> Void\n\n    var body: some View {\n        ZStack {\n            switch step {\n            case .welcome:\n                WelcomeView {\n                    withAnimation(.easeInOut(duration: 0.6)) {\n                        step = .cameraPermission\n                    }\n                }\n                .transition(.opacity)\n\n            case .cameraPermission:\n                PermissionRequestView(\n                    icon: Image(systemName: \"camera.fill\"),\n                    title: \"Enable Camera Access\",\n                    description: \"Boring Notch includes a mirror feature that lets you quickly check your appearance using your camera, right from the notch. Camera access is required only to show this live preview. You can turn the mirror feature on or off at any time in the app.\",\n                    privacyNote: \"Your camera is never used without your consent, and nothing is recorded or stored.\",\n                    onAllow: {\n                        Task {\n                            await requestCameraPermission()\n                            withAnimation(.easeInOut(duration: 0.6)) {\n                                step = .calendarPermission\n                            }\n                        }\n                    },\n                    onSkip: {\n                        withAnimation(.easeInOut(duration: 0.6)) {\n                            step = .calendarPermission\n                        }\n                    }\n                )\n                .transition(.opacity)\n\n            case .calendarPermission:\n                PermissionRequestView(\n                    icon: Image(systemName: \"calendar\"),\n                    title: \"Enable Calendar Access\",\n                    description: \"Boring Notch can show all your upcoming events in one place. Access to your calendar is needed to display your schedule.\",\n                    privacyNote: \"Your calendar data is only used to show your events and is never shared.\",\n                    onAllow: {\n                        Task {\n                                await requestCalendarPermission()\n                                withAnimation(.easeInOut(duration: 0.6)) {\n                                    step = .remindersPermission\n                                }\n                        }\n                    },\n                    onSkip: {\n                            withAnimation(.easeInOut(duration: 0.6)) {\n                                step = .remindersPermission\n                            }\n                    }\n                )\n                .transition(.opacity)\n\n                case .remindersPermission:\n                    PermissionRequestView(\n                        icon: Image(systemName: \"checklist\"),\n                        title: \"Enable Reminders Access\",\n                        description: \"Boring Notch can show your scheduled reminders alongside your calendar events. Access to Reminders is needed to display your reminders.\",\n                        privacyNote: \"Your reminders data is only used to show your reminders and is never shared.\",\n                        onAllow: {\n                            Task {\n                                await requestRemindersPermission()\n                                withAnimation(.easeInOut(duration: 0.6)) {\n                                    step = .accessibilityPermission\n                                }\n                            }\n                        },\n                        onSkip: {\n                            withAnimation(.easeInOut(duration: 0.6)) {\n                                step = .accessibilityPermission\n                            }\n                        }\n                    )\n                    .transition(.opacity)\n                \n            case .accessibilityPermission:\n                PermissionRequestView(\n                    icon: Image(systemName: \"hand.raised.fill\"),\n                    title: \"Enable Accessibility Access\",\n                    description: \"Accessibility access is required to replace system notifications with the Boring Notch HUD. This allows the app to intercept media and brightness events to display custom HUD overlays.\",\n                    privacyNote: \"Accessibility access is used only to improve media and brightness notifications. No data is collected or shared.\",\n                    onAllow: {\n                        Task {\n                            await requestAccessibilityPermission()\n                            withAnimation(.easeInOut(duration: 0.6)) {\n                                step = .musicPermission\n                            }\n                        }\n                    },\n                    onSkip: {\n                        withAnimation(.easeInOut(duration: 0.6)) {\n                            step = .musicPermission\n                        }\n                    }\n                )\n                .transition(.opacity)\n                \n            case .musicPermission:\n                MusicControllerSelectionView(\n                    onContinue: {\n                        withAnimation(.easeInOut(duration: 0.6)) {\n                            BoringViewCoordinator.shared.firstLaunch = false\n                            step = .finished\n                        }\n                    }\n                )\n                .transition(.opacity)\n\n            case .finished:\n                OnboardingFinishView(onFinish: onFinish, onOpenSettings: onOpenSettings)\n            }\n        }\n        .frame(width: 400, height: 600)\n    }\n\n    // MARK: - Permission Request Logic\n\n    func requestCameraPermission() async {\n        await AVCaptureDevice.requestAccess(for: .video)\n    }\n\n    func requestCalendarPermission() async {\n        _ = try? await calendarService.requestAccess(to: .event)\n    }\n\n    func requestRemindersPermission() async {\n        _ = try? await calendarService.requestAccess(to: .reminder)\n    }\n    \n    func requestAccessibilityPermission() async {\n        await XPCHelperClient.shared.ensureAccessibilityAuthorization(promptIfNeeded: true)\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Onboarding/PermissionsRequestView.swift",
    "content": "//\n//  PermissionsRequestView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-06-23.\n//\n\nimport SwiftUI\n\nstruct PermissionRequestView: View {\n    let icon: Image\n    let title: String\n    let description: String\n    let privacyNote: String?\n    let onAllow: () -> Void\n    let onSkip: () -> Void\n\n    var body: some View {\n        VStack(spacing: 28) {\n            icon\n                .resizable()\n                .scaledToFit()\n                .frame(width: 70, height: 56)\n                .foregroundColor(.effectiveAccent)\n                .padding(.top, 32)\n\n            Text(title)\n                .font(.title)\n                .fontWeight(.semibold)\n\n            Text(description)\n                .multilineTextAlignment(.center)\n                .padding(.horizontal)\n\n            if let privacyNote = privacyNote {\n                HStack(spacing: 8) {\n                    Image(systemName: \"lock.shield\")\n                        .foregroundColor(.secondary)\n                    Text(privacyNote)\n                        .font(.subheadline)\n                        .foregroundColor(.secondary)\n                        .multilineTextAlignment(.leading)\n                }\n                .padding(.bottom, 8)\n                .padding(.horizontal)\n            }\n\n            HStack {\n                Button(\"Not Now\") { onSkip() }\n                    .buttonStyle(.bordered)\n                Button(\"Allow Access\") { onAllow() }\n                    .buttonStyle(.borderedProminent)\n            }\n            .padding(.top, 10)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)\n        .background(\n            VisualEffectView(material: .underWindowBackground, blendingMode: .behindWindow)\n                .ignoresSafeArea()\n        )\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Onboarding/SparkleView.swift",
    "content": "//\n//  SparkleView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 2024. 09. 26..\n//\n\nimport SwiftUI\nimport AppKit\n\nclass SparkleNSView: NSView {\n    private var emitterLayer: CAEmitterLayer?\n    \n    override init(frame frameRect: NSRect) {\n        super.init(frame: frameRect)\n        self.wantsLayer = true\n        setupEmitterLayer()\n    }\n    \n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n    \n    private func setupEmitterLayer() {\n        let emitterLayer = CAEmitterLayer()\n        emitterLayer.emitterShape = .rectangle\n        emitterLayer.emitterMode = .surface\n        emitterLayer.renderMode = .oldestFirst\n        \n        let cell = CAEmitterCell()\n        cell.contents = NSImage(named: \"sparkle\")?.cgImage(forProposedRect: nil, context: nil, hints: nil)\n        cell.birthRate = 50\n        cell.lifetime = 5\n        cell.velocity = 10\n        cell.velocityRange = 5\n        cell.emissionRange = .pi * 2\n        cell.scale = 0.2\n        cell.scaleRange = 0.1\n        cell.alphaSpeed = -0.5\n        cell.yAcceleration = 10 // Add a slight downward motion\n        \n        emitterLayer.emitterCells = [cell]\n        \n        self.layer?.addSublayer(emitterLayer)\n        self.emitterLayer = emitterLayer\n        \n        updateEmitterForCurrentBounds()\n    }\n    \n    private func updateEmitterForCurrentBounds() {\n        guard let emitterLayer = self.emitterLayer else { return }\n        \n        emitterLayer.frame = self.bounds\n        emitterLayer.emitterSize = self.bounds.size\n        emitterLayer.emitterPosition = CGPoint(x: bounds.width / 2, y: bounds.height / 2)\n        \n        // Adjust birth rate based on view size\n        let area = bounds.width * bounds.height\n        let baseBirthRate: Float = 50\n        let adjustedBirthRate = 20 // Assuming 200x200 as base size\n        emitterLayer.emitterCells?.first?.birthRate = Float(adjustedBirthRate)\n    }\n    \n    override func setFrameSize(_ newSize: NSSize) {\n        super.setFrameSize(newSize)\n        updateEmitterForCurrentBounds()\n    }\n}\n\nstruct SparkleView: NSViewRepresentable {\n    func makeNSView(context: Context) -> SparkleNSView {\n        return SparkleNSView()\n    }\n    \n    func updateNSView(_ nsView: SparkleNSView, context: Context) {}\n}\n"
  },
  {
    "path": "boringNotch/components/Onboarding/WelcomeView.swift",
    "content": "//\n//  WelcomeView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 2024. 09. 26..\n//\n\nimport SwiftUI\nimport SwiftUIIntrospect\n\nstruct WelcomeView: View {\n    var onGetStarted: (() -> Void)? = nil\n    var body: some View {\n        ZStack(alignment: .top) {\n            ZStack {\n                Image(\"spotlight\")\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .padding(.bottom)\n                    .blur(radius: 3)\n                    .offset(y: -5)\n                    .background(SparkleView().opacity(0.6))\n                VStack(spacing: 8) {\n                    Image(\"logo2\")\n                        .resizable()\n                        .aspectRatio(contentMode: .fit)\n                        .frame(width: 100, height: 100)\n                        .padding(.bottom, 8)\n                    Text(\"Boring Notch\")\n                        .font(.system(.largeTitle, design: .default))\n                        .fontWeight(.semibold)\n                    Text(\"Welcome\")\n                        .font(.title)\n                        .foregroundStyle(.secondary)\n                        .padding(.bottom, 30)\n                    if false {\n                        Text(\"PRO\")\n                            .font(.system(size: 18, design: .rounded))\n                            .fontWeight(.bold)\n                            .foregroundStyle(.white)\n                            .padding(.horizontal, 12)\n                            .padding(.vertical, 3)\n                            .background(\n                                Capsule()\n                                    .fill(LinearGradient(colors: [.white.opacity(0.7), .white.opacity(0.3)], startPoint: .topLeading, endPoint: .bottomTrailing))\n                                    .strokeBorder(LinearGradient(stops: [.init(color: .white.opacity(0.7), location: 0.3), .init(color: .clear, location: 0.6)], startPoint: .topLeading, endPoint: .bottomTrailing))\n                                    .blendMode(.overlay)\n                            )\n                            .padding(.bottom, 30)\n                    }\n\n\n                    Button {\n                        onGetStarted?()\n                    } label: {\n                        Text(\"Get started\")\n                            .padding(.horizontal, 20)\n                            .padding(.vertical, 6)\n                    }\n                    .buttonStyle(BorderedProminentButtonStyle())\n                }\n                .padding(.top)\n            }\n            \n            Image(\"theboringteam\")\n                .resizable()\n                .aspectRatio(contentMode: .fit)\n                .frame(height: 22)\n                .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .bottom)\n                .padding()\n                .padding(.bottom, 36)\n                .blendMode(.overlay)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)\n        .ignoresSafeArea()\n        .background {\n            VisualEffectView(material: .hudWindow, blendingMode: .behindWindow)\n                .ignoresSafeArea()\n        }\n    }\n}\n\n#Preview {\n    WelcomeView()\n}\n"
  },
  {
    "path": "boringNotch/components/ProgressIndicator.swift",
    "content": "    //\n    //  ProgressIndicator.swift\n    //  boringNotch\n    //\n    //  Created by Harsh Vardhan  Goswami  on 11/08/24.\n    //\n\nimport Foundation\nimport SwiftUI\n\nstruct CircularProgressView: View {\n    let progress: Double\n    let color: Color\n    \n    var body: some View {\n        ZStack {\n            Circle()\n                .stroke(\n                    Color.white.opacity(0.2),\n                    lineWidth: 6\n                )\n            Circle()\n                .trim(from: 0, to: progress)\n                .stroke(\n                    color,\n                    // 1\n                    style: StrokeStyle(\n                        lineWidth: 6,\n                        lineCap: .round\n                    )\n                )\n                .rotationEffect(.degrees(-90))\n        }\n    }\n}\n\nenum ProgressIndicatorType {\n    case circle\n    case text\n}\n\n\n    // based on type .circle or .text\nstruct ProgressIndicator: View {\n    var type: ProgressIndicatorType\n    var progress: Double\n    var color: Color\n    \n    var body: some View {\n        switch type {\n            case .circle:\n                CircularProgressView(progress: progress, color: color).frame(\n                width: 20, height: 20)\n            case .text:\n                Text(\"\\(Int(progress * 100))%\")\n        }\n    }\n}\n\n#Preview {\n    ProgressIndicator(type: .circle, progress: 0.8, color: Color.blue).padding()\n        .frame(width: 200, height: 200)\n}\n"
  },
  {
    "path": "boringNotch/components/Settings/EditPanelView.swift",
    "content": "//\n//  EditPanelView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 12/08/2024.\n//\n\nimport SwiftUI\n\nstruct EditPanelView: View {\n    @State var wallpaperPath: URL?\n    var body: some View {\n        VStack {\n            HStack {\n                Text(\"Edit layout\")\n                    .font(.system(.largeTitle, design: .rounded))\n                    .foregroundColor(.white.opacity(0.5))\n                Spacer()\n                Button {\n                    exit(0)\n                } label: {\n                    Label(\"Close\", systemImage: \"xmark\")\n                }\n                .controlSize(.extraLarge)\n                .buttonStyle(AccessoryBarButtonStyle())\n            }\n            .padding()\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)\n    }\n}\n\n#Preview {\n    EditPanelView()\n}\n\nstruct VisualEffectView: NSViewRepresentable {\n    let material: NSVisualEffectView.Material\n    let blendingMode: NSVisualEffectView.BlendingMode\n    \n    func makeNSView(context _: Context) -> NSVisualEffectView {\n        let visualEffectView = NSVisualEffectView()\n        visualEffectView.material = material\n        visualEffectView.blendingMode = blendingMode\n        visualEffectView.state = NSVisualEffectView.State.active\n        visualEffectView.isEmphasized = true\n        return visualEffectView\n    }\n    \n    func updateNSView(_ visualEffectView: NSVisualEffectView, context _: Context) {\n        visualEffectView.material = material\n        visualEffectView.blendingMode = blendingMode\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Settings/ListItemPopover.swift",
    "content": "//\n//  ListItemPopover.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 15/09/2024.\n//\n\nimport SwiftUI\n\nstruct ListItemPopover<Content: View>: View {\n    let content: () -> Content\n    \n    @State private var isPresented: Bool = false\n    var body: some View {\n        Button {\n            isPresented.toggle()\n        } label: {\n            Image(systemName: \"info.circle\")\n                .foregroundStyle(.secondary)\n        }\n        .controlSize(.regular)\n        .popover(isPresented: $isPresented, attachmentAnchor: .rect(.bounds), arrowEdge: .trailing, content: {\n            content()\n                .padding()\n        })\n    }\n}\n\n"
  },
  {
    "path": "boringNotch/components/Settings/MusicSlotConfigurationView.swift",
    "content": "//\n//  MusicSlotConfigurationView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-11-17.\n//\n\nimport Defaults\nimport SwiftUI\nimport UniformTypeIdentifiers\n\nstruct MusicSlotConfigurationView: View {\n    @Default(.musicControlSlots) private var musicControlSlots\n    @ObservedObject private var musicManager = MusicManager.shared\n    @State private var draggedSlot: MusicControlButton?\n\n    private let fixedSlotCount: Int = 5\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 12) {\n            // Slot configuration (fixed 5)\n            slotConfigurationSection\n\n            // Reset button\n            HStack {\n                Spacer()\n                Button(\"Reset to Defaults\") {\n                    withAnimation {\n                        musicControlSlots = MusicControlButton.defaultLayout\n                    }\n                }\n                .buttonStyle(.borderless)\n            }\n        }\n        .onAppear {\n            ensureSlotCapacity(fixedSlotCount)\n        }\n    }\n\n    private var previewSection: some View {\n        HStack(alignment: .top, spacing: 12) {\n            HStack(spacing: 6) {\n                ForEach(0..<fixedSlotCount, id: \\.self) { index in\n                    let slot = slotValue(at: index)\n                    Group {\n                        if slot != .none {\n                            slotPreview(for: slot)\n                                .frame(maxWidth: 44)\n                                .onDrag {\n                                    // remember what's being dragged for UX\n                                    DispatchQueue.main.async { draggedSlot = slot }\n                                    return NSItemProvider(object: NSString(string: \"slot:\\(index)\"))\n                                }\n                                .onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in\n                                    let handled = handleDrop(providers, toIndex: index)\n                                    // clear drag state\n                                    DispatchQueue.main.async { draggedSlot = nil }\n                                    return handled\n                                }\n                        } else {\n                            // empty slot: allow drops but do not allow dragging\n                            slotPreview(for: slot)\n                                .frame(maxWidth: 44)\n                                .onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in\n                                    let handled = handleDrop(providers, toIndex: index)\n                                    DispatchQueue.main.async { draggedSlot = nil }\n                                    return handled\n                                }\n                        }\n                    }\n                }\n            }\n            .padding(12)\n            .background(Color(NSColor.controlBackgroundColor))\n            .cornerRadius(8)\n\n            VStack(spacing: 8) {\n                ZStack {\n                    RoundedRectangle(cornerRadius: 10)\n                        .fill(Color(NSColor.controlBackgroundColor))\n                        .frame(width: 56, height: 56)\n\n                    Image(systemName: \"trash\")\n                        .font(.system(size: 16, weight: .medium))\n                        .foregroundStyle(Color.primary)\n                }\n                .cornerRadius(10)\n                .contentShape(RoundedRectangle(cornerRadius: 10))\n                .onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in\n                    return handleDropOnTrash(providers)\n                }\n\n                Text(\"Clear slot\")\n                    .font(.caption2)\n                    .foregroundStyle(.secondary)\n                    .multilineTextAlignment(.center)\n                    .lineLimit(2)\n                    .frame(width: 72)\n            }\n        }\n    }\n\n    private var slotConfigurationSection: some View {\n        VStack(alignment: .leading, spacing: 8) {\n            HStack {\n                Text(\"Layout Preview\")\n                    .font(.headline)\n                    .foregroundStyle(.secondary)\n                Spacer()\n                Text(\"Drag items in the preview to reorder or drop from the palette\")\n                    .font(.subheadline)\n                    .foregroundStyle(.secondary)\n            }\n            previewSection\n\n            Divider()\n\n            VStack(alignment: .leading, spacing: 6) {\n                Text(\"Drag a control onto a slot\")\n                    .font(.caption)\n                    .foregroundStyle(.secondary)\n\n                ScrollView(.horizontal) {\n                    HStack(spacing: 12) {\n                        ForEach(MusicControlButton.pickerOptions, id: \\.self) { control in\n                            VStack(spacing: 6) {\n                                ZStack {\n                                    RoundedRectangle(cornerRadius: 8)\n                                        .fill(Color(NSColor.controlBackgroundColor))\n                                        .frame(width: 44, height: 44)\n\n                                    if control != .none {\n                                        Image(systemName: control.iconName)\n                                            .font(.system(size: control.prefersLargeScale ? 18 : 15, weight: .medium))\n                                            .foregroundStyle(control == .none ? Color.secondary : Color.primary)\n                                            .frame(width: 28, height: 28)\n                                    }\n                                }\n                                .cornerRadius(8)\n                                .contentShape(RoundedRectangle(cornerRadius: 8))\n                                .onDrag {\n                                    return NSItemProvider(object: NSString(string: \"control:\\(control.rawValue)\"))\n                                }\n                                .onTapGesture {\n                                    if let idx = musicControlSlots.firstIndex(of: .none) {\n                                        updateSlot(control, at: idx)\n                                    } else {\n                                        withAnimation { updateSlot(control, at: 0) }\n                                    }\n                                }\n\n                                Text(control.label)\n                                    .font(.caption2)\n                                    .foregroundStyle(.secondary)\n                                    .frame(width: 60)\n                                    .multilineTextAlignment(.center)\n                                    .lineLimit(2)\n                            }\n                        }\n                    }\n                    .padding(.vertical, 4)\n                }\n                .scrollIndicators(.visible)\n            }\n        }\n    }\n\n    private func slotConfigRow(for index: Int) -> some View {\n        let currentSlot = slotValue(at: index)\n\n        return HStack(spacing: 12) {\n            Text(\"\\(index + 1)\")\n                .font(.system(size: 14, weight: .medium, design: .monospaced))\n                .foregroundStyle(.secondary)\n                .frame(width: 20)\n\n            Group {\n                if currentSlot != .none {\n                    slotPreview(for: currentSlot)\n                        .frame(height: 32)\n                        .onDrag {\n                            DispatchQueue.main.async { draggedSlot = currentSlot }\n                            return NSItemProvider(object: NSString(string: \"slot:\\(index)\"))\n                        }\n                        .onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in\n                            let handled = handleDrop(providers, toIndex: index)\n                            DispatchQueue.main.async { draggedSlot = nil }\n                            return handled\n                        }\n                } else {\n                    // empty slot: allow drops but not dragging\n                    slotPreview(for: currentSlot)\n                        .frame(height: 32)\n                        .onDrop(of: [UTType.plainText.identifier], isTargeted: nil) { providers in\n                            let handled = handleDrop(providers, toIndex: index)\n                            DispatchQueue.main.async { draggedSlot = nil }\n                            return handled\n                        }\n                }\n            }\n\n            Spacer()\n        }\n        .padding(8)\n        .background(Color(NSColor.controlBackgroundColor).opacity(0.5))\n        .cornerRadius(6)\n    }\n\n    @ViewBuilder\n    private func slotPreview(for slot: MusicControlButton) -> some View {\n        ZStack {\n            RoundedRectangle(cornerRadius: 8)\n                .fill(Color(NSColor.controlBackgroundColor))\n                .frame(width: 44, height: 44)\n\n            if slot != .none {\n                Image(systemName: slot.iconName)\n                    .font(.system(size: slot.prefersLargeScale ? 18 : 15, weight: .medium))\n                    .foregroundStyle(previewIconColor(for: slot))\n                    .frame(width: 28, height: 28)\n            } else {\n                RoundedRectangle(cornerRadius: 6)\n                    .strokeBorder(style: StrokeStyle(lineWidth: 1, dash: [4, 4]))\n                    .foregroundStyle(Color.secondary.opacity(0.3))\n                    .frame(width: 32, height: 32)\n            }\n        }\n        .cornerRadius(8)\n        .contentShape(RoundedRectangle(cornerRadius: 8))\n    }\n\n    private func previewIconColor(for slot: MusicControlButton) -> Color {\n        switch slot {\n        case .shuffle:\n            return musicManager.isShuffled ? .red : .primary\n        case .repeatMode:\n            return musicManager.repeatMode != .off ? .red : .primary\n        case .favorite:\n            return musicManager.isFavoriteTrack ? .red : .primary\n        case .playPause:\n            return .primary\n        default:\n            return .primary\n        }\n    }\n\n    private func ensureSlotCapacity(_ target: Int) {\n        guard target > musicControlSlots.count else { return }\n        let missing = target - musicControlSlots.count\n        musicControlSlots.append(contentsOf: Array(repeating: .none, count: missing))\n    }\n\n    private func slotBinding(for index: Int) -> Binding<MusicControlButton> {\n        Binding(\n            get: { slotValue(at: index) },\n            set: { newValue in updateSlot(newValue, at: index) }\n        )\n    }\n\n    private func slotValue(at index: Int) -> MusicControlButton {\n        guard musicControlSlots.indices.contains(index) else { return .none }\n        return musicControlSlots[index]\n    }\n\n    private func handleDrop(_ providers: [NSItemProvider], toIndex: Int) -> Bool {\n        for provider in providers {\n            if provider.canLoadObject(ofClass: NSString.self) {\n                provider.loadObject(ofClass: NSString.self) { item, error in\n                    // item may be an NSString (which conforms to NSItemProviderReading) or other reading type\n                    if let nsstring = item as? NSString {\n                        let raw = nsstring as String\n                        DispatchQueue.main.async {\n                            processDropString(raw, toIndex: toIndex)\n                        }\n                    } else if let str = item as? String {\n                        DispatchQueue.main.async {\n                            processDropString(str, toIndex: toIndex)\n                        }\n                    }\n                }\n                return true\n            }\n        }\n        return false\n    }\n\n    private func handleDropOnTrash(_ providers: [NSItemProvider]) -> Bool {\n        for provider in providers {\n            if provider.canLoadObject(ofClass: NSString.self) {\n                provider.loadObject(ofClass: NSString.self) { item, error in\n                    if let nsstring = item as? NSString {\n                        let raw = nsstring as String\n                        DispatchQueue.main.async {\n                            if raw.hasPrefix(\"slot:\") {\n                                // parse source slot index and clear it\n                                let from = Int(raw.replacingOccurrences(of: \"slot:\", with: \"\")) ?? -1\n                                guard from >= 0 && from < fixedSlotCount else { return }\n                                var slots = musicControlSlots\n                                if from < slots.count {\n                                    slots[from] = .none\n                                    musicControlSlots = slots\n                                }\n                            }\n                        }\n                    } else if let str = item as? String {\n                        DispatchQueue.main.async {\n                            if str.hasPrefix(\"slot:\") {\n                                let from = Int(str.replacingOccurrences(of: \"slot:\", with: \"\")) ?? -1\n                                guard from >= 0 && from < fixedSlotCount else { return }\n                                var slots = musicControlSlots\n                                if from < slots.count {\n                                    slots[from] = .none\n                                    musicControlSlots = slots\n                                }\n                            }\n                        }\n                    }\n                }\n                return true\n            }\n        }\n        return false\n    }\n\n    private func processDropString(_ raw: String, toIndex: Int) {\n        if raw.hasPrefix(\"slot:\") {\n            let from = Int(raw.replacingOccurrences(of: \"slot:\", with: \"\")) ?? -1\n            guard from >= 0 && from < fixedSlotCount else { return }\n            var slots = musicControlSlots\n            if from < slots.count && toIndex < slots.count {\n                slots.swapAt(from, toIndex)\n                musicControlSlots = slots\n            }\n        } else if raw.hasPrefix(\"control:\") {\n            let val = raw.replacingOccurrences(of: \"control:\", with: \"\")\n            if let control = MusicControlButton(rawValue: val) {\n                // If this control already exists in another slot, clear that original slot\n                var slots = musicControlSlots\n                if let existing = slots.firstIndex(of: control), existing != toIndex {\n                    slots[existing] = .none\n                    musicControlSlots = slots\n                }\n\n                updateSlot(control, at: toIndex)\n            }\n        }\n    }\n\n    private func updateSlot(_ value: MusicControlButton, at index: Int) {\n        var slots = musicControlSlots\n        if index >= slots.count {\n            slots.append(contentsOf: Array(repeating: .none, count: index - slots.count + 1))\n        }\n        slots[index] = value\n        musicControlSlots = slots\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Settings/SettingsView.swift",
    "content": "//\n//  SettingsView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 07/08/2024.\n//\n\nimport AVFoundation\nimport Defaults\nimport EventKit\nimport KeyboardShortcuts\nimport LaunchAtLogin\nimport Sparkle\nimport SwiftUI\nimport SwiftUIIntrospect\n\nstruct SettingsView: View {\n    @State private var selectedTab = \"General\"\n    @State private var accentColorUpdateTrigger = UUID()\n\n    let updaterController: SPUStandardUpdaterController?\n\n    init(updaterController: SPUStandardUpdaterController? = nil) {\n        self.updaterController = updaterController\n    }\n\n    var body: some View {\n        NavigationSplitView {\n            List(selection: $selectedTab) {\n                NavigationLink(value: \"General\") {\n                    Label(\"General\", systemImage: \"gear\")\n                }\n                NavigationLink(value: \"Appearance\") {\n                    Label(\"Appearance\", systemImage: \"eye\")\n                }\n                NavigationLink(value: \"Media\") {\n                    Label(\"Media\", systemImage: \"play.laptopcomputer\")\n                }\n                NavigationLink(value: \"Calendar\") {\n                    Label(\"Calendar\", systemImage: \"calendar\")\n                }\n                NavigationLink(value: \"HUD\") {\n                    Label(\"HUDs\", systemImage: \"dial.medium.fill\")\n                }\n                NavigationLink(value: \"Battery\") {\n                    Label(\"Battery\", systemImage: \"battery.100.bolt\")\n                }\n//                NavigationLink(value: \"Downloads\") {\n//                    Label(\"Downloads\", systemImage: \"square.and.arrow.down\")\n//                }\n                NavigationLink(value: \"Shelf\") {\n                    Label(\"Shelf\", systemImage: \"books.vertical\")\n                }\n                NavigationLink(value: \"Shortcuts\") {\n                    Label(\"Shortcuts\", systemImage: \"keyboard\")\n                }\n                // NavigationLink(value: \"Extensions\") {\n                //     Label(\"Extensions\", systemImage: \"puzzlepiece.extension\")\n                // }\n                NavigationLink(value: \"Advanced\") {\n                    Label(\"Advanced\", systemImage: \"gearshape.2\")\n                }\n                NavigationLink(value: \"About\") {\n                    Label(\"About\", systemImage: \"info.circle\")\n                }\n            }\n            .listStyle(SidebarListStyle())\n            .tint(.effectiveAccent)\n            .toolbar(removing: .sidebarToggle)\n            .navigationSplitViewColumnWidth(200)\n        } detail: {\n            Group {\n                switch selectedTab {\n                case \"General\":\n                    GeneralSettings()\n                case \"Appearance\":\n                    Appearance()\n                case \"Media\":\n                    Media()\n                case \"Calendar\":\n                    CalendarSettings()\n                case \"HUD\":\n                    HUD()\n                case \"Battery\":\n                    Charge()\n                case \"Shelf\":\n                    Shelf()\n                case \"Shortcuts\":\n                    Shortcuts()\n                case \"Extensions\":\n                    GeneralSettings()\n                case \"Advanced\":\n                    Advanced()\n                case \"About\":\n                    if let controller = updaterController {\n                        About(updaterController: controller)\n                    } else {\n                        // Fallback with a default controller\n                        About(\n                            updaterController: SPUStandardUpdaterController(\n                                startingUpdater: false, updaterDelegate: nil,\n                                userDriverDelegate: nil))\n                    }\n                default:\n                    GeneralSettings()\n                }\n            }\n            .frame(maxWidth: .infinity, maxHeight: .infinity)\n        }\n        .navigationSplitViewStyle(.balanced)\n        .toolbar(removing: .sidebarToggle)\n        .toolbar {\n            ToolbarItem(placement: .principal) {\n                Text(\"\")\n                    .frame(width: 0, height: 0)\n                    .accessibilityHidden(true)\n            }\n        }\n        .formStyle(.grouped)\n        .frame(width: 700)\n        .background(Color(NSColor.windowBackgroundColor))\n        .tint(.effectiveAccent)\n        .id(accentColorUpdateTrigger)\n        .onReceive(NotificationCenter.default.publisher(for: NSNotification.Name(\"AccentColorChanged\"))) { _ in\n            accentColorUpdateTrigger = UUID()\n        }\n    }\n}\n\nstruct GeneralSettings: View {\n    @State private var screens: [(uuid: String, name: String)] = NSScreen.screens.compactMap { screen in\n        guard let uuid = screen.displayUUID else { return nil }\n        return (uuid, screen.localizedName)\n    }\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n\n    @Default(.mirrorShape) var mirrorShape\n    @Default(.showEmojis) var showEmojis\n    @Default(.gestureSensitivity) var gestureSensitivity\n    @Default(.minimumHoverDuration) var minimumHoverDuration\n    @Default(.nonNotchHeight) var nonNotchHeight\n    @Default(.nonNotchHeightMode) var nonNotchHeightMode\n    @Default(.notchHeight) var notchHeight\n    @Default(.notchHeightMode) var notchHeightMode\n    @Default(.showOnAllDisplays) var showOnAllDisplays\n    @Default(.automaticallySwitchDisplay) var automaticallySwitchDisplay\n    @Default(.enableGestures) var enableGestures\n    @Default(.openNotchOnHover) var openNotchOnHover\n    \n\n    var body: some View {\n        Form {\n            Section {\n                Toggle(isOn: Binding(\n                    get: { Defaults[.menubarIcon] },\n                    set: { Defaults[.menubarIcon] = $0 }\n                )) {\n                    Text(\"Show menu bar icon\")\n                }\n                .tint(.effectiveAccent)\n                LaunchAtLogin.Toggle(\"Launch at login\")\n                Defaults.Toggle(key: .showOnAllDisplays) {\n                    Text(\"Show on all displays\")\n                }\n                .onChange(of: showOnAllDisplays) {\n                    NotificationCenter.default.post(\n                        name: Notification.Name.showOnAllDisplaysChanged, object: nil)\n                }\n                Picker(\"Preferred display\", selection: $coordinator.preferredScreenUUID) {\n                    ForEach(screens, id: \\.uuid) { screen in\n                        Text(screen.name).tag(screen.uuid as String?)\n                    }\n                }\n                .onChange(of: NSScreen.screens) {\n                    screens = NSScreen.screens.compactMap { screen in\n                        guard let uuid = screen.displayUUID else { return nil }\n                        return (uuid, screen.localizedName)\n                    }\n                }\n                .disabled(showOnAllDisplays)\n                \n                Defaults.Toggle(key: .automaticallySwitchDisplay) {\n                    Text(\"Automatically switch displays\")\n                }\n                    .onChange(of: automaticallySwitchDisplay) {\n                        NotificationCenter.default.post(\n                            name: Notification.Name.automaticallySwitchDisplayChanged, object: nil)\n                    }\n                    .disabled(showOnAllDisplays)\n            } header: {\n                Text(\"System features\")\n            }\n\n            Section {\n                Picker(\n                    selection: $notchHeightMode,\n                    label:\n                        Text(\"Notch height on notch displays\")\n                ) {\n                    Text(\"Match real notch height\")\n                        .tag(WindowHeightMode.matchRealNotchSize)\n                    Text(\"Match menu bar height\")\n                        .tag(WindowHeightMode.matchMenuBar)\n                    Text(\"Custom height\")\n                        .tag(WindowHeightMode.custom)\n                }\n                .onChange(of: notchHeightMode) {\n                    switch notchHeightMode {\n                    case .matchRealNotchSize:\n                        notchHeight = 38\n                    case .matchMenuBar:\n                        notchHeight = 44\n                    case .custom:\n                        notchHeight = 38\n                    }\n                    NotificationCenter.default.post(\n                        name: Notification.Name.notchHeightChanged, object: nil)\n                }\n                if notchHeightMode == .custom {\n                    Slider(value: $notchHeight, in: 15...45, step: 1) {\n                        Text(\"Custom notch size - \\(notchHeight, specifier: \"%.0f\")\")\n                    }\n                    .onChange(of: notchHeight) {\n                        NotificationCenter.default.post(\n                            name: Notification.Name.notchHeightChanged, object: nil)\n                    }\n                }\n                Picker(\"Notch height on non-notch displays\", selection: $nonNotchHeightMode) {\n                    Text(\"Match menubar height\")\n                        .tag(WindowHeightMode.matchMenuBar)\n                    Text(\"Match real notch height\")\n                        .tag(WindowHeightMode.matchRealNotchSize)\n                    Text(\"Custom height\")\n                        .tag(WindowHeightMode.custom)\n                }\n                .onChange(of: nonNotchHeightMode) {\n                    switch nonNotchHeightMode {\n                    case .matchMenuBar:\n                        nonNotchHeight = 24\n                    case .matchRealNotchSize:\n                        nonNotchHeight = 32\n                    case .custom:\n                        nonNotchHeight = 32\n                    }\n                    NotificationCenter.default.post(\n                        name: Notification.Name.notchHeightChanged, object: nil)\n                }\n                if nonNotchHeightMode == .custom {\n                    Slider(value: $nonNotchHeight, in: 0...40, step: 1) {\n                        Text(\"Custom notch size - \\(nonNotchHeight, specifier: \"%.0f\")\")\n                    }\n                    .onChange(of: nonNotchHeight) {\n                        NotificationCenter.default.post(\n                            name: Notification.Name.notchHeightChanged, object: nil)\n                    }\n                }\n            } header: {\n                Text(\"Notch sizing\")\n            }\n\n            NotchBehaviour()\n\n            gestureControls()\n        }\n        .toolbar {\n            Button(\"Quit app\") {\n                NSApp.terminate(self)\n            }\n            .controlSize(.extraLarge)\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"General\")\n        .onChange(of: openNotchOnHover) {\n            if !openNotchOnHover {\n                enableGestures = true\n            }\n        }\n    }\n\n    @ViewBuilder\n    func gestureControls() -> some View {\n        Section {\n            Defaults.Toggle(key: .enableGestures) {\n                Text(\"Enable gestures\")\n            }\n                .disabled(!openNotchOnHover)\n            if enableGestures {\n                Toggle(\"Change media with horizontal gestures\", isOn: .constant(false))\n                    .disabled(true)\n                Defaults.Toggle(key: .closeGestureEnabled) {\n                    Text(\"Close gesture\")\n                }\n                Slider(value: $gestureSensitivity, in: 100...300, step: 100) {\n                    HStack {\n                        Text(\"Gesture sensitivity\")\n                        Spacer()\n                        Text(\n                            Defaults[.gestureSensitivity] == 100\n                                ? \"High\" : Defaults[.gestureSensitivity] == 200 ? \"Medium\" : \"Low\"\n                        )\n                        .foregroundStyle(.secondary)\n                    }\n                }\n            }\n        } header: {\n            HStack {\n                Text(\"Gesture control\")\n                customBadge(text: \"Beta\")\n            }\n        } footer: {\n            Text(\n                \"Two-finger swipe up on notch to close, two-finger swipe down on notch to open when **Open notch on hover** option is disabled\"\n            )\n            .multilineTextAlignment(.trailing)\n            .foregroundStyle(.secondary)\n            .font(.caption)\n        }\n    }\n\n    @ViewBuilder\n    func NotchBehaviour() -> some View {\n        Section {\n            Defaults.Toggle(key: .openNotchOnHover) {\n                Text(\"Open notch on hover\")\n            }\n            Defaults.Toggle(key: .enableHaptics) {\n                    Text(\"Enable haptic feedback\")\n            }\n            Toggle(\"Remember last tab\", isOn: $coordinator.openLastTabByDefault)\n            if openNotchOnHover {\n                Slider(value: $minimumHoverDuration, in: 0...1, step: 0.1) {\n                    HStack {\n                        Text(\"Hover delay\")\n                        Spacer()\n                        Text(\"\\(minimumHoverDuration, specifier: \"%.1f\")s\")\n                            .foregroundStyle(.secondary)\n                    }\n                }\n                .onChange(of: minimumHoverDuration) {\n                    NotificationCenter.default.post(\n                        name: Notification.Name.notchHeightChanged, object: nil)\n                }\n            }\n        } header: {\n            Text(\"Notch behavior\")\n        }\n    }\n}\n\nstruct Charge: View {\n    var body: some View {\n        Form {\n            Section {\n                Defaults.Toggle(key: .showBatteryIndicator) {\n                    Text(\"Show battery indicator\")\n                }\n                Defaults.Toggle(key: .showPowerStatusNotifications) {\n                    Text(\"Show power status notifications\")\n                }\n            } header: {\n                Text(\"General\")\n            }\n            Section {\n                Defaults.Toggle(key: .showBatteryPercentage) {\n                    Text(\"Show battery percentage\")\n                }\n                Defaults.Toggle(key: .showPowerStatusIcons) {\n                    Text(\"Show power status icons\")\n                }\n            } header: {\n                Text(\"Battery Information\")\n            }\n        }\n        .onAppear {\n            Task { @MainActor in\n                await XPCHelperClient.shared.isAccessibilityAuthorized()\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Battery\")\n    }\n}\n\n//struct Downloads: View {\n//    @Default(.selectedDownloadIndicatorStyle) var selectedDownloadIndicatorStyle\n//    @Default(.selectedDownloadIconStyle) var selectedDownloadIconStyle\n//    var body: some View {\n//        Form {\n//            warningBadge(\"We don't support downloads yet\", \"It will be supported later on.\")\n//            Section {\n//                Defaults.Toggle(key: .enableDownloadListener) {\n//                    Text(\"Show download progress\")\n//                }\n//                    .disabled(true)\n//                Defaults.Toggle(key: .enableSafariDownloads) {\n//                    Text(\"Enable Safari Downloads\")\n//                }\n//                    .disabled(!Defaults[.enableDownloadListener])\n//                Picker(\"Download indicator style\", selection: $selectedDownloadIndicatorStyle) {\n//                    Text(\"Progress bar\")\n//                        .tag(DownloadIndicatorStyle.progress)\n//                    Text(\"Percentage\")\n//                        .tag(DownloadIndicatorStyle.percentage)\n//                }\n//                Picker(\"Download icon style\", selection: $selectedDownloadIconStyle) {\n//                    Text(\"Only app icon\")\n//                        .tag(DownloadIconStyle.onlyAppIcon)\n//                    Text(\"Only download icon\")\n//                        .tag(DownloadIconStyle.onlyIcon)\n//                    Text(\"Both\")\n//                        .tag(DownloadIconStyle.iconAndAppIcon)\n//                }\n//\n//            } header: {\n//                HStack {\n//                    Text(\"Download indicators\")\n//                    comingSoonTag()\n//                }\n//            }\n//            Section {\n//                List {\n//                    ForEach([].indices, id: \\.self) { index in\n//                        Text(\"\\(index)\")\n//                    }\n//                }\n//                .frame(minHeight: 96)\n//                .overlay {\n//                    if true {\n//                        Text(\"No excluded apps\")\n//                            .foregroundStyle(Color(.secondaryLabelColor))\n//                    }\n//                }\n//                .actionBar(padding: 0) {\n//                    Group {\n//                        Button {\n//                        } label: {\n//                            Image(systemName: \"plus\")\n//                                .frame(width: 25, height: 16, alignment: .center)\n//                                .contentShape(Rectangle())\n//                                .foregroundStyle(.secondary)\n//                        }\n//\n//                        Divider()\n//                        Button {\n//                        } label: {\n//                            Image(systemName: \"minus\")\n//                                .frame(width: 20, height: 16, alignment: .center)\n//                                .contentShape(Rectangle())\n//                                .foregroundStyle(.secondary)\n//                        }\n//                    }\n//                }\n//            } header: {\n//                HStack(spacing: 4) {\n//                    Text(\"Exclude apps\")\n//                    comingSoonTag()\n//                }\n//            }\n//        }\n//        .navigationTitle(\"Downloads\")\n//    }\n//}\n\nstruct HUD: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @Default(.inlineHUD) var inlineHUD\n    @Default(.enableGradient) var enableGradient\n    @Default(.optionKeyAction) var optionKeyAction\n    @Default(.hudReplacement) var hudReplacement\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @State private var accessibilityAuthorized = false\n    \n    var body: some View {\n        Form {\n            Section {\n                HStack {\n                    VStack(alignment: .leading, spacing: 2) {\n                        Text(\"Replace system HUD\")\n                            .font(.headline)\n                        Text(\"Replaces the standard macOS volume, display brightness, and keyboard brightness HUDs with a custom design.\")\n                            .font(.subheadline)\n                            .foregroundStyle(.secondary)\n                            .fixedSize(horizontal: false, vertical: true)\n                    }\n                    Spacer(minLength: 40)\n                    Defaults.Toggle(\"\", key: .hudReplacement)\n                    .labelsHidden()\n                    .toggleStyle(.switch)\n                    .controlSize(.large)\n                    .disabled(!accessibilityAuthorized)\n                }\n                \n                if !accessibilityAuthorized {\n                    VStack(alignment: .leading, spacing: 8) {\n                        Text(\"Accessibility access is required to replace the system HUD.\")\n                            .font(.subheadline)\n                            .foregroundStyle(.secondary)\n\n                        HStack(spacing: 12) {\n                            Button(\"Request Accessibility\") {\n                                XPCHelperClient.shared.requestAccessibilityAuthorization()\n                            }\n                            .buttonStyle(.borderedProminent)\n                        }\n                    }\n                    .padding(.top, 6)\n                }\n            }\n            \n            Section {\n                Picker(\"Option key behaviour\", selection: $optionKeyAction) {\n                    ForEach(OptionKeyAction.allCases) { opt in\n                        Text(opt.rawValue).tag(opt)\n                    }\n                }\n                \n                Picker(\"Progress bar style\", selection: $enableGradient) {\n                    Text(\"Hierarchical\")\n                        .tag(false)\n                    Text(\"Gradient\")\n                        .tag(true)\n                }\n                Defaults.Toggle(key: .systemEventIndicatorShadow) {\n                    Text(\"Enable glowing effect\")\n                }\n                Defaults.Toggle(key: .systemEventIndicatorUseAccent) {\n                    Text(\"Tint progress bar with accent color\")\n                }\n            } header: {\n                Text(\"General\")\n            }\n            .disabled(!hudReplacement)\n            \n            Section {\n                Defaults.Toggle(key: .showOpenNotchHUD) {\n                    Text(\"Show HUD in open notch\")\n                }\n                Defaults.Toggle(key: .showOpenNotchHUDPercentage) {\n                    Text(\"Show percentage\")\n                }\n                .disabled(!Defaults[.showOpenNotchHUD])\n            } header: {\n                HStack {\n                    Text(\"Open Notch\")\n                    customBadge(text: \"Beta\")\n                }\n            }\n            .disabled(!hudReplacement)\n            \n            Section {\n                Picker(\"HUD style\", selection: $inlineHUD) {\n                    Text(\"Default\")\n                        .tag(false)\n                    Text(\"Inline\")\n                        .tag(true)\n                }\n                .onChange(of: Defaults[.inlineHUD]) {\n                    if Defaults[.inlineHUD] {\n                        withAnimation {\n                            Defaults[.systemEventIndicatorShadow] = false\n                            Defaults[.enableGradient] = false\n                        }\n                    }\n                }\n                \n                Defaults.Toggle(key: .showClosedNotchHUDPercentage) {\n                    Text(\"Show percentage\")\n                }\n            } header: {\n                Text(\"Closed Notch\")\n            }\n            .disabled(!Defaults[.hudReplacement])\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"HUDs\")\n        .task {\n            accessibilityAuthorized = await XPCHelperClient.shared.isAccessibilityAuthorized()\n        }\n        .onAppear {\n            XPCHelperClient.shared.startMonitoringAccessibilityAuthorization()\n        }\n        .onDisappear {\n            XPCHelperClient.shared.stopMonitoringAccessibilityAuthorization()\n        }\n        .onReceive(NotificationCenter.default.publisher(for: .accessibilityAuthorizationChanged)) { notification in\n            if let granted = notification.userInfo?[\"granted\"] as? Bool {\n                accessibilityAuthorized = granted\n            }\n        }\n    }\n}\n\nstruct Media: View {\n    @Default(.waitInterval) var waitInterval\n    @Default(.mediaController) var mediaController\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @Default(.hideNotchOption) var hideNotchOption\n    @Default(.enableSneakPeek) private var enableSneakPeek\n    @Default(.sneakPeekStyles) var sneakPeekStyles\n\n    @Default(.enableLyrics) var enableLyrics\n\n    var body: some View {\n        Form {\n            Section {\n                Picker(\"Music Source\", selection: $mediaController) {\n                    ForEach(availableMediaControllers) { controller in\n                        Text(controller.rawValue).tag(controller)\n                    }\n                }\n                .onChange(of: mediaController) { _, _ in\n                    NotificationCenter.default.post(\n                        name: Notification.Name.mediaControllerChanged,\n                        object: nil\n                    )\n                }\n            } header: {\n                Text(\"Media Source\")\n            } footer: {\n                if MusicManager.shared.isNowPlayingDeprecated {\n                    HStack {\n                        Text(\"YouTube Music requires this third-party app to be installed: \")\n                            .foregroundStyle(.secondary)\n                            .font(.caption)\n                        Link(\n                            \"https://github.com/pear-devs/pear-desktop\",\n                            destination: URL(string: \"https://github.com/pear-devs/pear-desktop\")!\n                        )\n                        .font(.caption)\n                        .foregroundColor(.blue)  // Ensures it's visibly a link\n                    }\n                } else {\n                    Text(\n                        \"'Now Playing' was the only option on previous versions and works with all media apps.\"\n                    )\n                    .foregroundStyle(.secondary)\n                    .font(.caption)\n                }\n            }\n            \n            Section {\n                Toggle(\n                    \"Show music live activity\",\n                    isOn: $coordinator.musicLiveActivityEnabled.animation()\n                )\n                Toggle(\"Show sneak peek on playback changes\", isOn: $enableSneakPeek)\n                Picker(\"Sneak Peek Style\", selection: $sneakPeekStyles) {\n                    ForEach(SneakPeekStyle.allCases) { style in\n                        Text(style.rawValue).tag(style)\n                    }\n                }\n                HStack {\n                    Stepper(value: $waitInterval, in: 0...10, step: 1) {\n                        HStack {\n                            Text(\"Media inactivity timeout\")\n                            Spacer()\n                            Text(\"\\(Defaults[.waitInterval], specifier: \"%.0f\") seconds\")\n                                .foregroundStyle(.secondary)\n                        }\n                    }\n                }\n                Picker(\n                    selection: $hideNotchOption,\n                    label:\n                        HStack {\n                            Text(\"Full screen behavior\")\n                            customBadge(text: \"Beta\")\n                        }\n                ) {\n                    Text(\"Hide for all apps\").tag(HideNotchOption.always)\n                    Text(\"Hide for media app only\").tag(\n                        HideNotchOption.nowPlayingOnly)\n                    Text(\"Never hide\").tag(HideNotchOption.never)\n                }\n            } header: {\n                Text(\"Media playback live activity\")\n            }\n            \n            Section {\n                MusicSlotConfigurationView()\n                Defaults.Toggle(key: .enableLyrics) {\n                    HStack {\n                        Text(\"Show lyrics below artist name\")\n                        customBadge(text: \"Beta\")\n                    }\n                }\n            } header: {\n                Text(\"Media controls\")\n            }  footer: {\n                Text(\"Customize which controls appear in the music player. Volume expands when active.\")\n                    .font(.caption)\n                    .foregroundStyle(.secondary)\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Media\")\n    }\n\n    // Only show controller options that are available on this macOS version\n    private var availableMediaControllers: [MediaControllerType] {\n        if MusicManager.shared.isNowPlayingDeprecated {\n            return MediaControllerType.allCases.filter { $0 != .nowPlaying }\n        } else {\n            return MediaControllerType.allCases\n        }\n    }\n}\n\nstruct CalendarSettings: View {\n    @ObservedObject private var calendarManager = CalendarManager.shared\n    @Default(.showCalendar) var showCalendar: Bool\n    @Default(.hideCompletedReminders) var hideCompletedReminders\n    @Default(.hideAllDayEvents) var hideAllDayEvents\n    @Default(.autoScrollToNextEvent) var autoScrollToNextEvent\n\n    var body: some View {\n        Form {\n            Defaults.Toggle(key: .showCalendar) {\n                Text(\"Show calendar\")\n            }\n            Defaults.Toggle(key: .hideCompletedReminders) {\n                Text(\"Hide completed reminders\")\n            }\n            Defaults.Toggle(key: .hideAllDayEvents) {\n                Text(\"Hide all-day events\")\n            }\n            Defaults.Toggle(key: .autoScrollToNextEvent) {\n                Text(\"Auto-scroll to next event\")\n            }\n            Defaults.Toggle(key: .showFullEventTitles) {\n                Text(\"Always show full event titles\")\n            }\n            Section(header: Text(\"Calendars\")) {\n                if calendarManager.calendarAuthorizationStatus != .fullAccess {\n                    Text(\"Calendar access is denied. Please enable it in System Settings.\")\n                        .foregroundColor(.red)\n                        .multilineTextAlignment(.center)\n                        .padding()\n                    Button(\"Open Calendar Settings\") {\n                        if let settingsURL = URL(\n                            string:\n                                \"x-apple.systempreferences:com.apple.preference.security?Privacy_Calendars\"\n                        ) {\n                            NSWorkspace.shared.open(settingsURL)\n                        }\n                    }\n                } else {\n                    List {\n                        ForEach(calendarManager.eventCalendars, id: \\.id) { calendar in\n                            Toggle(\n                                isOn: Binding(\n                                    get: { calendarManager.getCalendarSelected(calendar) },\n                                    set: { isSelected in\n                                        Task {\n                                            await calendarManager.setCalendarSelected(\n                                                calendar, isSelected: isSelected)\n                                        }\n                                    }\n                                )\n                            ) {\n                                Text(calendar.title)\n                            }\n                            .accentColor(lighterColor(from: calendar.color))\n                            .disabled(!showCalendar)\n                        }\n                    }\n                }\n            }\n            Section(header: Text(\"Reminders\")) {\n                if calendarManager.reminderAuthorizationStatus != .fullAccess {\n                    Text(\"Reminder access is denied. Please enable it in System Settings.\")\n                        .foregroundColor(.red)\n                        .multilineTextAlignment(.center)\n                        .padding()\n                    Button(\"Open Reminder Settings\") {\n                        if let settingsURL = URL(\n                            string:\n                                \"x-apple.systempreferences:com.apple.preference.security?Privacy_Reminders\"\n                        ) {\n                            NSWorkspace.shared.open(settingsURL)\n                        }\n                    }\n                } else {\n                    List {\n                        ForEach(calendarManager.reminderLists, id: \\.id) { calendar in\n                            Toggle(\n                                isOn: Binding(\n                                    get: { calendarManager.getCalendarSelected(calendar) },\n                                    set: { isSelected in\n                                        Task {\n                                            await calendarManager.setCalendarSelected(\n                                                calendar, isSelected: isSelected)\n                                        }\n                                    }\n                                )\n                            ) {\n                                Text(calendar.title)\n                            }\n                            .accentColor(lighterColor(from: calendar.color))\n                            .disabled(!showCalendar)\n                        }\n                    }\n                }\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Calendar\")\n        .onAppear {\n            Task {\n                await calendarManager.checkCalendarAuthorization()\n                await calendarManager.checkReminderAuthorization()\n            }\n        }\n    }\n}\n\nfunc lighterColor(from nsColor: NSColor, amount: CGFloat = 0.14) -> Color {\n    let srgb = nsColor.usingColorSpace(.sRGB) ?? nsColor\n    var (r, g, b, a): (CGFloat, CGFloat, CGFloat, CGFloat) = (0,0,0,0)\n    srgb.getRed(&r, green: &g, blue: &b, alpha: &a)\n\n    func lighten(_ c: CGFloat) -> CGFloat {\n        let increased = c + (1.0 - c) * amount\n        return min(max(increased, 0), 1)\n    }\n\n    let nr = lighten(r)\n    let ng = lighten(g)\n    let nb = lighten(b)\n\n    return Color(red: Double(nr), green: Double(ng), blue: Double(nb), opacity: Double(a))\n}\n\nstruct About: View {\n    @State private var showBuildNumber: Bool = false\n    let updaterController: SPUStandardUpdaterController\n    @Environment(\\.openWindow) var openWindow\n    var body: some View {\n        VStack {\n            Form {\n                Section {\n                    HStack {\n                        Text(\"Release name\")\n                        Spacer()\n                        Text(Defaults[.releaseName])\n                            .foregroundStyle(.secondary)\n                    }\n                    HStack {\n                        Text(\"Version\")\n                        Spacer()\n                        if showBuildNumber {\n                            Text(\"(\\(Bundle.main.buildVersionNumber ?? \"\"))\")\n                                .foregroundStyle(.secondary)\n                        }\n                        Text(Bundle.main.releaseVersionNumber ?? \"unkown\")\n                            .foregroundStyle(.secondary)\n                    }\n                    .onTapGesture {\n                        withAnimation {\n                            showBuildNumber.toggle()\n                        }\n                    }\n                } header: {\n                    Text(\"Version info\")\n                }\n\n                UpdaterSettingsView(updater: updaterController.updater)\n\n                HStack(spacing: 30) {\n                    Spacer(minLength: 0)\n                    Button {\n                        if let url = URL(string: \"https://github.com/TheBoredTeam/boring.notch\") {\n                            NSWorkspace.shared.open(url)\n                        }\n                    } label: {\n                        VStack(spacing: 5) {\n                            Image(\"Github\")\n                                .resizable()\n                                .aspectRatio(contentMode: .fit)\n                                .frame(width: 18)\n                            Text(\"GitHub\")\n                        }\n                        .contentShape(Rectangle())\n                    }\n                    Spacer(minLength: 0)\n                }\n                .buttonStyle(PlainButtonStyle())\n            }\n            VStack(spacing: 0) {\n                Divider()\n                Text(\"Made with 🫶🏻 by not so boring not.people\")\n                    .foregroundStyle(.secondary)\n                    .padding(.top, 5)\n                    .padding(.bottom, 7)\n                    .multilineTextAlignment(.center)\n                    .padding(.horizontal, 10)\n            }\n            .frame(maxWidth: .infinity, alignment: .center)\n        }\n        .toolbar {\n            //            Button(\"Welcome window\") {\n            //                openWindow(id: \"onboarding\")\n            //            }\n            //            .controlSize(.extraLarge)\n            CheckForUpdatesView(updater: updaterController.updater)\n        }\n        .navigationTitle(\"About\")\n    }\n}\n\nstruct Shelf: View {\n    \n    @Default(.shelfTapToOpen) var shelfTapToOpen: Bool\n    @Default(.quickShareProvider) var quickShareProvider\n    @Default(.expandedDragDetection) var expandedDragDetection: Bool\n    @StateObject private var quickShareService = QuickShareService.shared\n\n    private var selectedProvider: QuickShareProvider? {\n        quickShareService.availableProviders.first(where: { $0.id == quickShareProvider })\n    }\n    \n    init() {\n        Task { await QuickShareService.shared.discoverAvailableProviders() }\n    }\n    \n    var body: some View {\n        Form {\n            Section {\n                Defaults.Toggle(key: .boringShelf) {\n                    Text(\"Enable shelf\")\n                }\n                Defaults.Toggle(key: .openShelfByDefault) {\n                    Text(\"Open shelf by default if items are present\")\n                }\n                Defaults.Toggle(key: .expandedDragDetection) {\n                    Text(\"Expanded drag detection area\")\n                }\n                .onChange(of: expandedDragDetection) {\n                    NotificationCenter.default.post(\n                        name: Notification.Name.expandedDragDetectionChanged,\n                        object: nil\n                    )\n                }\n                Defaults.Toggle(key: .copyOnDrag) {\n                    Text(\"Copy items on drag\")\n                }\n                Defaults.Toggle(key: .autoRemoveShelfItems) {\n                    Text(\"Remove from shelf after dragging\")\n                }\n\n            } header: {\n                HStack {\n                    Text(\"General\")\n                }\n            }\n            \n            Section {\n                Picker(\"Quick Share Service\", selection: $quickShareProvider) {\n                    ForEach(quickShareService.availableProviders, id: \\.id) { provider in\n                        HStack {\n                            Group {\n                                if let imgData = provider.imageData, let nsImg = NSImage(data: imgData) {\n                                    Image(nsImage: nsImg)\n                                        .resizable()\n                                        .aspectRatio(contentMode: .fit)\n                                } else {\n                                    Image(systemName: \"square.and.arrow.up\")\n                                }\n                            }\n                            .frame(width: 16, height: 16)\n                            .foregroundColor(.accentColor)\n                            Text(provider.id)\n                        }\n                        .tag(provider.id)\n                    }\n                }\n                .pickerStyle(.menu)\n                \n                if let selectedProvider = selectedProvider {\n                    HStack {\n                        Group {\n                            if let imgData = selectedProvider.imageData, let nsImg = NSImage(data: imgData) {\n                                Image(nsImage: nsImg)\n                                    .resizable()\n                                    .aspectRatio(contentMode: .fit)\n                            } else {\n                                Image(systemName: \"square.and.arrow.up\")\n                            }\n                        }\n                        .frame(width: 16, height: 16)\n                        .foregroundColor(.accentColor)\n                        VStack(alignment: .leading, spacing: 2) {\n                            Text(\"Currently selected: \\(selectedProvider.id)\")\n                                .font(.caption)\n                                .foregroundColor(.secondary)\n                            Text(\"Files dropped on the shelf will be shared via this service\")\n                                .font(.caption2)\n                                .foregroundColor(.secondary)\n                        }\n                    }\n                    .padding(.vertical, 4)\n                }\n                // Providers are always enabled; user can pick default service above.\n                \n            } header: {\n                HStack {\n                    Text(\"Quick Share\")\n                }\n            } footer: {\n                Text(\"Choose which service to use when sharing files from the shelf. Click the shelf button to select files, or drag files onto it to share immediately.\")\n                    .font(.caption)\n                    .foregroundColor(.secondary)\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Shelf\")\n    }\n}\n\n//struct Extensions: View {\n//    @State private var effectTrigger: Bool = false\n//    var body: some View {\n//        Form {\n//            Section {\n//                List {\n//                    ForEach(extensionManager.installedExtensions.indices, id: \\.self) { index in\n//                        let item = extensionManager.installedExtensions[index]\n//                        HStack {\n//                            AppIcon(for: item.bundleIdentifier)\n//                                .resizable()\n//                                .frame(width: 24, height: 24)\n//                            Text(item.name)\n//                            ListItemPopover {\n//                                Text(\"Description\")\n//                            }\n//                            Spacer(minLength: 0)\n//                            HStack(spacing: 6) {\n//                                Circle()\n//                                    .frame(width: 6, height: 6)\n//                                    .foregroundColor(\n//                                        isExtensionRunning(item.bundleIdentifier)\n//                                            ? .green : item.status == .disabled ? .gray : .red\n//                                    )\n//                                    .conditionalModifier(isExtensionRunning(item.bundleIdentifier))\n//                                { view in\n//                                    view\n//                                        .shadow(color: .green, radius: 3)\n//                                }\n//                                Text(\n//                                    isExtensionRunning(item.bundleIdentifier)\n//                                        ? \"Running\"\n//                                        : item.status == .disabled ? \"Disabled\" : \"Stopped\"\n//                                )\n//                                .contentTransition(.numericText())\n//                                .foregroundStyle(.secondary)\n//                                .font(.footnote)\n//                            }\n//                            .frame(width: 60, alignment: .leading)\n//\n//                            Menu(\n//                                content: {\n//                                    Button(\"Restart\") {\n//                                        let ws = NSWorkspace.shared\n//\n//                                        if let ext = ws.runningApplications.first(where: {\n//                                            $0.bundleIdentifier == item.bundleIdentifier\n//                                        }) {\n//                                            ext.terminate()\n//                                        }\n//\n//                                        if let appURL = ws.urlForApplication(\n//                                            withBundleIdentifier: item.bundleIdentifier)\n//                                        {\n//                                            ws.openApplication(\n//                                                at: appURL, configuration: .init(),\n//                                                completionHandler: nil)\n//                                        }\n//                                    }\n//                                    .keyboardShortcut(\"R\", modifiers: .command)\n//                                    Button(\"Disable\") {\n//                                        if let ext = NSWorkspace.shared.runningApplications.first(\n//                                            where: { $0.bundleIdentifier == item.bundleIdentifier })\n//                                        {\n//                                            ext.terminate()\n//                                        }\n//                                        extensionManager.installedExtensions[index].status =\n//                                            .disabled\n//                                    }\n//                                    .keyboardShortcut(\"D\", modifiers: .command)\n//                                    Divider()\n//                                    Button(\"Uninstall\", role: .destructive) {\n//                                        //\n//                                    }\n//                                },\n//                                label: {\n//                                    Image(systemName: \"ellipsis.circle\")\n//                                        .foregroundStyle(.secondary)\n//                                }\n//                            )\n//                            .controlSize(.regular)\n//                        }\n//                        .buttonStyle(PlainButtonStyle())\n//                        .padding(.vertical, 5)\n//                    }\n//                }\n//                .frame(minHeight: 120)\n//                .actionBar {\n//                    Button {\n//                    } label: {\n//                        HStack(spacing: 3) {\n//                            Image(systemName: \"plus\")\n//                            Text(\"Add manually\")\n//                        }\n//                        .foregroundStyle(.secondary)\n//                    }\n//                    .disabled(true)\n//                    Spacer()\n//                    Button {\n//                        withAnimation(.linear(duration: 1)) {\n//                            effectTrigger.toggle()\n//                        } completion: {\n//                            effectTrigger.toggle()\n//                        }\n//                        extensionManager.checkIfExtensionsAreInstalled()\n//                    } label: {\n//                        HStack(spacing: 3) {\n//                            Image(systemName: \"arrow.triangle.2.circlepath\")\n//                                .rotationEffect(effectTrigger ? .degrees(360) : .zero)\n//                        }\n//                        .foregroundStyle(.secondary)\n//                    }\n//                }\n//                .controlSize(.small)\n//                .buttonStyle(PlainButtonStyle())\n//                .overlay {\n//                    if extensionManager.installedExtensions.isEmpty {\n//                        Text(\"No extension installed\")\n//                            .foregroundStyle(Color(.secondaryLabelColor))\n//                            .padding(.bottom, 22)\n//                    }\n//                }\n//            } header: {\n//                HStack(spacing: 0) {\n//                    Text(\"Installed extensions\")\n//                    if !extensionManager.installedExtensions.isEmpty {\n//                        Text(\" – \\(extensionManager.installedExtensions.count)\")\n//                            .foregroundStyle(.secondary)\n//                    }\n//                }\n//            }\n//        }\n//        .accentColor(.effectiveAccent)\n//        .navigationTitle(\"Extensions\")\n//        // TipsView()\n//        // .padding(.horizontal, 19)\n//    }\n//}\n\nstruct Appearance: View {\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @Default(.mirrorShape) var mirrorShape\n    @Default(.sliderColor) var sliderColor\n    @Default(.useMusicVisualizer) var useMusicVisualizer\n    @Default(.customVisualizers) var customVisualizers\n    @Default(.selectedVisualizer) var selectedVisualizer\n\n    let icons: [String] = [\"logo2\"]\n    @State private var selectedIcon: String = \"logo2\"\n    @State private var selectedListVisualizer: CustomVisualizer? = nil\n    @State private var isPresented: Bool = false\n    @State private var name: String = \"\"\n    @State private var url: String = \"\"\n    @State private var speed: CGFloat = 1.0\n    var body: some View {\n        Form {\n            Section {\n                Toggle(\"Always show tabs\", isOn: $coordinator.alwaysShowTabs)\n                Defaults.Toggle(key: .settingsIconInNotch) {\n                    Text(\"Show settings icon in notch\")\n                }\n\n            } header: {\n                Text(\"General\")\n            }\n\n            Section {\n                Defaults.Toggle(key: .coloredSpectrogram) {\n                    Text(\"Colored spectrogram\")\n                }\n                Defaults\n                    .Toggle(\"Player tinting\", key: .playerColorTinting)\n                Defaults.Toggle(key: .lightingEffect) {\n                    Text(\"Enable blur effect behind album art\")\n                }\n                Picker(\"Slider color\", selection: $sliderColor) {\n                    ForEach(SliderColorEnum.allCases, id: \\.self) { option in\n                        Text(option.rawValue)\n                    }\n                }\n            } header: {\n                Text(\"Media\")\n            }\n\n            Section {\n                Toggle(\n                    \"Use music visualizer spectrogram\",\n                    isOn: $useMusicVisualizer.animation()\n                )\n                .disabled(true)\n                if !useMusicVisualizer {\n                    if customVisualizers.count > 0 {\n                        Picker(\n                            \"Selected animation\",\n                            selection: $selectedVisualizer\n                        ) {\n                            ForEach(\n                                customVisualizers,\n                                id: \\.self\n                            ) { visualizer in\n                                Text(visualizer.name)\n                                    .tag(visualizer)\n                            }\n                        }\n                    } else {\n                        HStack {\n                            Text(\"Selected animation\")\n                            Spacer()\n                            Text(\"No custom animation available\")\n                                .foregroundStyle(.secondary)\n                        }\n                    }\n                }\n            } header: {\n                HStack {\n                    Text(\"Custom music live activity animation\")\n                    customBadge(text: \"Coming soon\")\n                }\n            }\n\n            Section {\n                List {\n                    ForEach(customVisualizers, id: \\.self) { visualizer in\n                        HStack {\n                            LottieView(\n                                url: visualizer.url, speed: visualizer.speed,\n                                loopMode: .loop\n                            )\n                            .frame(width: 30, height: 30, alignment: .center)\n                            Text(visualizer.name)\n                            Spacer(minLength: 0)\n                            if selectedVisualizer == visualizer {\n                                Text(\"selected\")\n                                    .font(.caption)\n                                    .fontWeight(.medium)\n                                    .foregroundStyle(.secondary)\n                                    .padding(.trailing, 8)\n                            }\n                        }\n                        .buttonStyle(PlainButtonStyle())\n                        .padding(.vertical, 2)\n                        .background(\n                            selectedListVisualizer != nil\n                                ? selectedListVisualizer == visualizer\n                                    ? Color.effectiveAccent : Color.clear : Color.clear,\n                            in: RoundedRectangle(cornerRadius: 5)\n                        )\n                        .contentShape(Rectangle())\n                        .onTapGesture {\n                            if selectedListVisualizer == visualizer {\n                                selectedListVisualizer = nil\n                                return\n                            }\n                            selectedListVisualizer = visualizer\n                        }\n                    }\n                }\n                .safeAreaPadding(\n                    EdgeInsets(top: 5, leading: 0, bottom: 5, trailing: 0)\n                )\n                .frame(minHeight: 120)\n                .actionBar {\n                    HStack(spacing: 5) {\n                        Button {\n                            name = \"\"\n                            url = \"\"\n                            speed = 1.0\n                            isPresented.toggle()\n                        } label: {\n                            Image(systemName: \"plus\")\n                                .foregroundStyle(.secondary)\n                                .contentShape(Rectangle())\n                        }\n                        Divider()\n                        Button {\n                            if selectedListVisualizer != nil {\n                                let visualizer = selectedListVisualizer!\n                                selectedListVisualizer = nil\n                                customVisualizers.remove(\n                                    at: customVisualizers.firstIndex(of: visualizer)!)\n                                if visualizer == selectedVisualizer && customVisualizers.count > 0 {\n                                    selectedVisualizer = customVisualizers[0]\n                                }\n                            }\n                        } label: {\n                            Image(systemName: \"minus\")\n                                .foregroundStyle(.secondary)\n                                .contentShape(Rectangle())\n                        }\n                    }\n                }\n                .controlSize(.small)\n                .buttonStyle(PlainButtonStyle())\n                .overlay {\n                    if customVisualizers.isEmpty {\n                        Text(\"No custom visualizer\")\n                            .foregroundStyle(Color(.secondaryLabelColor))\n                            .padding(.bottom, 22)\n                    }\n                }\n                .sheet(isPresented: $isPresented) {\n                    VStack(alignment: .leading) {\n                        Text(\"Add new visualizer\")\n                            .font(.largeTitle.bold())\n                            .padding(.vertical)\n                        TextField(\"Name\", text: $name)\n                        TextField(\"Lottie JSON URL\", text: $url)\n                        HStack {\n                            Text(\"Speed\")\n                            Spacer(minLength: 80)\n                            Text(\"\\(speed, specifier: \"%.1f\")s\")\n                                .multilineTextAlignment(.trailing)\n                                .foregroundStyle(.secondary)\n                            Slider(value: $speed, in: 0...2, step: 0.1)\n                        }\n                        .padding(.vertical)\n                        HStack {\n                            Button {\n                                isPresented.toggle()\n                            } label: {\n                                Text(\"Cancel\")\n                                    .frame(maxWidth: .infinity, alignment: .center)\n                            }\n\n                            Button {\n                                let visualizer: CustomVisualizer = .init(\n                                    UUID: UUID(),\n                                    name: name,\n                                    url: URL(string: url)!,\n                                    speed: speed\n                                )\n\n                                if !customVisualizers.contains(visualizer) {\n                                    customVisualizers.append(visualizer)\n                                }\n\n                                isPresented.toggle()\n                            } label: {\n                                Text(\"Add\")\n                                    .frame(maxWidth: .infinity, alignment: .center)\n                            }\n                            .buttonStyle(BorderedProminentButtonStyle())\n                        }\n                    }\n                    .textFieldStyle(RoundedBorderTextFieldStyle())\n                    .controlSize(.extraLarge)\n                    .padding()\n                }\n            } header: {\n                HStack(spacing: 0) {\n                    Text(\"Custom vizualizers (Lottie)\")\n                    if !Defaults[.customVisualizers].isEmpty {\n                        Text(\" – \\(Defaults[.customVisualizers].count)\")\n                            .foregroundStyle(.secondary)\n                    }\n                }\n            }\n\n            Section {\n                Defaults.Toggle(key: .showMirror) {\n                    Text(\"Enable boring mirror\")\n                }\n                    .disabled(!checkVideoInput())\n                Picker(\"Mirror shape\", selection: $mirrorShape) {\n                    Text(\"Circle\")\n                        .tag(MirrorShapeEnum.circle)\n                    Text(\"Square\")\n                        .tag(MirrorShapeEnum.rectangle)\n                }\n                Defaults.Toggle(key: .showNotHumanFace) {\n                    Text(\"Show cool face animation while inactive\")\n                }\n            } header: {\n                HStack {\n                    Text(\"Additional features\")\n                }\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Appearance\")\n    }\n\n    func checkVideoInput() -> Bool {\n        if AVCaptureDevice.default(for: .video) != nil {\n            return true\n        }\n\n        return false\n    }\n}\n\nstruct Advanced: View {\n    @Default(.useCustomAccentColor) var useCustomAccentColor\n    @Default(.customAccentColorData) var customAccentColorData\n    @Default(.extendHoverArea) var extendHoverArea\n    @Default(.showOnLockScreen) var showOnLockScreen\n    @Default(.hideFromScreenRecording) var hideFromScreenRecording\n    \n    @State private var customAccentColor: Color = .accentColor\n    @State private var selectedPresetColor: PresetAccentColor? = nil\n    let icons: [String] = [\"logo2\"]\n    @State private var selectedIcon: String = \"logo2\"\n    \n    // macOS accent colors\n    enum PresetAccentColor: String, CaseIterable, Identifiable {\n        case blue = \"Blue\"\n        case purple = \"Purple\"\n        case pink = \"Pink\"\n        case red = \"Red\"\n        case orange = \"Orange\"\n        case yellow = \"Yellow\"\n        case green = \"Green\"\n        case graphite = \"Graphite\"\n        \n        var id: String { self.rawValue }\n        \n        var color: Color {\n            switch self {\n            case .blue: return Color(red: 0.0, green: 0.478, blue: 1.0)\n            case .purple: return Color(red: 0.686, green: 0.322, blue: 0.871)\n            case .pink: return Color(red: 1.0, green: 0.176, blue: 0.333)\n            case .red: return Color(red: 1.0, green: 0.271, blue: 0.227)\n            case .orange: return Color(red: 1.0, green: 0.584, blue: 0.0)\n            case .yellow: return Color(red: 1.0, green: 0.8, blue: 0.0)\n            case .green: return Color(red: 0.4, green: 0.824, blue: 0.176)\n            case .graphite: return Color(red: 0.557, green: 0.557, blue: 0.576)\n            }\n        }\n    }\n    \n    var body: some View {\n        Form {\n            Section {\n                VStack(alignment: .leading, spacing: 16) {\n                    // Toggle between system and custom\n                    Picker(\"Accent color\", selection: $useCustomAccentColor) {\n                        Text(\"System\").tag(false)\n                        Text(\"Custom\").tag(true)\n                    }\n                    .pickerStyle(.segmented)\n                    \n                    if !useCustomAccentColor {\n                        // System accent info\n                        VStack(alignment: .leading, spacing: 8) {\n                            HStack(spacing: 12) {\n                                AccentCircleButton(\n                                    isSelected: true,\n                                    color: .accentColor,\n                                    isSystemDefault: true\n                                ) {}\n                                \n                                VStack(alignment: .leading, spacing: 2) {\n                                    Text(\"Using System Accent\")\n                                        .font(.body)\n                                    Text(\"Your macOS system accent color\")\n                                        .font(.caption)\n                                        .foregroundStyle(.secondary)\n                                }\n                                Spacer()\n                            }\n                        }\n                    } else {\n                        // Custom color options\n                        VStack(alignment: .leading, spacing: 12) {\n                            Text(\"Color Presets\")\n                                .font(.caption)\n                                .fontWeight(.semibold)\n                                .foregroundStyle(.secondary)\n                            \n                            HStack(spacing: 12) {\n                                ForEach(PresetAccentColor.allCases) { preset in\n                                    AccentCircleButton(\n                                        isSelected: selectedPresetColor == preset,\n                                        color: preset.color,\n                                        isMulticolor: false\n                                    ) {\n                                        selectedPresetColor = preset\n                                        customAccentColor = preset.color\n                                        saveCustomColor(preset.color)\n                                        forceUiUpdate()\n                                    }\n                                }\n                                Spacer()\n                            }\n                            \n                            Divider()\n                                .padding(.vertical, 4)\n                            \n                            // Custom color picker\n                            HStack(spacing: 12) {\n                                VStack(alignment: .leading, spacing: 2) {\n                                    Text(\"Pick a Color\")\n                                        .font(.body)\n                                    Text(\"Choose any color\")\n                                        .font(.caption)\n                                        .foregroundStyle(.secondary)\n                                }\n                                \n                                Spacer()\n                                \n                                ColorPicker(selection: Binding(\n                                    get: { customAccentColor },\n                                    set: { newColor in\n                                        customAccentColor = newColor\n                                        selectedPresetColor = nil\n                                        saveCustomColor(newColor)\n                                        forceUiUpdate()\n                                    }\n                                ), supportsOpacity: false) {\n                                    ZStack {\n                                        Circle()\n                                            .fill(customAccentColor)\n                                            .frame(width: 32, height: 32)\n                                        \n                                        if selectedPresetColor == nil {\n                                            Circle()\n                                                .strokeBorder(.primary.opacity(0.3), lineWidth: 2)\n                                                .frame(width: 32, height: 32)\n                                        }\n                                    }\n                                }\n                                .labelsHidden()\n                            }\n                        }\n                    }\n                }\n                .padding(.vertical, 4)\n            } header: {\n                Text(\"Accent color\")\n            } footer: {\n                Text(\"Choose between your system accent color or customize it with your own selection.\")\n                    .multilineTextAlignment(.trailing)\n                    .foregroundStyle(.secondary)\n                    .font(.caption)\n            }\n            .onAppear {\n                initializeAccentColorState()\n            }\n            \n            Section {\n                Defaults.Toggle(key: .enableShadow) {\n                    Text(\"Enable window shadow\")\n                }\n                Defaults.Toggle(key: .cornerRadiusScaling) {\n                    Text(\"Corner radius scaling\")\n                }\n            } header: {\n                Text(\"Window Appearance\")\n            }\n            \n            Section {\n                HStack {\n                    ForEach(icons, id: \\.self) { icon in\n                        Spacer()\n                        VStack {\n                            Image(icon)\n                                .resizable()\n                                .frame(width: 80, height: 80)\n                                .background(\n                                    RoundedRectangle(cornerRadius: 20, style: .circular)\n                                        .strokeBorder(\n                                            icon == selectedIcon ? Color.effectiveAccent : .clear,\n                                            lineWidth: 2.5\n                                        )\n                                )\n\n                            Text(\"Default\")\n                                .fontWeight(.medium)\n                                .font(.caption)\n                                .foregroundStyle(icon == selectedIcon ? .white : .secondary)\n                                .padding(.horizontal, 10)\n                                .padding(.vertical, 3)\n                                .background(\n                                    Capsule()\n                                        .fill(icon == selectedIcon ? Color.effectiveAccent : .clear)\n                                )\n                        }\n                        .onTapGesture {\n                            withAnimation {\n                                selectedIcon = icon\n                            }\n                            NSApp.applicationIconImage = NSImage(named: icon)\n                        }\n                        Spacer()\n                    }\n                }\n                .disabled(true)\n            } header: {\n                HStack {\n                    Text(\"App icon\")\n                    customBadge(text: \"Coming soon\")\n                }\n            }\n            \n            Section {\n                Defaults.Toggle(key: .extendHoverArea) {\n                    Text(\"Extend hover area\")\n                }\n                Defaults.Toggle(key: .hideTitleBar) {\n                    Text(\"Hide title bar\")\n                }\n                Defaults.Toggle(key: .showOnLockScreen) {\n                    Text(\"Show notch on lock screen\")\n                }\n                Defaults.Toggle(key: .hideFromScreenRecording) {\n                    Text(\"Hide from screen recording\")\n                }\n            } header: {\n                Text(\"Window Behavior\")\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Advanced\")\n        .onAppear {\n            loadCustomColor()\n        }\n    }\n    \n    private func forceUiUpdate() {\n        // Force refresh the UI\n        DispatchQueue.main.async {\n            NotificationCenter.default.post(name: Notification.Name(\"AccentColorChanged\"), object: nil)\n        }\n    }\n    \n    private func saveCustomColor(_ color: Color) {\n        let nsColor = NSColor(color)\n        if let colorData = try? NSKeyedArchiver.archivedData(withRootObject: nsColor, requiringSecureCoding: false) {\n            Defaults[.customAccentColorData] = colorData\n            forceUiUpdate()\n        }\n    }\n    \n    private func loadCustomColor() {\n        if let colorData = Defaults[.customAccentColorData],\n           let nsColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData) {\n            customAccentColor = Color(nsColor: nsColor)\n            \n            // Check if loaded color matches a preset\n            selectedPresetColor = nil\n            for preset in PresetAccentColor.allCases {\n                if colorsAreEqual(Color(nsColor: nsColor), preset.color) {\n                    selectedPresetColor = preset\n                    break\n                }\n            }\n        }\n    }\n    \n    private func colorsAreEqual(_ color1: Color, _ color2: Color) -> Bool {\n        let nsColor1 = NSColor(color1).usingColorSpace(.sRGB) ?? NSColor(color1)\n        let nsColor2 = NSColor(color2).usingColorSpace(.sRGB) ?? NSColor(color2)\n        \n        return abs(nsColor1.redComponent - nsColor2.redComponent) < 0.01 &&\n               abs(nsColor1.greenComponent - nsColor2.greenComponent) < 0.01 &&\n               abs(nsColor1.blueComponent - nsColor2.blueComponent) < 0.01\n    }\n    \n    private func initializeAccentColorState() {\n        if !useCustomAccentColor {\n            selectedPresetColor = nil // Multicolor is selected when useCustomAccentColor is false\n        } else {\n            loadCustomColor()\n        }\n    }\n}\n\n// MARK: - Accent Circle Button Component\nstruct AccentCircleButton: View {\n    let isSelected: Bool\n    let color: Color\n    var isSystemDefault: Bool = false\n    var isMulticolor: Bool = false\n    let action: () -> Void\n    \n    var body: some View {\n        Button(action: action) {\n            ZStack {\n                // Color circle\n                Circle()\n                    .fill(color)\n                    .frame(width: 32, height: 32)\n                \n                // Subtle border\n                Circle()\n                    .strokeBorder(Color.primary.opacity(0.15), lineWidth: 1)\n                    .frame(width: 32, height: 32)\n                \n                // Apple-style highlight ring around the middle when selected\n                if isSelected {\n                    Circle()\n                        .strokeBorder(\n                            Color.white.opacity(0.5),\n                            lineWidth: 2\n                        )\n                        .frame(width: 28, height: 28)\n                }\n            }\n        }\n        .buttonStyle(.plain)\n        .help(isSystemDefault ? \"Use your macOS system accent color\" : \"\")\n    }\n}\n\nstruct Shortcuts: View {\n    var body: some View {\n        Form {\n            Section {\n                KeyboardShortcuts.Recorder(\"Toggle Sneak Peek:\", name: .toggleSneakPeek)\n            } header: {\n                Text(\"Media\")\n            } footer: {\n                Text(\n                    \"Sneak Peek shows the media title and artist under the notch for a few seconds.\"\n                )\n                .multilineTextAlignment(.trailing)\n                .foregroundStyle(.secondary)\n                .font(.caption)\n            }\n            Section {\n                KeyboardShortcuts.Recorder(\"Toggle Notch Open:\", name: .toggleNotchOpen)\n            }\n        }\n        .accentColor(.effectiveAccent)\n        .navigationTitle(\"Shortcuts\")\n    }\n}\n\nfunc proFeatureBadge() -> some View {\n    Text(\"Upgrade to Pro\")\n        .foregroundStyle(Color(red: 0.545, green: 0.196, blue: 0.98))\n        .font(.footnote.bold())\n        .padding(.vertical, 3)\n        .padding(.horizontal, 6)\n        .background(\n            RoundedRectangle(cornerRadius: 4).stroke(\n                Color(red: 0.545, green: 0.196, blue: 0.98), lineWidth: 1))\n}\n\nfunc comingSoonTag() -> some View {\n    Text(\"Coming soon\")\n        .foregroundStyle(.secondary)\n        .font(.footnote.bold())\n        .padding(.vertical, 3)\n        .padding(.horizontal, 6)\n        .background(Color(nsColor: .secondarySystemFill))\n        .clipShape(.capsule)\n}\n\nfunc customBadge(text: String) -> some View {\n    Text(text)\n        .foregroundStyle(.secondary)\n        .font(.footnote.bold())\n        .padding(.vertical, 3)\n        .padding(.horizontal, 6)\n        .background(Color(nsColor: .secondarySystemFill))\n        .clipShape(.capsule)\n}\n\nfunc warningBadge(_ text: String, _ description: String) -> some View {\n    Section {\n        HStack(spacing: 12) {\n            Image(systemName: \"exclamationmark.triangle.fill\")\n                .font(.system(size: 22))\n                .foregroundStyle(.yellow)\n            VStack(alignment: .leading) {\n                Text(text)\n                    .font(.headline)\n                Text(description)\n                    .foregroundStyle(.secondary)\n            }\n            Spacer()\n        }\n    }\n}\n\n#Preview {\n    HUD()\n}\n"
  },
  {
    "path": "boringNotch/components/Settings/SettingsWindowController.swift",
    "content": "//\n//  SettingsWindowController.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-06-14.\n//\n\nimport AppKit\nimport SwiftUI\nimport Defaults\nimport Sparkle\n\nclass SettingsWindowController: NSWindowController {\n    static let shared = SettingsWindowController()\n    private var updaterController: SPUStandardUpdaterController?\n    \n    private init() {\n        let window = NSWindow(\n            contentRect: NSRect(x: 0, y: 0, width: 700, height: 600),\n            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],\n            backing: .buffered,\n            defer: false\n        )\n        \n        super.init(window: window)\n        \n        setupWindow()\n    }\n    \n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n    \n    func setUpdaterController(_ controller: SPUStandardUpdaterController) {\n        self.updaterController = controller\n        // Recreate the content view with the proper updater controller\n        setupWindow()\n    }\n    \n    private func setupWindow() {\n        guard let window = window else { return }\n        \n        window.title = \"Boring Notch Settings\"\n        window.titlebarAppearsTransparent = false\n        window.titleVisibility = .visible\n        window.toolbarStyle = .unified\n        window.isMovableByWindowBackground = true\n        \n        // Make it behave like a regular app window with proper Spaces support\n        window.collectionBehavior = [.managed, .participatesInCycle, .fullScreenAuxiliary]\n        \n        // Ensure proper window behavior\n        window.hidesOnDeactivate = false\n        window.isExcludedFromWindowsMenu = false\n        \n        // Configure window to be a standard document-style window\n        window.isRestorable = true\n        window.identifier = NSUserInterfaceItemIdentifier(\"BoringNotchSettingsWindow\")\n        \n        // Create the SwiftUI content\n        let settingsView = SettingsView(updaterController: updaterController)\n        let hostingView = NSHostingView(rootView: settingsView)\n        window.contentView = hostingView\n        \n        // Handle window closing\n        window.delegate = self\n    }\n    \n    func showWindow() {\n        // Set app to regular mode first\n        NSApp.setActivationPolicy(.regular)\n        \n        // If window is already visible, bring it to front properly\n        if window?.isVisible == true {\n            NSApp.activate(ignoringOtherApps: true)\n            window?.orderFrontRegardless()\n            window?.makeKeyAndOrderFront(nil)\n            return\n        }\n        \n        // Show the window with proper ordering\n        window?.orderFrontRegardless()\n        window?.makeKeyAndOrderFront(nil)\n        window?.center()\n        \n        // Activate the app and ensure window gets focus\n        NSApp.activate(ignoringOtherApps: true)\n        \n        // Force window to front after activation\n        DispatchQueue.main.async { [weak self] in\n            self?.window?.makeKeyAndOrderFront(nil)\n        }\n    }\n    \n    override func close() {\n        super.close()\n        relinquishFocus()\n    }\n    \n    private func relinquishFocus() {\n        window?.orderOut(nil)\n        \n        // Set app back to accessory mode immediately\n        NSApp.setActivationPolicy(.accessory)\n    }\n}\n\nextension SettingsWindowController: NSWindowDelegate {\n    func windowWillClose(_ notification: Notification) {\n        relinquishFocus()\n    }\n    \n    func windowShouldClose(_ sender: NSWindow) -> Bool {\n        return true\n    }\n    \n    func windowDidBecomeKey(_ notification: Notification) {\n        // Ensure app is in regular mode when window becomes key\n        NSApp.setActivationPolicy(.regular)\n    }\n    \n    func windowDidResignKey(_ notification: Notification) {\n    }\n    \n}\n"
  },
  {
    "path": "boringNotch/components/Settings/SoftwareUpdater.swift",
    "content": "//\n//  SoftwareUpdater.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 09/08/2024.\n//\n\nimport SwiftUI\nimport Sparkle\n\nfinal class CheckForUpdatesViewModel: ObservableObject {\n    @Published var canCheckForUpdates = false\n\n    init(updater: SPUUpdater) {\n        updater.publisher(for: \\.canCheckForUpdates)\n            .assign(to: &$canCheckForUpdates)\n    }\n}\n\nstruct CheckForUpdatesView: View {\n    @ObservedObject private var checkForUpdatesViewModel: CheckForUpdatesViewModel\n    private let updater: SPUUpdater\n    \n    init(updater: SPUUpdater) {\n        self.updater = updater\n        \n        // Create our view model for our CheckForUpdatesView\n        self.checkForUpdatesViewModel = CheckForUpdatesViewModel(updater: updater)\n    }\n    \n    var body: some View {\n        Button(\"Check for Updates…\", action: updater.checkForUpdates)\n            .disabled(!checkForUpdatesViewModel.canCheckForUpdates)\n    }\n}\n\nstruct UpdaterSettingsView: View {\n    private let updater: SPUUpdater\n    \n    @State private var automaticallyChecksForUpdates: Bool\n    @State private var automaticallyDownloadsUpdates: Bool\n    \n    init(updater: SPUUpdater) {\n        self.updater = updater\n        self.automaticallyChecksForUpdates = updater.automaticallyChecksForUpdates\n        self.automaticallyDownloadsUpdates = updater.automaticallyDownloadsUpdates\n    }\n    \n    var body: some View {\n        Section {\n            Toggle(\"Automatically check for updates\", isOn: $automaticallyChecksForUpdates)\n                .onChange(of: automaticallyChecksForUpdates) { _, newValue in\n                    updater.automaticallyChecksForUpdates = newValue\n                }\n            \n            Toggle(\"Automatically download updates\", isOn: $automaticallyDownloadsUpdates)\n                .disabled(!automaticallyChecksForUpdates)\n                .onChange(of: automaticallyDownloadsUpdates) { _, newValue in\n                    updater.automaticallyDownloadsUpdates = newValue\n                }\n        } header: {\n            HStack {\n                Text(\"Software updates\")\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Models/Bookmark.swift",
    "content": "//\n//  Bookmark.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-08.\n//\n\nimport Foundation\nimport AppKit\n\nstruct Bookmark: Sendable, Equatable, Codable {\n    let data: Data\n\n    init(data: Data) {\n        self.data = data\n    }\n\n    init(url: URL) throws {\n        guard url.isFileURL, FileManager.default.fileExists(atPath: url.path) else {\n            throw NSError(domain: \"Bookmark\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"Not a valid file URL or file does not exist at \\(url.path)\"])\n        }\n        do {\n            let bookmark = try url.bookmarkData(\n                options: .withSecurityScope,\n                includingResourceValuesForKeys: nil,\n                relativeTo: nil\n            )\n            NSLog(\"✅ Successfully created bookmark for \\(url.path)\")\n            self.data = bookmark\n        } catch {\n            NSLog(\"❌ Failed to create bookmark for \\(url.path): \\(error.localizedDescription)\")\n            throw error\n        }\n    }\n\n    func resolve() -> (url: URL?, refreshedData: Data?) {\n        guard !data.isEmpty else { return (nil, nil) }\n        var isStale = false\n        do {\n            let url = try URL(\n                resolvingBookmarkData: data,\n                options: [.withSecurityScope],\n                relativeTo: nil,\n                bookmarkDataIsStale: &isStale\n            )\n            if isStale, let newData = try? url.bookmarkData(options: [.withSecurityScope]) {\n                NSLog(\"⚠️ Bookmark was stale for \\(url.path), refreshed\")\n                return (url, newData)\n            }\n            return (url, nil)\n        } catch {\n            NSLog(\"❌ Failed to resolve bookmark: \\(error.localizedDescription)\")\n            return (nil, nil)\n        }\n    }\n\n    func resolveURL() -> URL? {\n        return resolve().url\n    }\n\n    var refreshedData: Data? {\n        return resolve().refreshedData\n    }\n    \n    static func update(in items: inout [ShelfItem], for item: ShelfItem, newBookmark: Data) {\n        guard let idx = items.firstIndex(where: { $0.id == item.id }) else { return }\n        guard case .file = items[idx].kind else { return }\n        items[idx].kind = ShelfItemKind.file(bookmark: newBookmark)\n    }\n\n    func validate() async -> Bool {\n        let (url, _) = resolve()\n        guard let url = url else { return false }\n        return url.accessSecurityScopedResource { url in\n            FileManager.default.fileExists(atPath: url.path)\n        }\n    }\n\n    func withAccess<T: Sendable>(_ block: @Sendable (URL) async throws -> T) async rethrows -> T? {\n        let url = resolveURL()\n        guard let url = url else { return nil }\n        return try await url.accessSecurityScopedResource { url in\n            try await block(url)\n        }\n    }\n\n    func withAccess<T>(_ block: (URL) throws -> T) rethrows -> T? {\n        let url = resolveURL()\n        guard let url = url else { return nil }\n        return try url.accessSecurityScopedResource { url in\n            try block(url)\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Models/ShelfItem.swift",
    "content": "//\n//  ShelfItem.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport AppKit\nimport Foundation\n\nenum ShelfItemKind: Codable, Equatable, Sendable {\n    case file(bookmark: Data)\n    case text(string: String)\n    case link(url: URL)\n\n    enum CodingKeys: String, CodingKey { case type, value }\n\n    enum KindTag: String, Codable { case file, text, link }\n\n    init(from decoder: Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(KindTag.self, forKey: .type)\n        switch type {\n        case .file:\n            let data = try container.decode(Data.self, forKey: .value)\n            self = .file(bookmark: data)\n        case .text:\n            self = .text(string: try container.decode(String.self, forKey: .value))\n        case .link:\n            self = .link(url: try container.decode(URL.self, forKey: .value))\n        }\n    }\n\n    func encode(to encoder: Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch self {\n        case .file(let bookmark):\n            try container.encode(KindTag.file, forKey: .type)\n            try container.encode(bookmark, forKey: .value)\n        case .text(let string):\n            try container.encode(KindTag.text, forKey: .type)\n            try container.encode(string, forKey: .value)\n        case .link(let url):\n            try container.encode(KindTag.link, forKey: .type)\n            try container.encode(url, forKey: .value)\n        }\n    }\n\n}\n\n@MainActor\nstruct ShelfItem: Identifiable, Codable, Equatable, Sendable {\n    let id: UUID\n    var kind: ShelfItemKind\n    var isTemporary: Bool\n    init(id: UUID = UUID(), kind: ShelfItemKind, isTemporary: Bool = false) {\n        self.id = id\n        self.kind = kind\n        self.isTemporary = isTemporary\n    }\n    \n    var displayName: String {\n        switch kind {\n        case .file(let bookmarkData):\n            let bookmark = Bookmark(data: bookmarkData)\n            guard let resolvedURL = bookmark.resolveURL() else { return \"\" }\n            \n            // Check for stored data files (text blocks, weblocs, etc.) to provide friendly names\n            if resolvedURL.pathExtension.lowercased() == \"json\" && resolvedURL.path.contains(\"TextBlocks\") {\n                do {\n                    let data = try Data(contentsOf: resolvedURL)\n                    let decoder = JSONDecoder()\n                    decoder.dateDecodingStrategy = .iso8601\n                    struct TextBlockData: Codable {\n                        let content: String\n                        let title: String?\n                        var displayTitle: String {\n                            if let title = title, !title.isEmpty {\n                                return title\n                            }\n                            let firstLine = content.components(separatedBy: .newlines).first ?? content\n                            if firstLine.count > 50 {\n                                return String(firstLine.prefix(47)) + \"...\"\n                            }\n                            return firstLine\n                        }\n                    }\n                    if let textData = try? decoder.decode(TextBlockData.self, from: data) {\n                        return textData.displayTitle\n                    }\n                } catch {\n                    // Fall through to default naming\n                }\n            } else if resolvedURL.pathExtension.lowercased() == \"webloc\" && resolvedURL.path.contains(\"WebLocs\") {\n                do {\n                    let data = try Data(contentsOf: resolvedURL)\n                    if let plist = try PropertyListSerialization.propertyList(from: data, format: nil) as? [String: Any],\n                       let urlString = plist[\"URL\"] as? String {\n                        let title = plist[\"Title\"] as? String\n                        return title ?? urlString\n                    }\n                } catch {\n                    // Fall through to default naming\n                }\n            }\n            return (try? resolvedURL.resourceValues(forKeys: [.localizedNameKey]).localizedName) ?? resolvedURL.lastPathComponent\n        case .text(let string):\n            return string.trimmingCharacters(in: .whitespacesAndNewlines)\n        case .link(let url):\n            let s = url.absoluteString\n            if s.hasPrefix(\"https://\") {\n                return String(s.dropFirst(\"https://\".count))\n            } else if s.hasPrefix(\"http://\") {\n                return String(s.dropFirst(\"http://\".count))\n            } else {\n                return s\n            }\n        }\n    }\n    \n    var fileURL: URL? {\n        guard case .file = kind else { return nil }\n        return ShelfStateViewModel.shared.resolveFileURL(for: self)\n    }\n    \n    var URL: URL? {\n        if case let .file(bookmark) = kind { return resolvedContext(for: bookmark)?.url }\n        else if case let .link(url) = kind { return url }\n        else { return nil }\n    }\n    \n    var icon: NSImage {\n        guard case .file = kind else {\n            return Self.thumbnailSymbolImage(systemName: kind.iconSymbolName) ?? NSImage()\n        }\n        if let resolvedURL = ShelfStateViewModel.shared.resolveFileURL(for: self) {\n            return NSWorkspace.shared.icon(forFile: resolvedURL.path)\n        }\n        return NSImage()\n    }\n    \n\n    func cleanupStoredData() {\n        guard case let .file(bookmark) = kind,\n              let context = resolvedContext(for: bookmark) else { return }\n        \n        let url = context.url\n        \n        // Handle temporary files\n        if isTemporary {\n            TemporaryFileStorageService.shared.removeTemporaryFileIfNeeded(at: url)\n            return\n        }\n    }\n}\n\nprivate extension ShelfItem {\n   static func thumbnailSymbolImage(\n        systemName: String,\n    size: CGSize = CGSize(width: 64, height: 80), \n    symbolPointSize: CGFloat = 38,\n    backgroundColor: NSColor = NSColor.white,\n    symbolColor: NSColor = NSColor.labelColor\n    ) -> NSImage? {\n        let image = NSImage(size: size)\n        image.lockFocus()\n        defer { image.unlockFocus() }\n\n        let rect = CGRect(origin: .zero, size: size)\n        let cornerRadius = min(size.width, size.height) * 0.06\n        let path = NSBezierPath(roundedRect: rect.insetBy(dx: 2, dy: 2), xRadius: cornerRadius, yRadius: cornerRadius)\n        backgroundColor.setFill()\n        path.fill()\n\n        if let symbol = NSImage(systemSymbolName: systemName, accessibilityDescription: nil) {\n            let symbolSize = CGSize(width: symbolPointSize, height: symbolPointSize)\n            let symbolOrigin = CGPoint(\n                x: (size.width - symbolSize.width) / 2,\n                y: (size.height - symbolSize.height) / 2\n            )\n            let symbolRect = CGRect(origin: symbolOrigin, size: symbolSize)\n            symbol.draw(in: symbolRect)\n        }\n\n        return image\n    }\n}\n\n// MARK: - Identity key for deduplication\nextension ShelfItem {\n    var identityKey: String {\n        switch kind {\n        case .file(let bookmark):\n            if let url = resolvedContext(for: bookmark)?.url {\n                return \"file://\" + url.standardizedFileURL.path\n            }\n            return \"file://missing/\" + bookmark.base64EncodedString()\n        case .link(let u):\n            return \"link://\" + u.absoluteString\n        case .text(let s):\n            return \"text://\" + s\n        }\n    }\n}\n\n// MARK: - Private helpers\nprivate extension ShelfItemKind {\n    var iconSymbolName: String {\n        switch self {\n        case .file:\n            return \"questionmark.circle\"\n        case .text:\n            return \"text.justifyleft\"\n        case .link:\n            return \"link\"\n        }\n    }\n}\n\nprivate extension ShelfItem {\n    func resolvedContext(for bookmarkData: Data) -> (url: URL, bookmark: Data)? {\n        let bookmark = Bookmark(data: bookmarkData)\n        if let url = bookmark.resolveURL() {\n            return (url, bookmark.refreshedData ?? bookmarkData)\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/ImageProcessingService.swift",
    "content": "//\n//  ImageProcessingService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-16.\n//\n\nimport Foundation\nimport AppKit\nimport CoreImage\nimport CoreGraphics\nimport Vision\nimport PDFKit\nimport UniformTypeIdentifiers\nimport ImageIO\n\n/// Options for image conversion\nstruct ImageConversionOptions {\n    enum ImageFormat {\n        case png, jpeg, heic, tiff, bmp\n        \n        var utType: UTType {\n            switch self {\n            case .png: return .png\n            case .jpeg: return .jpeg\n            case .heic: return .heic\n            case .tiff: return .tiff\n            case .bmp: return .bmp\n            }\n        }\n        \n        var fileExtension: String {\n            switch self {\n            case .png: return \"png\"\n            case .jpeg: return \"jpg\"\n            case .heic: return \"heic\"\n            case .tiff: return \"tiff\"\n            case .bmp: return \"bmp\"\n            }\n        }\n    }\n    \n    let format: ImageFormat\n    let compressionQuality: Double // 0.0 to 1.0, only applies to JPEG/HEIC\n    let maxDimension: CGFloat? // Max width or height, nil for no scaling\n    let removeMetadata: Bool\n}\n\n/// Service for processing images (background removal, conversion, PDF creation)\n@MainActor\nfinal class ImageProcessingService {\n    static let shared = ImageProcessingService()\n    \n    private init() {}\n    private let ciContext = CIContext(options: nil)\n    \n    // MARK: - Remove Background\n    \n    /// Removes the background from an image using Vision framework\n    func removeBackground(from url: URL) async throws -> URL? {\n        guard let inputImage = NSImage(contentsOf: url) else {\n            throw ImageProcessingError.invalidImage\n        }\n        \n        guard let cgImage = inputImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {\n            throw ImageProcessingError.invalidImage\n        }\n        \n        let request = VNGenerateForegroundInstanceMaskRequest()\n        let handler = VNImageRequestHandler(cgImage: cgImage)\n        \n        try handler.perform([request])\n        \n        guard let result = request.results?.first else {\n            throw ImageProcessingError.backgroundRemovalFailed\n        }\n        \n        let mask = try result.generateScaledMaskForImage(forInstances: result.allInstances, from: handler)\n        \n        let output = try await applyMask(mask, to: cgImage)\n        \n        let processedImage = NSImage(cgImage: output, size: inputImage.size)\n        \n        // Create temporary file\n        let originalName = url.deletingPathExtension().lastPathComponent\n        let newName = \"\\(originalName)_no_bg.png\"\n        \n        guard let pngData = processedImage.tiffRepresentation,\n              let bitmap = NSBitmapImageRep(data: pngData),\n              let finalData = bitmap.representation(using: .png, properties: [:]) else {\n            throw ImageProcessingError.saveFailed\n        }\n        \n        guard let tempURL = await TemporaryFileStorageService.shared.createTempFile(\n            for: .data(finalData, suggestedName: newName)\n        ) else {\n            throw ImageProcessingError.saveFailed\n        }\n        \n        return tempURL\n    }\n    \n    private func applyMask(_ mask: CVPixelBuffer, to image: CGImage) async throws -> CGImage {\n        let ciImage = CIImage(cgImage: image)\n        let maskImage = CIImage(cvPixelBuffer: mask)\n        \n        let filter = CIFilter.blendWithMask()\n        filter.inputImage = ciImage\n        filter.maskImage = maskImage\n        filter.backgroundImage = CIImage.empty()\n        \n        guard let output = filter.outputImage else {\n            throw ImageProcessingError.backgroundRemovalFailed\n        }\n        \n        let context = CIContext()\n        guard let result = context.createCGImage(output, from: output.extent) else {\n            throw ImageProcessingError.backgroundRemovalFailed\n        }\n        \n        return result\n    }\n    \n    // MARK: - Convert Image\n    \n    /// Converts an image with specified options\n    func convertImage(from url: URL, options: ImageConversionOptions) async throws -> URL? {\n        guard var inputImage = NSImage(contentsOf: url) else {\n            throw ImageProcessingError.invalidImage\n        }\n        \n        // Scale image if needed\n        if let maxDim = options.maxDimension {\n            inputImage = scaleImage(inputImage, maxDimension: maxDim)\n        }\n        \n        // Get image data based on format\n        let imageData: Data?\n        \n        if options.removeMetadata {\n            // Create new image without metadata\n            guard let cgImage = inputImage.cgImage(forProposedRect: nil, context: nil, hints: nil) else {\n                throw ImageProcessingError.invalidImage\n            }\n            \n            let newImage = NSImage(cgImage: cgImage, size: inputImage.size)\n            imageData = try convertToFormat(newImage, format: options.format, quality: options.compressionQuality)\n        } else {\n            imageData = try convertToFormat(inputImage, format: options.format, quality: options.compressionQuality)\n        }\n        \n        guard let data = imageData else {\n            throw ImageProcessingError.conversionFailed\n        }\n        \n        // Create temporary file\n        let originalName = url.deletingPathExtension().lastPathComponent\n        let newName = \"\\(originalName)_converted.\\(options.format.fileExtension)\"\n        \n        guard let tempURL = await TemporaryFileStorageService.shared.createTempFile(\n            for: .data(data, suggestedName: newName)\n        ) else {\n            throw ImageProcessingError.saveFailed\n        }\n        \n        return tempURL\n    }\n    \n    private func convertToFormat(_ image: NSImage, format: ImageConversionOptions.ImageFormat, quality: Double) throws -> Data? {\n        guard let tiffData = image.tiffRepresentation,\n              let bitmap = NSBitmapImageRep(data: tiffData) else {\n            return nil\n        }\n        \n        switch format {\n        case .png:\n            return bitmap.representation(using: .png, properties: [:])\n        case .jpeg:\n            let properties: [NSBitmapImageRep.PropertyKey: Any] = [\n                .compressionFactor: quality\n            ]\n            return bitmap.representation(using: .jpeg, properties: properties)\n        case .tiff:\n            let properties: [NSBitmapImageRep.PropertyKey: Any] = [\n                .compressionMethod: NSNumber(value: NSBitmapImageRep.TIFFCompression.lzw.rawValue)\n            ]\n            return bitmap.representation(using: .tiff, properties: properties)\n        case .bmp:\n            return bitmap.representation(using: .bmp, properties: [:])\n        case .heic:\n            // HEIC requires using CIContext\n            guard let cgImage = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {\n                return nil\n            }\n            let ciImage = CIImage(cgImage: cgImage)\n            let context = CIContext()\n            let colorSpace = CGColorSpace(name: CGColorSpace.sRGB)!\n            let options: [CIImageRepresentationOption: Any] = [\n                CIImageRepresentationOption(rawValue: kCGImageDestinationLossyCompressionQuality as String): quality\n            ]\n            return try? context.heifRepresentation(of: ciImage, format: .RGBA8, colorSpace: colorSpace, options: options)\n        }\n    }\n    \n    private func scaleImage(_ image: NSImage, maxDimension: CGFloat) -> NSImage {\n        guard maxDimension > 0 else { return image }\n\n        guard let srcCG = image.cgImage(forProposedRect: nil, context: nil, hints: nil) else {\n            return image\n        }\n\n        let srcMax = max(srcCG.width, srcCG.height)\n        if CGFloat(srcMax) <= maxDimension {\n            return image // no downscaling needed\n        }\n\n        let scale = maxDimension / CGFloat(srcMax)\n\n        let ciImage = CIImage(cgImage: srcCG)\n        let lanczos = CIFilter.lanczosScaleTransform()\n        lanczos.inputImage = ciImage\n        lanczos.scale = Float(scale)\n        lanczos.aspectRatio = 1.0\n\n        guard let output = lanczos.outputImage else {\n            return image\n        }\n\n        // Preserve the source color space for exact color matching\n        let colorSpace = srcCG.colorSpace ?? CGColorSpace(name: CGColorSpace.sRGB)!\n        let ciContext = CIContext(options: [.workingColorSpace: colorSpace])\n\n        // Render using the CIContext with matching color space\n        guard let dstCG = ciContext.createCGImage(output, from: output.extent, format: .RGBA8, colorSpace: colorSpace) else {\n            return image\n        }\n\n        return NSImage(cgImage: dstCG, size: NSSize(width: dstCG.width, height: dstCG.height))\n    }\n    \n    // MARK: - Create PDF\n    \n    /// Creates a PDF from multiple image URLs\n    func createPDF(from imageURLs: [URL], outputName: String? = nil) async throws -> URL? {\n        guard !imageURLs.isEmpty else {\n            throw ImageProcessingError.noImagesProvided\n        }\n        \n        let pdfDocument = PDFDocument()\n        \n        for (index, url) in imageURLs.enumerated() {\n            guard let image = NSImage(contentsOf: url) else {\n                continue\n            }\n            \n            let pdfPage = PDFPage(image: image)\n            if let page = pdfPage {\n                pdfDocument.insert(page, at: index)\n            }\n        }\n        \n        guard pdfDocument.pageCount > 0 else {\n            throw ImageProcessingError.pdfCreationFailed\n        }\n        \n        // Create temporary file\n        let name = outputName ?? \"images_\\(Date().timeIntervalSince1970).pdf\"\n        let pdfName = name.hasSuffix(\".pdf\") ? name : \"\\(name).pdf\"\n        \n        guard let pdfData = pdfDocument.dataRepresentation() else {\n            throw ImageProcessingError.pdfCreationFailed\n        }\n        \n        guard let tempURL = await TemporaryFileStorageService.shared.createTempFile(\n            for: .data(pdfData, suggestedName: pdfName)\n        ) else {\n            throw ImageProcessingError.saveFailed\n        }\n        \n        return tempURL\n    }\n    \n    // MARK: - Helper Methods\n    \n    /// Checks if a URL is an image file\n    func isImageFile(_ url: URL) -> Bool {\n        guard let contentType = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType else {\n            return false\n        }\n        return contentType.conforms(to: .image)\n    }\n}\n\n// MARK: - Errors\n\nenum ImageProcessingError: LocalizedError {\n    case invalidImage\n    case backgroundRemovalFailed\n    case conversionFailed\n    case pdfCreationFailed\n    case noImagesProvided\n    case saveFailed\n    \n    var errorDescription: String? {\n        switch self {\n        case .invalidImage:\n            return \"The file is not a valid image\"\n        case .backgroundRemovalFailed:\n            return \"Failed to remove background from image\"\n        case .conversionFailed:\n            return \"Failed to convert image format\"\n        case .pdfCreationFailed:\n            return \"Failed to create PDF from images\"\n        case .noImagesProvided:\n            return \"No images were provided\"\n        case .saveFailed:\n            return \"Failed to save processed file\"\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/QuickLookService.swift",
    "content": "//\n//  QuickLookService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-07.\n//\n\nimport Foundation\nimport UniformTypeIdentifiers\nimport SwiftUI\nimport QuickLookUI\nimport AppKit\n\n@MainActor\nfinal class QuickLookService: ObservableObject {\n    @Published var urls: [URL] = []\n    @Published var selectedURL: URL?\n\n    @Published var isQuickLookOpen: Bool = false\n\n    private var previewPanel: QLPreviewPanel?\n    private var dataSource: QuickLookDataSource?\n    private var accessingURLs: [URL] = []\n    private var previewPanelObserver: Any?\n\n    func show(urls: [URL], selectFirst: Bool = true, slideshow: Bool = false) {\n        guard !urls.isEmpty else { return }\n        stopAccessingCurrentURLs()\n        accessingURLs = urls.filter { url in\n            if url.isFileURL {\n                return url.startAccessingSecurityScopedResource()\n            }\n            return true\n        }\n        self.urls = accessingURLs\n        self.isQuickLookOpen = true\n        if selectFirst {\n            self.selectedURL = accessingURLs.first\n        }\n        // Observe the shared Quick Look preview panel closing so we can relinquish security scope\n        let panel = QLPreviewPanel.shared()\n        // Remove any existing observer for previous panel\n        if let prev = previewPanel {\n            NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: prev)\n        }\n        previewPanel = panel\n        NotificationCenter.default.addObserver(self, selector: #selector(previewPanelWillClose(_:)), name: NSWindow.willCloseNotification, object: panel)\n    }\n\n    func hide() {\n        stopAccessingCurrentURLs()\n        selectedURL = nil\n        urls.removeAll()\n        isQuickLookOpen = false\n        if let panel = previewPanel, panel.isVisible {\n            panel.orderOut(nil)\n        }\n        if let panel = previewPanel {\n            NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: panel)\n            previewPanel = nil\n        }\n    }\n    \n    private func stopAccessingCurrentURLs() {\n        NSLog(\"Stopping access to \\(accessingURLs.count) URLs\")\n        for url in accessingURLs where url.isFileURL {\n            url.stopAccessingSecurityScopedResource()\n        }\n        accessingURLs.removeAll()\n        // If Quick Look panel was closed externally, also remove observer and clear reference\n        if let panel = previewPanel {\n            NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: panel)\n            previewPanel = nil\n        }\n    }\n    \n    func showQuickLook(urls: [URL]) {\n        show(urls: urls, selectFirst: true, slideshow: false)\n    }\n\n    func updateSelection(urls: [URL]) {\n        guard isQuickLookOpen else { return }\n    show(urls: urls, selectFirst: true)\n    }\n}\n\nextension QuickLookService {\n    @objc private func previewPanelWillClose(_ notification: Notification) {\n        guard let panel = notification.object as? QLPreviewPanel, panel === previewPanel else { return }\n        // Ensure cleanup happens on main actor\n        Task { @MainActor in\n            stopAccessingCurrentURLs()\n            selectedURL = nil\n            urls.removeAll()\n            isQuickLookOpen = false\n            // Remove observer and clear reference\n            NotificationCenter.default.removeObserver(self, name: NSWindow.willCloseNotification, object: panel)\n            previewPanel = nil\n        }\n    }\n}\n\nstruct QuickLookPresenter: ViewModifier {\n    @ObservedObject var service: QuickLookService\n\n    func body(content: Content) -> some View {\n        content\n            .quickLookPreview($service.selectedURL, in: service.urls)\n    }\n}\n\nextension View {\n    func quickLookPresenter(using service: QuickLookService) -> some View {\n        self.modifier(QuickLookPresenter(service: service))\n    }\n}\n\n\nfinal class QuickLookDataSource: NSObject, QLPreviewPanelDataSource, QLPreviewPanelDelegate {\n    private let urls: [URL]\n\n    init(urls: [URL]) {\n        self.urls = urls\n        super.init()\n    }\n\n    nonisolated func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int {\n        return urls.count\n    }\n    nonisolated func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> QLPreviewItem! {\n        guard index >= 0 && index < urls.count else { return nil }\n        return urls[index] as QLPreviewItem\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/QuickShareService.swift",
    "content": "//\n//  QuickShareService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport AppKit\nimport Foundation\nimport UniformTypeIdentifiers\n\n/// Dynamic representation of a sharing provider discovered at runtime\nstruct QuickShareProvider: Identifiable, Hashable, Sendable {\n    var id: String\n    var imageData: Data?\n    var supportsRawText: Bool\n}\n\nclass QuickShareService: ObservableObject {\n    static let shared = QuickShareService()\n    \n    @Published var availableProviders: [QuickShareProvider] = []\n    @Published var isPickerOpen = false\n    private var cachedServices: [String: NSSharingService] = [:]\n    // Hold security-scoped URLs during sharing\n    private var sharingAccessingURLs: [URL] = []\n    private var lifecycleDelegate: SharingLifecycleDelegate?\n   \n    init() {\n        Task {\n            await discoverAvailableProviders()\n        }\n    }\n    \n    // MARK: - Provider Discovery\n    \n    @MainActor\n    func discoverAvailableProviders() async {\n        let finder = ShareServiceFinder()\n\n        // Use simple test items without creating actual temp files\n        // This avoids issues with the Share Sheet retaining references to deleted files\n        let testItems: [Any] = [\n            URL(string:\"http://example.com\") ?? URL(fileURLWithPath: \"/\"),\n            \"Test Text\" as NSString\n        ]\n\n        let services = await finder.findApplicableServices(for: testItems)\n\n        var providers: [QuickShareProvider] = []\n\n        for svc in services {\n            let title = svc.title\n            let imgData = svc.image.tiffRepresentation\n            let supportsRawText = svc.canPerform(withItems: [\"Test Text\"])\n            let provider = QuickShareProvider(id: title, imageData: imgData, supportsRawText: supportsRawText)\n            if !providers.contains(provider) {\n                providers.append(provider)\n                cachedServices[title] = svc\n            }\n        }\n        \n        if let idx = providers.firstIndex(where: { $0.id == \"AirDrop\" }) {\n            let ad = providers.remove(at: idx)\n            providers.insert(ad, at: 0)\n        }\n\n        if !providers.contains(where: { $0.id == \"System Share Menu\" }) {\n            providers.append(QuickShareProvider(id: \"System Share Menu\", imageData: nil, supportsRawText: true))\n        }\n\n        self.availableProviders = providers\n\n    }\n    \n    // MARK: - File Picker\n    @MainActor\n    func showFilePicker(for provider: QuickShareProvider, from view: NSView?) async {\n        guard !isPickerOpen else {\n            print(\"⚠️ QuickShareService: File picker already open\")\n            return\n        }\n\n        isPickerOpen = true\n        SharingStateManager.shared.beginInteraction()\n\n        let panel = NSOpenPanel()\n        panel.allowsMultipleSelection = true\n        panel.canChooseDirectories = true\n        panel.canChooseFiles = true\n        panel.title = \"Select Files for \\(provider.id)\"\n        panel.message = \"Choose files to share via \\(provider.id)\"\n\n        let completion: (NSApplication.ModalResponse) -> Void = { [weak self] response in\n            defer {\n                self?.isPickerOpen = false\n                SharingStateManager.shared.endInteraction()\n            }\n\n            if response == .OK && !panel.urls.isEmpty {\n                Task {\n                    await self?.shareFilesOrText(panel.urls, using: provider, from: view)\n                }\n            }\n        }\n\n        let response = panel.runModal()\n        completion(response)\n    }\n    \n    // MARK: - Sharing\n    @MainActor\n    func shareFilesOrText(_ items: [Any], using provider: QuickShareProvider, from view: NSView?) async {\n        let fileURLs = items.compactMap { $0 as? URL }.filter { $0.isFileURL }\n        // Stop any previous sharing access\n        stopSharingAccessingURLs()\n        // Start security-scoped access for all file URLs\n        sharingAccessingURLs = fileURLs.filter { $0.startAccessingSecurityScopedResource() }\n\n        // Setup lifecycle delegate to keep notch open during picker/service\n        let delegate = SharingStateManager.shared.makeDelegate { [weak self] in\n            self?.lifecycleDelegate = nil\n            self?.stopSharingAccessingURLs()\n        }\n        lifecycleDelegate = delegate\n\n        if let svc = cachedServices[provider.id], svc.canPerform(withItems: items) {\n            // For direct service path, explicitly mark service interaction start\n            delegate.markServiceBegan()\n            svc.delegate = delegate\n            svc.perform(withItems: items)\n        } else {\n            let picker = NSSharingServicePicker(items: items)\n            picker.delegate = delegate\n            delegate.markPickerBegan()\n            if let view {\n                picker.show(relativeTo: .zero, of: view, preferredEdge: .minY)\n            }\n        }\n    }\n\n    private func stopSharingAccessingURLs() {\n        NSLog(\"Stopping sharing access to URLs\")\n        for url in sharingAccessingURLs {\n            url.stopAccessingSecurityScopedResource()\n        }\n        sharingAccessingURLs.removeAll()\n    }\n// MARK: - SharingServiceDelegate\n\nprivate class SharingServiceDelegate: NSObject {}\n    \n    func shareDroppedFiles(_ providers: [NSItemProvider], using shareProvider: QuickShareProvider, from view: NSView?) async {\n        var itemsToShare: [Any] = []\n        var foundText: String?\n\n        for provider in providers {\n            if let webURL = await provider.extractURL() {\n                itemsToShare.append(webURL)\n            } else if foundText == nil, let text = await provider.extractText() {\n                foundText = text\n            } else if let itemFileURL = await provider.extractItem() {\n                let resolvedURL = await resolveShelfItemBookmark(for: itemFileURL) ?? itemFileURL\n                itemsToShare.append(resolvedURL)\n            }\n        }\n\n        // If text was found, prioritize sharing it.\n        if let text = foundText {\n            if shareProvider.supportsRawText {\n                await shareFilesOrText([text], using: shareProvider, from: view)\n            } else {\n                if let tempTextURL = await TemporaryFileStorageService.shared.createTempFile(for: .text(text)) {\n                    await shareFilesOrText([tempTextURL], using: shareProvider, from: view)\n                    TemporaryFileStorageService.shared.removeTemporaryFileIfNeeded(at: tempTextURL)\n                } else {\n                    await shareFilesOrText([text], using: shareProvider, from: view)\n                }\n            }\n        } else if !itemsToShare.isEmpty {\n            await shareFilesOrText(itemsToShare, using: shareProvider, from: view)\n        }\n    }\n\n    private func resolveShelfItemBookmark(for fileURL: URL) async -> URL? {\n        let items = await ShelfStateViewModel.shared.items\n\n        for itm in items {\n            if let resolved = await ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: itm) {\n                if resolved.standardizedFileURL.path == fileURL.standardizedFileURL.path {\n                    return resolved\n                }\n            }\n        }\n        print(\"❌ Failed to resolve bookmark for shelf item\")\n        return nil\n    }\n}\n\n// MARK: - App Storage Extension for Provider Selection\n\nextension QuickShareProvider {\n    static var defaultProvider: QuickShareProvider {\n        let svc = QuickShareService.shared\n\n        if let airdrop = svc.availableProviders.first(where: { $0.id == \"AirDrop\" }) {\n            return airdrop\n        }\n        return svc.availableProviders.first ?? QuickShareProvider(id: \"System Share Menu\", imageData: nil, supportsRawText: true)\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/ShareServiceFinder.swift",
    "content": "//\n//  ShareServiceFinder.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-06.\n//\n\nimport Cocoa\n\nclass ShareServiceFinder: NSObject, NSSharingServicePickerDelegate {\n\n    @MainActor\n    private var onServicesCaptured: (([NSSharingService]) -> Void)?\n\n    /// Returns share services asynchronously without blocking the UI\n    @MainActor\n    func findApplicableServices(for items: [Any], timeout: TimeInterval = 2.0) async -> [NSSharingService] {\n\n        let dummyView = NSView(frame: .zero)\n        let picker = NSSharingServicePicker(items: items)\n        picker.delegate = self\n\n        return await withCheckedContinuation { continuation in\n            var didResume = false\n\n            // Capture services callback\n            Task { @MainActor in\n                self.onServicesCaptured = { services in\n                    guard !didResume else { return }\n                    didResume = true\n                    continuation.resume(returning: services)\n                }\n            }\n\n            picker.show(relativeTo: dummyView.bounds, of: dummyView, preferredEdge: .minY)\n\n\n            // Timeout task\n            Task { @MainActor in\n                try? await Task.sleep(for: .seconds(timeout))\n                guard !didResume else { return }\n                didResume = true\n                print(\"Warning: timed out waiting for sharing services\")\n                continuation.resume(returning: [])\n            }\n        }\n    }\n\n    // MARK: NSSharingServicePickerDelegate\n\n    func sharingServicePicker(_ picker: NSSharingServicePicker,\n                              sharingServicesForItems items: [Any],\n                              proposedSharingServices proposed: [NSSharingService]) -> [NSSharingService] {\n        Task { @MainActor in\n            self.onServicesCaptured?(proposed)\n        }\n        return proposed\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/ShelfActionService.swift",
    "content": "//\n//  ShelfActionService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-07.\n//\n\nimport AppKit\nimport Foundation\n\n/// A service providing common actions for `ShelfItem`s, such as opening, revealing, or copying paths.\n@MainActor\nenum ShelfActionService {\n\n    static func open(_ item: ShelfItem) {\n        switch item.kind {\n        case .file(let bookmark):\n            handleBookmarkedFile(bookmark) { url in\n                NSWorkspace.shared.open(url)\n            }\n        case .link(let url):\n            NSWorkspace.shared.open(url)\n        case .text(let string):\n            NSPasteboard.general.clearContents()\n            NSPasteboard.general.setString(string, forType: .string)\n        }\n    }\n\n    static func reveal(_ item: ShelfItem) {\n        guard case .file(let bookmark) = item.kind else { return }\n        handleBookmarkedFile(bookmark) { url in\n            NSWorkspace.shared.activateFileViewerSelecting([url])\n        }\n    }\n\n    static func copyPath(_ item: ShelfItem) {\n        guard case .file(let bookmark) = item.kind else { return }\n        handleBookmarkedFile(bookmark) { url in\n            NSPasteboard.general.clearContents()\n            NSPasteboard.general.setString(url.path, forType: .string)\n        }\n    }\n\n    static func remove(_ item: ShelfItem) {\n        ShelfStateViewModel.shared.remove(item)\n    }\n\n    private static func handleBookmarkedFile(_ bookmarkData: Data, action: @escaping @Sendable (URL) -> Void) {\n        Task {\n            let bookmark = Bookmark(data: bookmarkData)\n            if let url = bookmark.resolveURL() {\n                url.accessSecurityScopedResource { accessibleURL in\n                    action(accessibleURL)\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/ShelfDropService.swift",
    "content": "//\n//  ShelfDropService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-26.\n//\n\nimport AppKit\nimport Foundation\nimport UniformTypeIdentifiers\n\nstruct ShelfDropService {\n    static func items(from providers: [NSItemProvider]) async -> [ShelfItem] {\n        var results: [ShelfItem] = []\n\n        for provider in providers {\n            if let item = await processProvider(provider) {\n                results.append(item)\n            }\n        }\n\n        return results\n    }\n    \n    private static func processProvider(_ provider: NSItemProvider) async -> ShelfItem? {\n        if let actualFileURL = await provider.extractFileURL() {\n            if let bookmark = createBookmark(for: actualFileURL) {\n                return await ShelfItem(kind: .file(bookmark: bookmark), isTemporary: false)\n            }\n            return nil\n        }\n        \n        if let url = await provider.extractURL() {\n            if url.isFileURL {\n                if let bookmark = createBookmark(for: url) {\n                    return await ShelfItem(kind: .file(bookmark: bookmark), isTemporary: false)\n                }\n            } else {\n                return await ShelfItem(kind: .link(url: url), isTemporary: false)\n            }\n            return nil\n        }\n        \n        if let text = await provider.extractText() {\n            return await ShelfItem(kind: .text(string: text), isTemporary: false)\n        }\n        \n        if let data = await provider.loadData() {\n            if let tempDataURL = await TemporaryFileStorageService.shared.createTempFile(for: .data(data, suggestedName: provider.suggestedName)),\n               let bookmark = createBookmark(for: tempDataURL) {\n                return await ShelfItem(kind: .file(bookmark: bookmark), isTemporary: true)\n            }\n            return nil\n        }\n        \n        if let fileURL = await provider.extractItem() {\n            if let bookmark = createBookmark(for: fileURL) {\n                return await ShelfItem(kind: .file(bookmark: bookmark), isTemporary: false)\n            }\n        }\n        \n        return nil\n    }\n    \n    private static func createBookmark(for url: URL) -> Data? {\n        return (try? Bookmark(url: url))?.data\n    }\n}\n\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/ShelfPersistenceService.swift",
    "content": "//\n//  ShelfPersistenceService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport Foundation\n\n// Access model types\n@_exported import struct Foundation.URL\n\n\nfinal class ShelfPersistenceService {\n    static let shared = ShelfPersistenceService()\n\n    private let fileURL: URL\n    private let encoder = JSONEncoder()\n    private let decoder = JSONDecoder()\n\n    private init() {\n        let fm = FileManager.default\n        let support = try? fm.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)\n        let dir = (support ?? fm.temporaryDirectory).appendingPathComponent(\"boringNotch\", isDirectory: true).appendingPathComponent(\"Shelf\", isDirectory: true)\n        try? fm.createDirectory(at: dir, withIntermediateDirectories: true)\n        fileURL = dir.appendingPathComponent(\"items.json\")\n        encoder.outputFormatting = [.prettyPrinted]\n        decoder.dateDecodingStrategy = .iso8601\n        encoder.dateEncodingStrategy = .iso8601\n    }\n\n    func load() -> [ShelfItem] {\n        guard let data = try? Data(contentsOf: fileURL) else { return [] }\n        \n        // Try to decode as array first (normal case)\n        if let items = try? decoder.decode([ShelfItem].self, from: data) {\n            return items\n        }\n        \n        // If array decoding fails, try to decode individual items\n        do {\n            // Parse as JSON array to get individual item data\n            guard let jsonArray = try JSONSerialization.jsonObject(with: data) as? [Any] else {\n                print(\"⚠️ Shelf persistence file is not a valid JSON array\")\n                return []\n            }\n            \n            var validItems: [ShelfItem] = []\n            var failedCount = 0\n            \n            for (index, jsonItem) in jsonArray.enumerated() {\n                do {\n                    let itemData = try JSONSerialization.data(withJSONObject: jsonItem)\n                    let item = try decoder.decode(ShelfItem.self, from: itemData)\n                    validItems.append(item)\n                } catch {\n                    failedCount += 1\n                    print(\"⚠️ Failed to decode shelf item at index \\(index): \\(error.localizedDescription)\")\n                }\n            }\n            \n            if failedCount > 0 {\n                print(\"📦 Successfully loaded \\(validItems.count) shelf items, discarded \\(failedCount) corrupted items\")\n            }\n            \n            return validItems\n        } catch {\n            print(\"❌ Failed to parse shelf persistence file: \\(error.localizedDescription)\")\n            return []\n        }\n    }\n\n    func save(_ items: [ShelfItem]) {\n        do {\n            let data = try encoder.encode(items)\n            try data.write(to: fileURL, options: Data.WritingOptions.atomic)\n        } catch {\n            print(\"Failed to save shelf items: \\(error.localizedDescription)\")\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/TemporaryFileStorageService.swift",
    "content": "//\n//  TemporaryFileStorageService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport Foundation\nimport AppKit\nimport UniformTypeIdentifiers\n\nenum TempFileType {\n    case data(Data, suggestedName: String?)\n    case text(String)\n    case url(URL)\n}\n\nclass TemporaryFileStorageService {\n    static let shared = TemporaryFileStorageService()\n    \n    // MARK: - Public Interface\n    \n    /// Creates a temporary file and tracks it for manual cleanup\n    func createTempFile(for type: TempFileType) async -> URL? {\n        return await withCheckedContinuation { continuation in\n            let result = createTempFile(for: type)\n            continuation.resume(returning: result)\n        }\n    }\n    \n    func removeTemporaryFileIfNeeded(at url: URL) {\n        let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())\n\n        guard url.path.hasPrefix(tempDirectory.path) else {\n            print(\"Attempted to remove temporary file outside temp directory: \\(url.path)\")\n            return\n        }\n\n        let folderURL = url.deletingLastPathComponent()\n\n        do {\n            try FileManager.default.removeItem(at: url)\n            print(\"Deleted file: \\(url.path)\")\n\n            let contents = try FileManager.default.contentsOfDirectory(atPath: folderURL.path)\n            if contents.isEmpty {\n                try FileManager.default.removeItem(at: folderURL)\n                print(\"Folder was empty, deleted folder: \\(folderURL.path)\")\n            } else {\n                print(\"Folder not deleted — it still contains \\(contents.count) item(s).\")\n            }\n\n        } catch {\n            print(\"Error: \\(error.localizedDescription)\")\n        }\n    }\n    \n    // MARK: - Private Implementation\n    \n    private func createTempFile(for type: TempFileType) -> URL? {\n        let tempDir = URL(fileURLWithPath: NSTemporaryDirectory())\n        let uuid = UUID().uuidString\n        \n        switch type {\n        case .data(let data, let suggestedName):\n            let filename = suggestedName ?? \".dat\"\n            let dirURL = tempDir.appendingPathComponent(uuid, isDirectory: true)\n            let fileURL = dirURL.appendingPathComponent(filename)\n            \n            do {\n                try FileManager.default.createDirectory(at: dirURL, withIntermediateDirectories: true)\n                try data.write(to: fileURL)\n                return fileURL\n            } catch {\n                print(\"Error: \\(error)\")\n                return nil\n            }\n            \n        case .text(let string):\n            let filename = \"\\(uuid).txt\"\n            let dirURL = tempDir.appendingPathComponent(uuid, isDirectory: true)\n            let fileURL = dirURL.appendingPathComponent(filename)\n            \n            guard let data = string.data(using: .utf8) else {\n                print(\"❌ Failed to convert text to data\")\n                return nil\n            }\n            \n            do {\n                try FileManager.default.createDirectory(at: dirURL, withIntermediateDirectories: true)\n                try data.write(to: fileURL)\n                return fileURL\n            } catch {\n                print(\"Error: \\(error)\")\n                return nil\n            }\n            \n        case .url(let url):\n            let filename = \"\\(url.host ?? uuid).webloc\"\n            let dirURL = tempDir.appendingPathComponent(uuid, isDirectory: true)\n            let fileURL = dirURL.appendingPathComponent(filename)\n            \n            let weblocContent = createWeblocContent(for: url)\n            guard let data = weblocContent.data(using: String.Encoding.utf8) else {\n                print(\"❌ Failed to create webloc data\")\n                return nil\n            }\n            \n            do {\n                try FileManager.default.createDirectory(at: dirURL, withIntermediateDirectories: true)\n                try data.write(to: fileURL)\n                return fileURL\n            } catch {\n                print(\"Error: \\(error)\")\n                return nil\n            }\n        }\n    }\n    \n    private func createFile(at url: URL, data: Data) -> URL? {\n        do {\n            try data.write(to: url)\n            return url\n        } catch {\n            print(\"❌ Failed to create temp file at \\(url.path): \\(error)\")\n            return nil\n        }\n    }\n    func createZip(from urls: [URL], suggestedName: String? = nil) async -> URL? {\n        let tempDir = URL(fileURLWithPath: NSTemporaryDirectory())\n        let uuid = UUID().uuidString\n        let workingDir = tempDir.appendingPathComponent(\"zip_\\(uuid)\", isDirectory: true)\n\n        do {\n            try FileManager.default.createDirectory(at: workingDir, withIntermediateDirectories: true)\n        } catch {\n            print(\"❌ Failed to create zip working directory: \\(error)\")\n            return nil\n        }\n\n        // Helper to run zip process\n        func runZip(arguments: [String], currentDirectory: URL) -> Bool {\n            let proc = Process()\n            proc.executableURL = URL(fileURLWithPath: \"/usr/bin/zip\")\n            proc.arguments = arguments\n            proc.currentDirectoryURL = currentDirectory\n            do {\n                try proc.run()\n                proc.waitUntilExit()\n                return proc.terminationStatus == 0\n            } catch {\n                print(\"❌ Failed to run zip: \\(error)\")\n                return false\n            }\n        }\n\n        // Single-item optimization: do not copy contents into the working dir.\n        if urls.count == 1, let src = urls.first {\n            let isDir = (try? src.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false\n            let baseName = src.lastPathComponent\n            let archiveName: String\n            if isDir {\n                // Folder: name as FolderName.zip and include the folder itself in the archive\n                archiveName = \"\\(baseName).zip\"\n                let archiveURL = workingDir.appendingPathComponent(archiveName)\n                // Run zip from the parent directory so the folder is stored as top-level entry\n                let parent = src.deletingLastPathComponent()\n                let args = [\"-r\", \"-q\", archiveURL.path, baseName]\n                let ok = runZip(arguments: args, currentDirectory: parent)\n                if ok {\n                    return archiveURL\n                } else {\n                    return nil\n                }\n            } else {\n                // File: include the file only (no parent folders). Name should include original extension.\n                archiveName = \"\\(baseName).zip\"\n                let archiveURL = workingDir.appendingPathComponent(archiveName)\n                let parent = src.deletingLastPathComponent()\n                // -j to junk paths and store only the file\n                let args = [\"-j\", \"-q\", archiveURL.path, baseName]\n                let ok = runZip(arguments: args, currentDirectory: parent)\n                if ok {\n                    return archiveURL\n                } else {\n                    return nil\n                }\n            }\n        }\n\n        // Multi-item: copy items into working dir (so their relative structure is preserved), zip, then remove copies.\n        for src in urls {\n            let dest = workingDir.appendingPathComponent(src.lastPathComponent)\n            do {\n                if FileManager.default.fileExists(atPath: dest.path) {\n                    // Avoid collision by appending a suffix\n                    let unique = \"\\(UUID().uuidString)_\\(src.lastPathComponent)\"\n                    try FileManager.default.copyItem(at: src, to: workingDir.appendingPathComponent(unique))\n                } else {\n                    try FileManager.default.copyItem(at: src, to: dest)\n                }\n            } catch {\n                print(\"⚠️ Failed to copy \\(src.path) to working dir: \\(error)\")\n            }\n        }\n\n        let archiveName = suggestedName ?? \"Archive.zip\"\n        let archiveURL = workingDir.appendingPathComponent(archiveName)\n        let args = [\"-r\", \"-q\", archiveURL.path, \".\"]\n        let ok = runZip(arguments: args, currentDirectory: workingDir)\n        if ok {\n            // Remove the copied (uncompressed) items so the temp folder contains only the archive\n            do {\n                let contents = try FileManager.default.contentsOfDirectory(at: workingDir, includingPropertiesForKeys: nil)\n                for file in contents {\n                    if file.standardizedFileURL != archiveURL.standardizedFileURL {\n                        try FileManager.default.removeItem(at: file)\n                    }\n                }\n            } catch {\n                print(\"⚠️ Failed to cleanup working directory after zip: \\(error)\")\n            }\n            return archiveURL\n        } else {\n            return nil\n        }\n    }\n    \n    // MARK: - Content Creation Helpers\n    \n    \n    private func createWeblocContent(for url: URL) -> String {\n        return \"\"\"\n        <?xml version=\"1.0\" encoding=\"UTF-8\"?>\n        <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n        <plist version=\"1.0\">\n        <dict>\n            <key>URL</key>\n            <string>\\(url.absoluteString)</string>\n        </dict>\n        </plist>\n        \"\"\"\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Services/ThumbnailService.swift",
    "content": "//\n//  ThumbnailService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-07.\n//\n\nimport Foundation\nimport AppKit\nimport QuickLookThumbnailing\nimport UniformTypeIdentifiers\n\nactor ThumbnailService {\n    static let shared = ThumbnailService()\n\n    private var cache: [String: NSImage] = [:]\n    private var pendingRequests: [String: Task<NSImage?, Never>] = [:]\n    private let thumbnailGenerator = QLThumbnailGenerator.shared\n\n    private init() {}\n    \n    func thumbnail(for url: URL, size: CGSize) async -> NSImage? {\n        let cacheKey = \"\\(url.path)_\\(size.width)x\\(size.height)\"\n        \n        if let cached = cache[cacheKey] {\n            return cached\n        }\n        \n        if let pending = pendingRequests[cacheKey] {\n            return await pending.value\n        }\n        \n        let task = Task<NSImage?, Never> {\n            let thumbnail = await generateQuickLookThumbnail(for: url, size: size)\n            if let thumbnail = thumbnail {\n                cache[cacheKey] = thumbnail\n            }\n            pendingRequests[cacheKey] = nil\n            return thumbnail\n        }\n        \n        pendingRequests[cacheKey] = task\n        return await task.value\n    }\n    \n    func clearCache() {\n        cache.removeAll()\n    }\n    \n    func clearCache(for url: URL) {\n        cache = cache.filter { !$0.key.starts(with: url.path) }\n    }\n    \n    // MARK: - Private Methods\n    \n    private func generateQuickLookThumbnail(for url: URL, size: CGSize) async -> NSImage? {\n        let scale = await MainActor.run { NSScreen.main?.backingScaleFactor ?? 2.0 }\n        \n        return await url.accessSecurityScopedResource { scopedURL in\n            NSLog(\"🔐 ThumbnailService: obtaining security scope for \\(scopedURL.path)\")\n            let request = QLThumbnailGenerator.Request(\n                fileAt: scopedURL,\n                size: size,\n                scale: scale,\n                representationTypes: .all\n            )\n            request.iconMode = true\n\n            return await withCheckedContinuation { (continuation: CheckedContinuation<NSImage?, Never>) in\n                thumbnailGenerator.generateBestRepresentation(for: request) { representation, error in\n                    if let rep = representation {\n                        NSLog(\"🔍 ThumbnailService: generated thumbnail for \\(scopedURL.path)\")\n                        continuation.resume(returning: rep.nsImage)\n                    } else {\n                        if let err = error { \n                            NSLog(\"⚠️ ThumbnailService: thumbnail error for \\(scopedURL.path): \\(err.localizedDescription)\") \n                        }\n                        continuation.resume(returning: nil)\n                    }\n                }\n            }\n        }\n    }\n}\n\n// MARK: - Extensions\n\nextension QLThumbnailRepresentation {\n    var nsImage: NSImage {\n        return NSImage(cgImage: self.cgImage, size: self.cgImage.size)\n    }\n}\n\nextension CGImage {\n    var size: NSSize {\n        return NSSize(width: self.width, height: self.height)\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/ViewModels/ShelfItemViewModel.swift",
    "content": "//\n//  ShelfItemViewModel.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport Foundation\nimport AppKit\nimport SwiftUI\nimport UniformTypeIdentifiers\nimport CoreServices\nimport ObjectiveC\n\n@MainActor\nfinal class ShelfItemViewModel: ObservableObject {\n    @Published private(set) var item: ShelfItem\n    @Published var thumbnail: NSImage?\n    @Published var isDropTargeted: Bool = false\n    @Published var isRenaming: Bool = false\n    @Published var draftTitle: String = \"\"\n    private var sharingLifecycle: SharingLifecycleDelegate?\n    private var quickShareLifecycle: SharingLifecycleDelegate?\n    private var sharingAccessingURLs: [URL] = []\n    private static var copiedURLs: [URL] = []\n\n    private let selection = ShelfSelectionModel.shared\n\n    init(item: ShelfItem) {\n        self.item = item\n        self.draftTitle = item.displayName\n        Task { await loadThumbnail() }\n    }\n\n    var isSelected: Bool { selection.isSelected(item.id) }\n\n    func loadThumbnail() async {\n        guard let url = item.fileURL else { return }\n        if let image = await ThumbnailService.shared.thumbnail(for: url, size: CGSize(width: 56, height: 56)) {\n            self.thumbnail = image\n        }\n    }\n\n    // MARK: - Drag & Drop helpers\n    func dragItemProvider() -> NSItemProvider {\n    let selectedItems = selection.selectedItems(in: ShelfStateViewModel.shared.items)\n        if selectedItems.count > 1 && selectedItems.contains(where: { $0.id == item.id }) {\n            return createMultiItemProvider(for: selectedItems)\n        }\n        return createItemProvider(for: item)\n    }\n\n    private func createItemProvider(for item: ShelfItem) -> NSItemProvider {\n        switch item.kind {\n        case .file:\n            let provider = NSItemProvider()\n            if let url = ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: item) {\n                provider.registerObject(url as NSURL, visibility: .all)\n            } else {\n                provider.registerObject(item.displayName as NSString, visibility: .all)\n            }\n            return provider\n        case .text(let string):\n            return NSItemProvider(object: string as NSString)\n        case .link(let url):\n            return NSItemProvider(object: url as NSURL)\n        }\n    }\n\n    private func createMultiItemProvider(for items: [ShelfItem]) -> NSItemProvider {\n        let provider = NSItemProvider()\n        var urls: [URL] = []\n        var textItems: [String] = []\n        for item in items {\n            switch item.kind {\n            case .file:\n                if let url = ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: item) {\n                    urls.append(url)\n                } else {\n                    textItems.append(item.displayName)\n                }\n            case .text(let string):\n                textItems.append(string)\n            case .link:\n                break\n            }\n        }\n        if !urls.isEmpty {\n            for url in urls {\n                provider.registerObject(url as NSURL, visibility: .all)\n            }\n        }\n        if !textItems.isEmpty {\n            provider.registerObject(textItems.joined(separator: \"\\n\") as NSString, visibility: .all)\n        }\n        return provider\n    }\n\n    // MARK: - Actions\n    func handleClick(event: NSEvent, view: NSView) {\n        let flags = event.modifierFlags\n        if flags.contains(.shift) {\n            selection.shiftSelect(to: item, in: ShelfStateViewModel.shared.items)\n        } else if flags.contains(.command) {\n            selection.toggle(item)\n        } else if flags.contains(.control) {\n            handleRightClick(event: event, view: view)\n        } else {\n            if !selection.isSelected(item.id) { selection.selectSingle(item) }\n        }\n        if event.clickCount == 2 { handleDoubleClick() }\n    }\n\n    func handleRightClick(event: NSEvent, view: NSView) {\n        if !selection.isSelected(item.id) { selection.selectSingle(item) }\n        presentContextMenu(event: event, in: view)\n    }\n\n    func handleDoubleClick() {\n    let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n        for it in selected { ShelfActionService.open(it) }\n    }\n\n    func shareItem(from view: NSView?) {\n        Task {\n            var itemsToShare: [Any] = []\n            var fileURLs: [URL] = []\n            if case .text(let text) = item.kind {\n                itemsToShare.append(text)\n            } else {\n                for item in ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items) {\n                    switch item.kind {\n                    case .file:\n                        // Use immediate update for user-initiated share action\n                        if let url = ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: item) {\n                            itemsToShare.append(url)\n                            fileURLs.append(url)\n                        }\n                    case .text(let string):\n                        itemsToShare.append(string)\n                    case .link(let url):\n                        itemsToShare.append(url)\n                    }\n                }\n            }\n            \n            guard !itemsToShare.isEmpty else { return }\n             \n            stopSharingAccessingURLs()\n            // Start security-scoped access for all file URLs and keep it active during sharing\n            sharingAccessingURLs = fileURLs.filter { $0.startAccessingSecurityScopedResource() }\n            \n            // Create and retain lifecycle delegate for the entire share operation\n            let lifecycle = SharingStateManager.shared.makeDelegate { [weak self] in\n                self?.sharingLifecycle = nil\n                self?.stopSharingAccessingURLs()\n            }\n            self.sharingLifecycle = lifecycle\n            \n            let picker = NSSharingServicePicker(items: itemsToShare)\n            picker.delegate = lifecycle\n            lifecycle.markPickerBegan()\n            if let view {\n                picker.show(relativeTo: .zero, of: view, preferredEdge: .minY)\n            }\n        }\n    }\n    \n    private func stopSharingAccessingURLs() {\n        for url in sharingAccessingURLs {\n            url.stopAccessingSecurityScopedResource()\n        }\n        sharingAccessingURLs.removeAll()\n    }\n\n    /// Call this closure to request a QuickLook preview for the given URLs.\n    var onQuickLookRequest: (([URL]) -> Void)?\n\n    // MARK: - Context Menu helpers (extracted from view)\n    func loadOpenWithApps() -> [URL] {\n        // Support both files and link items. For link items we ask NSWorkspace for apps that can open the URL (browsers).\n        if let fileURL = item.fileURL {\n            var results: [URL] = NSWorkspace.shared.urlsForApplications(toOpen: fileURL)\n            if results.isEmpty {\n                if let uti = try? fileURL.resourceValues(forKeys: [.contentTypeKey]).contentType {\n                    results = NSWorkspace.shared.urlsForApplications(toOpen: uti)\n                }\n            }\n            let unique = Array(Set(results))\n            let sorted = unique.sorted { appDisplayName(for: $0) < appDisplayName(for: $1) }\n            return sorted\n        } else if case .link(let url) = item.kind {\n            var results: [URL] = NSWorkspace.shared.urlsForApplications(toOpen: url)\n            if results.isEmpty {\n                if let uti = try? url.resourceValues(forKeys: [.contentTypeKey]).contentType {\n                    results = NSWorkspace.shared.urlsForApplications(toOpen: uti)\n                }\n            }\n            let unique = Array(Set(results))\n            let sorted = unique.sorted { appDisplayName(for: $0) < appDisplayName(for: $1) }\n            return sorted\n        }\n        return []\n    }\n\n    private func ensureContextMenuSelection() {\n        if !selection.isSelected(item.id) { selection.selectSingle(item) }\n    }\n\n    func presentContextMenu(event: NSEvent, in view: NSView) {\n        ensureContextMenuSelection()\n        let menu = NSMenu()\n\n        func addMenuItem(title: String) {\n            let mi = NSMenuItem(title: title, action: nil, keyEquivalent: \"\")\n            menu.addItem(mi)\n        }\n\n        let selectedItems = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n        let selectedFileURLs = selectedItems.compactMap { $0.fileURL }\n        let selectedLinkURLs: [URL] = selectedItems.compactMap { itm in\n            if case .link(let url) = itm.kind { return url }\n            return nil\n        }\n        let selectedFolderURLs = selectedFileURLs.filter { isDirectory($0) }\n        // URLs valid for Open/Open With (exclude folders)\n        let selectedOpenableURLs = selectedItems.compactMap { itm -> URL? in\n            if let u = itm.fileURL { return isDirectory(u) ? nil : u }\n            if case .link(let url) = itm.kind { return url }\n            return nil\n        }\n\n        if !selectedOpenableURLs.isEmpty {\n            addMenuItem(title: \"Open\")\n        }\n\n        if !selectedOpenableURLs.isEmpty {\n            let openWith = NSMenuItem(title: \"Open With\", action: nil, keyEquivalent: \"\")\n            let submenu = NSMenu()\n\n            // Choose a representative URL to compute apps (prefer current item if not a folder)\n            let baseURLForApps: URL? = {\n                if let u = item.fileURL, !isDirectory(u) { return u }\n                if case .link(let u) = item.kind { return u }\n                return selectedOpenableURLs.first\n            }()\n\n            let openWithApps: [URL] = {\n                guard let u = baseURLForApps else { return [] }\n                if u.isFileURL {\n                    var results = NSWorkspace.shared.urlsForApplications(toOpen: u)\n                    if results.isEmpty, let uti = try? u.resourceValues(forKeys: [.contentTypeKey]).contentType {\n                        results = NSWorkspace.shared.urlsForApplications(toOpen: uti)\n                    }\n                    return Array(Set(results))\n                } else {\n                    return Array(Set(NSWorkspace.shared.urlsForApplications(toOpen: u)))\n                }\n            }()\n            let defaultApp = defaultAppURL()\n\n            if openWithApps.isEmpty {\n                let noApps = NSMenuItem(title: \"No Compatible Apps Found\", action: nil, keyEquivalent: \"\")\n                noApps.isEnabled = false\n                submenu.addItem(noApps)\n            } else {\n                if let defaultApp = defaultApp {\n                    let appName = appDisplayName(for: defaultApp)\n                    let def = NSMenuItem(title: appName, action: nil, keyEquivalent: \"\")\n                    def.representedObject = defaultApp\n                    def.image = nsAppIcon(for: defaultApp, size: 16)\n\n                    let title = NSMutableAttributedString(string: appName, attributes: [\n                        .font: NSFont.menuFont(ofSize: 0),\n                        .foregroundColor: NSColor.labelColor\n                    ])\n                    let defaultPart = NSAttributedString(string: \" (default)\", attributes: [\n                        .font: NSFont.menuFont(ofSize: 0),\n                        .foregroundColor: NSColor.secondaryLabelColor\n                    ])\n                    title.append(defaultPart)\n                    def.attributedTitle = title\n                    submenu.addItem(def)\n\n                    if openWithApps.count > 1 || !openWithApps.contains(defaultApp) {\n                        submenu.addItem(NSMenuItem.separator())\n                    }\n                }\n                for appURL in openWithApps where appURL != defaultApp {\n                    let mi = NSMenuItem(title: appDisplayName(for: appURL), action: nil, keyEquivalent: \"\")\n                    mi.representedObject = appURL\n                    mi.image = nsAppIcon(for: appURL, size: 16)\n                    submenu.addItem(mi)\n                }\n            }\n\n            submenu.addItem(NSMenuItem.separator())\n            let other = NSMenuItem(title: \"Other…\", action: nil, keyEquivalent: \"\")\n            other.representedObject = \"__OTHER__\"\n            submenu.addItem(other)\n\n            openWith.submenu = submenu\n            menu.addItem(openWith)\n        }\n\n        if !selectedFileURLs.isEmpty { addMenuItem(title: \"Show in Finder\") }\n        // Allow Quick Look for files and link URLs\n        if !selectedFileURLs.isEmpty || !selectedLinkURLs.isEmpty {\n            // Add Quick Look menu item\n            let quickLookItem = NSMenuItem(title: \"Quick Look\", action: nil, keyEquivalent: \"\")\n            menu.addItem(quickLookItem)\n            \n            // Add Slideshow as alternate menu item (shown when Option key is held)\n            let slideshowItem = NSMenuItem(title: \"Quick Look\", action: nil, keyEquivalent: \"\")\n            slideshowItem.isAlternate = true\n            slideshowItem.keyEquivalentModifierMask = [.option]\n            menu.addItem(slideshowItem)\n        }\n\n        menu.addItem(NSMenuItem.separator())\n        addMenuItem(title: \"Share…\")\n        \n        // Add image processing options for image files grouped under \"Image Actions\"\n        let imageURLs = selectedFileURLs.filter { ImageProcessingService.shared.isImageFile($0) }\n        if !imageURLs.isEmpty {\n            menu.addItem(NSMenuItem.separator())\n\n            let imageActions = NSMenuItem(title: \"Image Actions\", action: nil, keyEquivalent: \"\")\n            let imageSubmenu = NSMenu()\n\n            // Remove Background - only for single images\n            if imageURLs.count == 1 {\n                let removeBg = NSMenuItem(title: \"Remove Background\", action: nil, keyEquivalent: \"\")\n                imageSubmenu.addItem(removeBg)\n            }\n\n            // Convert Image - only for single images\n            if imageURLs.count == 1 {\n                let convertItem = NSMenuItem(title: \"Convert Image…\", action: nil, keyEquivalent: \"\")\n                imageSubmenu.addItem(convertItem)\n            }\n\n            // Create PDF - for one or more images\n            let createPDF = NSMenuItem(title: \"Create PDF\", action: nil, keyEquivalent: \"\")\n            imageSubmenu.addItem(createPDF)\n\n            imageActions.submenu = imageSubmenu\n            menu.addItem(imageActions)\n            menu.addItem(NSMenuItem.separator())\n        }\n\n        // Add compression option for files/folders (single or multiple)\n        if !selectedFileURLs.isEmpty {\n            let compressItem = NSMenuItem(title: \"Compress\", action: nil, keyEquivalent: \"\")\n            menu.addItem(compressItem)\n        }\n\n        if selectedItems.count == 1, case .file(_) = item.kind { addMenuItem(title: \"Rename\") }\n\n        // Always show \"Copy\" for all item types\n        addMenuItem(title: \"Copy\")\n        // If there are file URLs, add \"Copy Path\" as an alternate menu item (Option key)\n        if !selectedFileURLs.isEmpty {\n            let copyPathItem = NSMenuItem(title: \"Copy Path\", action: nil, keyEquivalent: \"\")\n            copyPathItem.isAlternate = true\n            copyPathItem.keyEquivalentModifierMask = [.option]\n            menu.addItem(copyPathItem)\n        }\n\n        menu.addItem(NSMenuItem.separator())\n        addMenuItem(title: \"Remove\")\n\n        let actionTarget = MenuActionTarget(item: item, view: view, viewModel: self)\n\n        for menuItem in menu.items {\n            if menuItem.isSeparatorItem { continue }\n            menuItem.target = actionTarget\n            menuItem.action = #selector(MenuActionTarget.handle(_:))\n\n            if let submenu = menuItem.submenu {\n                for subItem in submenu.items {\n                    if !subItem.isSeparatorItem {\n                        subItem.target = actionTarget\n                        subItem.action = #selector(MenuActionTarget.handle(_:))\n                    }\n                }\n            }\n        }\n        \n        menu.retainActionTarget(actionTarget)\n        \n        NSMenu.popUpContextMenu(menu, with: event, for: view)\n    }\n\n    private func isDirectory(_ url: URL) -> Bool {\n        return url.accessSecurityScopedResource { scoped in\n            (try? scoped.resourceValues(forKeys: [.isDirectoryKey]).isDirectory) ?? false\n        }\n    }\n\n    private final class MenuActionTarget: NSObject {\n        let item: ShelfItem\n        weak var view: NSView?\n        unowned let viewModel: ShelfItemViewModel\n\n        // Keep associated objects (like accessory view handlers) without magic keys\n        private static var sliderHandlerAssoc = AssociatedObject<AnyObject>()\n\n        init(item: ShelfItem, view: NSView, viewModel: ShelfItemViewModel) {\n            self.item = item\n            self.view = view\n            self.viewModel = viewModel\n        }\n\n        @MainActor @objc func handle(_ sender: NSMenuItem) {\n            let title = sender.title\n\n            if let marker = sender.representedObject as? String, marker == \"__OTHER__\" {\n                openWithPanel()\n                return\n            }\n\n            if let appURL = sender.representedObject as? URL {\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                \n                Task {\n                        var allSelectedURLs: [URL] = []\n\n                        for itm in selected {\n                            if let fileURL = itm.fileURL {\n                                allSelectedURLs.append(fileURL)\n                            } else if case .link(let url) = itm.kind {\n                                allSelectedURLs.append(url)\n                            }\n                        }\n\n                        guard !allSelectedURLs.isEmpty else { return }\n\n                        let config = NSWorkspace.OpenConfiguration()\n\n                        let fileURLs = allSelectedURLs.filter { $0.isFileURL }\n                        do {\n                            if !fileURLs.isEmpty {\n                                _ = try await fileURLs.accessSecurityScopedResources { _ in\n                                    try await NSWorkspace.shared.open(allSelectedURLs, withApplicationAt: appURL, configuration: config)\n                                }\n                            } else {\n                                try await NSWorkspace.shared.open(allSelectedURLs, withApplicationAt: appURL, configuration: config)\n                            }\n                        } catch {\n                            print(\"❌ Failed to open with application: \\(error.localizedDescription)\")\n                        }\n                }\n                return\n            }\n\n            switch title {\n            case \"Quick Look\":\n                // Handle all selected items for Quick Look, not just the clicked item\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                let urls: [URL] = selected.compactMap { item in\n                    if let fileURL = item.fileURL {\n                        return fileURL\n                    }\n                    if case .link(let url) = item.kind {\n                        return url\n                    }\n                    return nil\n                }\n                if !urls.isEmpty {\n                    viewModel.onQuickLookRequest?(urls)\n                }\n\n            case \"Open\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                for it in selected { ShelfActionService.open(it) }\n\n            case \"Share…\":\n                viewModel.shareItem(from: view)\n\n            case \"Rename\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                if selected.count == 1, let single = selected.first { showRenameDialog(for: single) }\n\n            case \"Show in Finder\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                Task {\n                    let urls = await selected.asyncCompactMap { item -> URL? in\n                        if case .file = item.kind {\n                            // Use immediate update for user-initiated menu action\n                            return await ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: item)\n                        }\n                        return nil\n                    }\n                    if !urls.isEmpty {\n                        await urls.accessSecurityScopedResources { accessibleURLs in\n                            NSWorkspace.shared.activateFileViewerSelecting(accessibleURLs)\n                        }\n                    }\n                }\n\n            case \"Copy Path\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                let paths = selected.compactMap { $0.fileURL?.path }\n                if !paths.isEmpty {\n                    NSPasteboard.general.clearContents()\n                    NSPasteboard.general.setString(paths.joined(separator: \"\\n\"), forType: .string)\n                }\n\n            case \"Copy\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                let pb = NSPasteboard.general\n                \n                // Stop accessing previously copied URLs\n                for url in ShelfItemViewModel.copiedURLs {\n                    url.stopAccessingSecurityScopedResource()\n                }\n                ShelfItemViewModel.copiedURLs.removeAll()\n                \n                pb.clearContents()\n                Task {\n                    let fileURLs = await selected.asyncCompactMap { item -> URL? in\n                        if case .file = item.kind {\n                            return ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: item)\n                        }\n                        return nil\n                    }\n                    if !fileURLs.isEmpty {\n                        // Start security-scoped access for all URLs and keep them active\n                        ShelfItemViewModel.copiedURLs = fileURLs.filter { $0.startAccessingSecurityScopedResource() }\n                        NSLog(\"🔐 Started security-scoped access for \\(ShelfItemViewModel.copiedURLs.count) copied files\")\n                        \n                        // Write to pasteboard\n                        pb.writeObjects(fileURLs as [NSURL])\n                    } else {\n                        let strings = selected.map { $0.displayName }\n                        if !strings.isEmpty {\n                            pb.setString(strings.joined(separator: \"\\n\"), forType: .string)\n                        }\n                    }\n                }\n\n            case \"Remove\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                for it in selected { ShelfActionService.remove(it) }\n                \n            case \"Remove Background\":\n                handleRemoveBackground()\n                \n            case \"Convert Image…\":\n                showConvertImageDialog()\n                \n            case \"Create PDF\":\n                handleCreatePDF()\n            \n            case \"Compress\":\n                let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n                let fileURLs = selected.compactMap { $0.fileURL }\n                guard !fileURLs.isEmpty else { break }\n\n                Task {\n                    do {\n                        // Create ZIP in a temporary location while holding access to selected resources\n                        if let zipTempURL = try await fileURLs.accessSecurityScopedResources(accessor: { urls in\n                            await TemporaryFileStorageService.shared.createZip(from: urls)\n                        }) {\n                            if let bookmark = try? Bookmark(url: zipTempURL) {\n                                let newItem = ShelfItem(kind: .file(bookmark: bookmark.data), isTemporary: true)\n                                ShelfStateViewModel.shared.add([newItem])\n                            } else {\n                                // Fallback: reveal the temporary file in Finder\n                                NSWorkspace.shared.activateFileViewerSelecting([zipTempURL])\n                            }\n                        }\n                    } catch {\n                        print(\"❌ Compress failed: \\(error)\")\n                    }\n                }\n                \n            default:\n                break\n            }\n        }\n\n        @MainActor\n        private func openWithPanel() {\n            // Support both file items and link items\n            let targetURL: URL?\n            let needsSecurityScope: Bool\n            \n            if let fileURL = item.fileURL {\n                targetURL = fileURL\n                needsSecurityScope = true\n            } else if case .link(let url) = item.kind {\n                targetURL = url\n                needsSecurityScope = false\n            } else {\n                targetURL = nil\n                needsSecurityScope = false\n            }\n            guard let fileURL = targetURL else { return }\n\n            let panel = NSOpenPanel()\n            panel.title = \"Choose Application\"\n            panel.message = \"Choose an application to open the document \\\"\\(item.displayName)\\\".\"\n            panel.prompt = \"Open\"\n            panel.allowsMultipleSelection = false\n            panel.canChooseFiles = true\n            panel.canChooseDirectories = false\n            panel.resolvesAliases = true\n            if #available(macOS 12.0, *) {\n                panel.allowedContentTypes = [.application]\n            }\n            panel.directoryURL = URL(fileURLWithPath: \"/Applications\")\n\n            // Compute recommended applications for the selected target\n            let recommendedApps: Set<URL> = {\n                let apps: [URL]\n                if let uti = (try? fileURL.resourceValues(forKeys: [.contentTypeKey]))?.contentType {\n                    apps = NSWorkspace.shared.urlsForApplications(toOpen: uti)\n                } else {\n                    apps = NSWorkspace.shared.urlsForApplications(toOpen: fileURL)\n                }\n                return Set(apps.map { $0.standardizedFileURL })\n            }()\n\n            // Delegate to filter entries when in \"Recommended Applications\" mode\n            final class AppChooserDelegate: NSObject, NSOpenSavePanelDelegate {\n                enum Mode { case recommended, all }\n                var mode: Mode = .recommended\n                let recommended: Set<URL>\n                init(recommended: Set<URL>) { self.recommended = recommended }\n                \n                func panel(_ sender: Any, shouldEnable url: URL) -> Bool {\n                    let ext = url.pathExtension.lowercased()\n                    if ext == \"app\" {\n                        switch mode {\n                        case .all:\n                            return true\n                        case .recommended:\n                            // Standardize URLs for reliable comparison\n                            let std = url.standardizedFileURL\n                            return recommended.contains(std)\n                        }\n                    }\n\n                    var isDirectory: ObjCBool = false\n                    if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory), isDirectory.boolValue {\n                        return true\n                    }\n                    \n                    return false\n                }\n            }\n\n            let chooserDelegate = AppChooserDelegate(recommended: recommendedApps)\n            panel.delegate = chooserDelegate\n\n            let enableLabel = NSTextField(labelWithString: \"Enable:\")\n            enableLabel.font = .systemFont(ofSize: NSFont.systemFontSize)\n            enableLabel.alignment = .natural\n            enableLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal)\n            \n            let popup = NSPopUpButton(frame: .zero, pullsDown: false)\n            popup.addItems(withTitles: [\"Recommended Applications\", \"All Applications\"])\n            popup.font = .systemFont(ofSize: NSFont.systemFontSize)\n            popup.selectItem(at: 0)\n            \n            popup.setContentHuggingPriority(.defaultLow, for: .horizontal)\n            popup.widthAnchor.constraint(greaterThanOrEqualToConstant: 200).isActive = true\n            \n            let alwaysCheckbox = NSButton(checkboxWithTitle: \"Always Open With\", target: nil, action: nil)\n            alwaysCheckbox.font = .systemFont(ofSize: NSFont.systemFontSize)\n            alwaysCheckbox.setContentHuggingPriority(.defaultLow, for: .horizontal)\n\n            let row = NSStackView(views: [enableLabel, popup])\n            row.orientation = .horizontal\n            row.spacing = 8\n            row.alignment = .centerY\n            row.distribution = .fill\n            \n            let column = NSStackView(views: [row, alwaysCheckbox])\n            column.orientation = .vertical\n            column.spacing = 12\n            column.alignment = .centerX\n            column.distribution = .fill\n            column.edgeInsets = NSEdgeInsets(top: 16, left: 20, bottom: 16, right: 20)\n            \n            panel.accessoryView = column\n            panel.isAccessoryViewDisclosed = true\n\n            // Wire up popup to switch filter mode\n            class PopupBinder: NSObject {\n                weak var popup: NSPopUpButton?\n                weak var chooserDelegate: AppChooserDelegate?\n                weak var panel: NSOpenPanel?\n                init(popup: NSPopUpButton, chooserDelegate: AppChooserDelegate, panel: NSOpenPanel) {\n                    self.popup = popup\n                    self.chooserDelegate = chooserDelegate\n                    self.panel = panel\n                }\n                @objc func changed(_ sender: Any?) {\n                    if popup?.indexOfSelectedItem == 1 {\n                        chooserDelegate?.mode = .all\n                    } else {\n                        chooserDelegate?.mode = .recommended\n                    }\n                    if let panel = panel {\n                        panel.validateVisibleColumns()\n                        let currentDir = panel.directoryURL\n                        panel.directoryURL = currentDir\n                    }\n                }\n            }\n            let binder = PopupBinder(popup: popup, chooserDelegate: chooserDelegate, panel: panel)\n            popup.target = binder\n            popup.action = #selector(PopupBinder.changed(_:))\n\n            panel.begin { response in\n                if response == .OK, let appURL = panel.url {\n                    Task {\n                        do {\n                            let config = NSWorkspace.OpenConfiguration()\n                            if alwaysCheckbox.state == .on, let bundleID = Bundle(url: appURL)?.bundleIdentifier {\n                                if let contentType = (try? fileURL.resourceValues(forKeys: [.contentTypeKey]))?.contentType {\n                                    let status = LSSetDefaultRoleHandlerForContentType(contentType.identifier as CFString, LSRolesMask.all, bundleID as CFString)\n                                    if status != noErr { print(\"⚠️ Failed to set default handler for \\(contentType.identifier): \\(status)\") }\n                                } else if let scheme = fileURL.scheme {\n                                    let status = LSSetDefaultHandlerForURLScheme(scheme as CFString, bundleID as CFString)\n                                    if status != noErr { print(\"⚠️ Failed to set default handler for scheme \\(scheme): \\(status)\") }\n                                }\n                            }\n\n                            if needsSecurityScope {\n                                _ = try await fileURL.accessSecurityScopedResource { accessibleURL in\n                                    try await NSWorkspace.shared.open([accessibleURL], withApplicationAt: appURL, configuration: config)\n                                }\n                            } else {\n                                try await NSWorkspace.shared.open([fileURL], withApplicationAt: appURL, configuration: config)\n                            }\n                        } catch {\n                            print(\"❌ Failed to open with application: \\(error.localizedDescription)\")\n                        }\n                    }\n                }\n                // Keep binder/delegate alive until panel finishes\n                _ = binder\n                _ = chooserDelegate\n            }\n        }\n        \n        @MainActor\n        private func showRenameDialog(for item: ShelfItem) {\n            guard case let .file(bookmarkData) = item.kind else { return }\n            Task {\n                let bookmark = Bookmark(data: bookmarkData)\n                if let fileURL = bookmark.resolveURL() {\n                    // Start security-scoped access and keep it active until rename completes.\n                    let didStart = fileURL.startAccessingSecurityScopedResource()\n\n                    let savePanel = NSSavePanel()\n                    savePanel.title = \"Rename File\"\n                    savePanel.prompt = \"Rename\"\n                    savePanel.nameFieldStringValue = fileURL.lastPathComponent\n                    savePanel.directoryURL = fileURL.deletingLastPathComponent()\n                    savePanel.begin { response in\n                        if response == .OK, let newURL = savePanel.url {\n                            Task {\n                                do {\n                                    NSLog(\"🔐 Rename: moving from \\(fileURL.path) to \\(newURL.path) (securityScope=\\(didStart))\")\n\n                                    try FileManager.default.moveItem(at: fileURL, to: newURL)\n\n                                    if let newBookmark = try? Bookmark(url: newURL) {\n                                        ShelfStateViewModel.shared.updateBookmark(for: item, bookmark: newBookmark.data)\n                                    }\n                                } catch {\n                                    print(\"❌ Failed to rename file: \\(error.localizedDescription)\")\n                                }\n                                if didStart { fileURL.stopAccessingSecurityScopedResource() }\n                            }\n                        } else {\n                            if didStart { fileURL.stopAccessingSecurityScopedResource() }\n                        }\n                    }\n                }\n            }\n        }\n        \n        @MainActor\n        private func handleRemoveBackground() {\n            let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n            let imageURLs = selected.compactMap { $0.fileURL }.filter { ImageProcessingService.shared.isImageFile($0) }\n            \n            guard let imageURL = imageURLs.first else { return }\n            \n            Task {\n                do {\n                    let resultURL = try await imageURL.accessSecurityScopedResource { url in\n                        try await ImageProcessingService.shared.removeBackground(from: url)\n                    }\n                    \n                    if let resultURL = resultURL {\n                        // Create bookmark and add to shelf as temporary item\n                        if let bookmark = try? Bookmark(url: resultURL) {\n                            let newItem = ShelfItem(\n                                kind: .file(bookmark: bookmark.data),\n                                isTemporary: true\n                            )\n                            ShelfStateViewModel.shared.add([newItem])\n                        }\n                    }\n                } catch {\n                    print(\"❌ Failed to remove background: \\(error.localizedDescription)\")\n                    await showErrorAlert(title: \"Background Removal Failed\", message: error.localizedDescription)\n                }\n            }\n        }\n        \n        @MainActor\n        private func handleCreatePDF() {\n            let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n            let imageURLs = selected.compactMap { $0.fileURL }.filter { ImageProcessingService.shared.isImageFile($0) }\n            \n            guard !imageURLs.isEmpty else { return }\n            \n            Task {\n                do {\n                    let resultURL = try await imageURLs.accessSecurityScopedResources { urls in\n                        try await ImageProcessingService.shared.createPDF(from: urls)\n                    }\n                    \n                    if let resultURL = resultURL {\n                        // Create bookmark and add to shelf as temporary item\n                        if let bookmark = try? Bookmark(url: resultURL) {\n                            let newItem = ShelfItem(\n                                kind: .file(bookmark: bookmark.data),\n                                isTemporary: true\n                            )\n                            ShelfStateViewModel.shared.add([newItem])\n                        }\n                    }\n                } catch {\n                    print(\"❌ Failed to create PDF: \\(error.localizedDescription)\")\n                    await showErrorAlert(title: \"PDF Creation Failed\", message: error.localizedDescription)\n                }\n            }\n        }\n        \n        @MainActor\n        private func showConvertImageDialog() {\n            let selected = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n            let imageURLs = selected.compactMap { $0.fileURL }.filter { ImageProcessingService.shared.isImageFile($0) }\n            \n            guard let imageURL = imageURLs.first else { return }\n            \n            // Create and show conversion options dialog with better layout\n            let alert = NSAlert()\n            alert.messageText = \"Convert Image\"\n            alert.alertStyle = .informational\n            alert.addButton(withTitle: \"Convert\")\n            alert.addButton(withTitle: \"Cancel\")\n            \n            // Create accessory view with better spacing and organization\n            let accessoryView = NSView(frame: NSRect(x: 0, y: 0, width: 380, height: 180))\n            accessoryView.wantsLayer = true\n            \n            // MARK: Format Row\n            let formatLabel = NSTextField(labelWithString: \"Format:\")\n            formatLabel.frame = NSRect(x: 0, y: 145, width: 100, height: 20)\n            formatLabel.font = .systemFont(ofSize: 12, weight: .medium)\n            accessoryView.addSubview(formatLabel)\n            \n            let formatPopup = NSPopUpButton(frame: NSRect(x: 120, y: 140, width: 250, height: 28))\n            formatPopup.addItems(withTitles: [\"PNG\", \"JPEG\", \"HEIC\", \"TIFF\", \"BMP\"])\n            formatPopup.selectItem(at: 0)\n            formatPopup.font = .systemFont(ofSize: 12)\n            accessoryView.addSubview(formatPopup)\n            \n            // MARK: Image Size Row\n            let imageSizeLabel = NSTextField(labelWithString: \"Image Size:\")\n            imageSizeLabel.frame = NSRect(x: 0, y: 105, width: 100, height: 20)\n            imageSizeLabel.font = .systemFont(ofSize: 12, weight: .medium)\n            accessoryView.addSubview(imageSizeLabel)\n            \n            let imageSizePopup = NSPopUpButton(frame: NSRect(x: 120, y: 100, width: 160, height: 28))\n            imageSizePopup.addItems(withTitles: [\"Actual Size\", \"Large\", \"Medium\", \"Small\", \"Custom...\"])\n            imageSizePopup.selectItem(at: 0)\n            imageSizePopup.font = .systemFont(ofSize: 12)\n            accessoryView.addSubview(imageSizePopup)\n            \n            // Custom size field (initially hidden)\n            let customSizeField = NSTextField(frame: NSRect(x: 285, y: 103, width: 85, height: 22))\n            customSizeField.placeholderString = \"e.g., 1920\"\n            customSizeField.font = .systemFont(ofSize: 12)\n            customSizeField.isHidden = true\n            accessoryView.addSubview(customSizeField)\n            \n            // MARK: Preserve Metadata Checkbox\n            let metadataCheckbox = NSButton(checkboxWithTitle: \"Preserve Metadata\", target: nil, action: nil)\n            metadataCheckbox.frame = NSRect(x: 120, y: 65, width: 200, height: 20)\n            metadataCheckbox.font = .systemFont(ofSize: 12)\n            metadataCheckbox.state = .on\n            accessoryView.addSubview(metadataCheckbox)\n            \n            // MARK: Separator line\n            let separatorLine = NSView(frame: NSRect(x: 0, y: 50, width: 380, height: 1))\n            separatorLine.wantsLayer = true\n            separatorLine.layer?.backgroundColor = NSColor.separatorColor.cgColor\n            accessoryView.addSubview(separatorLine)\n            \n            // MARK: Format-specific options (shown/hidden based on format selection)\n            let qualityRow = NSView(frame: NSRect(x: 0, y: 15, width: 380, height: 30))\n            qualityRow.wantsLayer = true\n            \n            let qualityLabel = NSTextField(labelWithString: \"Compression:\")\n            qualityLabel.frame = NSRect(x: 0, y: 7, width: 100, height: 20)\n            qualityLabel.font = .systemFont(ofSize: 12, weight: .medium)\n            qualityRow.addSubview(qualityLabel)\n            \n            let qualitySlider = NSSlider(frame: NSRect(x: 120, y: 12, width: 200, height: 20))\n            qualitySlider.minValue = 0.0\n            qualitySlider.maxValue = 1.0\n            qualitySlider.doubleValue = 0.85\n            accessoryView.addSubview(qualitySlider)\n            \n            let qualityValueLabel = NSTextField(labelWithString: \"85%\")\n            qualityValueLabel.frame = NSRect(x: 325, y: 7, width: 55, height: 20)\n            qualityValueLabel.font = .systemFont(ofSize: 12)\n            qualityValueLabel.alignment = .left\n            accessoryView.addSubview(qualityValueLabel)\n            \n            // Update quality label and hide/show compression row based on format\n            let updateQualityLabel = {\n                let value = Int(qualitySlider.doubleValue * 100)\n                qualityValueLabel.stringValue = \"\\(value)%\"\n            }\n            \n            let updateCompressionVisibility = {\n                let formatIndex = formatPopup.indexOfSelectedItem\n                let showCompression = formatIndex == 1 || formatIndex == 2 // JPEG or HEIC\n                qualitySlider.isHidden = !showCompression\n                qualityValueLabel.isHidden = !showCompression\n                qualityLabel.isHidden = !showCompression\n            }\n            \n            let updateCustomSizeVisibility = {\n                let sizeIndex = imageSizePopup.indexOfSelectedItem\n                customSizeField.isHidden = sizeIndex != 4 // Show only for \"Custom...\"\n            }\n            \n            // Create a target object to handle slider value changes\n            class SliderHandler: NSObject {\n                let updateLabel: () -> Void\n                let updateVisibility: () -> Void\n                let updateCustomSize: () -> Void\n                init(updateLabel: @escaping () -> Void, updateVisibility: @escaping () -> Void, updateCustomSize: @escaping () -> Void) {\n                    self.updateLabel = updateLabel\n                    self.updateVisibility = updateVisibility\n                    self.updateCustomSize = updateCustomSize\n                }\n                @objc func sliderChanged(_ sender: NSSlider) {\n                    updateLabel()\n                }\n                @objc func formatChanged(_ sender: NSPopUpButton) {\n                    updateVisibility()\n                }\n                @objc func sizeChanged(_ sender: NSPopUpButton) {\n                    updateCustomSize()\n                }\n            }\n            \n            let handler = SliderHandler(updateLabel: updateQualityLabel, updateVisibility: updateCompressionVisibility, updateCustomSize: updateCustomSizeVisibility)\n            qualitySlider.target = handler\n            qualitySlider.action = #selector(SliderHandler.sliderChanged(_:))\n            qualitySlider.isContinuous = true\n            \n            formatPopup.target = handler\n            formatPopup.action = #selector(SliderHandler.formatChanged(_:))\n            \n            imageSizePopup.target = handler\n            imageSizePopup.action = #selector(SliderHandler.sizeChanged(_:))\n            \n            updateCompressionVisibility()\n            updateQualityLabel()\n            updateCustomSizeVisibility()\n            \n            // Keep the handler alive using the `AssociatedObject` helper instead of a magic string key\n            MenuActionTarget.sliderHandlerAssoc[accessoryView] = handler\n            \n            alert.accessoryView = accessoryView\n            \n            let response = alert.runModal()\n            \n            if response == .alertFirstButtonReturn {\n                // Get selected options\n                let formatIndex = formatPopup.indexOfSelectedItem\n                let format: ImageConversionOptions.ImageFormat\n                switch formatIndex {\n                case 0: format = .png\n                case 1: format = .jpeg\n                case 2: format = .heic\n                case 3: format = .tiff\n                case 4: format = .bmp\n                default: format = .png\n                }\n                \n                let quality = qualitySlider.doubleValue\n                \n                // Get max dimension based on image size selection\n                let maxDimension: CGFloat? = {\n                    let sizeIndex = imageSizePopup.indexOfSelectedItem\n                    switch sizeIndex {\n                    case 0: return nil // Actual Size\n                    case 1: return 1280 // Large \n                    case 2: return 640  // Medium \n                    case 3: return 320  // Small \n                    case 4: // Custom (user-specified)\n                        let text = customSizeField.stringValue.trimmingCharacters(in: .whitespaces)\n                        guard !text.isEmpty, let value = Double(text), value > 0 else { return nil }\n                        return CGFloat(value)\n                    default: return nil\n                    }\n                }()\n                \n                let removeMetadata = metadataCheckbox.state == .off // Note: we invert this\n                \n                let options = ImageConversionOptions(\n                    format: format,\n                    compressionQuality: quality,\n                    maxDimension: maxDimension,\n                    removeMetadata: removeMetadata\n                )\n                \n                Task {\n                    do {\n                        let resultURL = try await imageURL.accessSecurityScopedResource { url in\n                            try await ImageProcessingService.shared.convertImage(from: url, options: options)\n                        }\n                        \n                        if let resultURL = resultURL {\n                            // Create bookmark and add to shelf as temporary item\n                            if let bookmark = try? Bookmark(url: resultURL) {\n                                let newItem = ShelfItem(\n                                    kind: .file(bookmark: bookmark.data),\n                                    isTemporary: true\n                                )\n                                ShelfStateViewModel.shared.add([newItem])\n                            }\n                        }\n                    } catch {\n                        print(\"❌ Failed to convert image: \\(error.localizedDescription)\")\n                        showErrorAlert(title: \"Image Conversion Failed\", message: error.localizedDescription)\n                    }\n                }\n            }\n        }\n        \n        @MainActor\n        private func showErrorAlert(title: String, message: String) {\n            let alert = NSAlert()\n            alert.messageText = title\n            alert.informativeText = message\n            alert.alertStyle = .warning\n            alert.addButton(withTitle: \"OK\")\n            alert.runModal()\n        }\n    }\n\n    // MARK: - Private helpers\n    private func appDisplayName(for appURL: URL) -> String {\n        (try? appURL.resourceValues(forKeys: [.localizedNameKey]).localizedName) ?? appURL.lastPathComponent\n    }\n\n    private func nsAppIcon(for appURL: URL, size: CGFloat) -> NSImage? {\n        let baseIcon = NSWorkspace.shared.icon(forFile: appURL.path)\n        baseIcon.isTemplate = false\n\n        let targetSize = NSSize(width: size, height: size)\n        let rendered = NSImage(size: targetSize, flipped: false) { rect in\n            NSGraphicsContext.current?.imageInterpolation = .high\n            baseIcon.draw(in: rect, from: .zero, operation: .sourceOver, fraction: 1.0, respectFlipped: true, hints: [\n                .interpolation: NSImageInterpolation.high.rawValue\n            ])\n            return true\n        }\n\n        rendered.size = targetSize\n        return rendered\n    }\n\n    private func defaultAppURL() -> URL? {\n        if let fileURL = item.fileURL {\n            return NSWorkspace.shared.urlForApplication(toOpen: fileURL)\n        } else if case .link(let url) = item.kind {\n            return NSWorkspace.shared.urlForApplication(toOpen: url)\n        }\n        return nil\n    }\n}\n\nfileprivate extension Sequence {\n    func asyncCompactMap<T>(_ transform: (Element) async -> T?) async -> [T] {\n        var result: [T] = []\n        for element in self {\n            if let transformed = await transform(element) {\n                result.append(transformed)\n            }\n        }\n        return result\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/ViewModels/ShelfSelectionModel.swift",
    "content": "//\n//  ShelfSelectionModel.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-26.\n//\n\nimport Foundation\nimport Combine\n\nprivate let _shelfTypeAnchor: Bool = {\n    _ = String(describing: ShelfItem.self)\n    return true\n}()\n\n@MainActor\nfinal class ShelfSelectionModel: ObservableObject {\n    static let shared = ShelfSelectionModel()\n\n    @Published private(set) var selectedIDs: Set<UUID> = []\n\n    // Anchor for shift-range selection\n    private var lastAnchorID: UUID? = nil\n\n    func isSelected(_ id: UUID) -> Bool { selectedIDs.contains(id) }\n\n    var hasSelection: Bool { !selectedIDs.isEmpty }\n\n    var firstSelectedItem: ShelfItem? {\n        guard let firstID = selectedIDs.first else { return nil }\n        return ShelfStateViewModel.shared.items.first(where: { $0.id == firstID })\n    }\n\n    func selectedItems(in allItems: [ShelfItem]) -> [ShelfItem] {\n        allItems.filter { selectedIDs.contains($0.id) }\n    }\n\n    func selectSingle(_ item: ShelfItem) {\n        selectedIDs = [item.id]\n        lastAnchorID = item.id\n    }\n\n    func toggle(_ item: ShelfItem) {\n        if selectedIDs.contains(item.id) {\n            selectedIDs.remove(item.id)\n        } else {\n            selectedIDs.insert(item.id)\n        }\n        lastAnchorID = item.id\n    }\n\n    func shiftSelect(to item: ShelfItem, in allItems: [ShelfItem]) {\n        // Determine anchor\n        let anchorID = lastAnchorID ?? selectedIDs.first ?? item.id\n        guard let startIndex = allItems.firstIndex(where: { $0.id == anchorID }),\n              let endIndex = allItems.firstIndex(where: { $0.id == item.id }) else {\n            // Fallback to single select if indices not found\n            return selectSingle(item)\n        }\n        let lower = min(startIndex, endIndex)\n        let upper = max(startIndex, endIndex)\n        let rangeIDs = allItems[lower...upper].map { $0.id }\n        selectedIDs = Set(rangeIDs)\n    }\n\n    func clear() {\n        selectedIDs.removeAll()\n        lastAnchorID = nil\n    }\n\n    // Keep anchor sane if items array changed drastically (optional helper)\n    func ensureValidAnchor(in allItems: [ShelfItem]) {\n        if let anchor = lastAnchorID, !allItems.contains(where: { $0.id == anchor }) {\n            lastAnchorID = selectedIDs.first\n        }\n    }\n\n    @Published private(set) var isDragging: Bool = false\n\n    func beginDrag() {\n        isDragging = true\n    }\n\n    func endDrag() {\n        isDragging = false\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/ViewModels/ShelfStateViewModel.swift",
    "content": "//\n//  ShelfStateViewModel.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-09.\n\nimport Foundation\nimport AppKit\n\n@MainActor\nfinal class ShelfStateViewModel: ObservableObject {\n    static let shared = ShelfStateViewModel()\n\n    @Published private(set) var items: [ShelfItem] = [] {\n        didSet { ShelfPersistenceService.shared.save(items) }\n    }\n\n    @Published var isLoading: Bool = false\n\n    var isEmpty: Bool { items.isEmpty }\n\n    // Queue for deferred bookmark updates to avoid publishing during view updates\n    private var pendingBookmarkUpdates: [ShelfItem.ID: Data] = [:]\n    private var updateTask: Task<Void, Never>?\n\n    private init() {\n        items = ShelfPersistenceService.shared.load()\n    }\n\n\n    func add(_ newItems: [ShelfItem]) {\n        guard !newItems.isEmpty else { return }\n        var merged = items\n        // Deduplicate by identityKey while preserving order (existing first)\n        var seen: Set<String> = Set(merged.map { $0.identityKey })\n        for it in newItems {\n            let key = it.identityKey\n            if !seen.contains(key) {\n                merged.append(it)\n                seen.insert(key)\n            }\n        }\n        items = merged\n    }\n\n    func remove(_ item: ShelfItem) {\n        item.cleanupStoredData()\n        items.removeAll { $0.id == item.id }\n    }\n\n    func updateBookmark(for item: ShelfItem, bookmark: Data) {\n        guard let idx = items.firstIndex(where: { $0.id == item.id }) else { return }\n        if case .file = items[idx].kind {\n            items[idx].kind = .file(bookmark: bookmark)\n        }\n    }\n\n    private func scheduleDeferredBookmarkUpdate(for item: ShelfItem, bookmark: Data) {\n        pendingBookmarkUpdates[item.id] = bookmark\n        \n        // Cancel existing task and schedule a new one\n        updateTask?.cancel()\n        updateTask = Task { @MainActor [weak self] in\n            await Task.yield()\n            \n            guard let self = self else { return }\n            \n            for (itemID, bookmarkData) in self.pendingBookmarkUpdates {\n                if let idx = self.items.firstIndex(where: { $0.id == itemID }),\n                   case .file = self.items[idx].kind {\n                    self.items[idx].kind = .file(bookmark: bookmarkData)\n                }\n            }\n            \n            self.pendingBookmarkUpdates.removeAll()\n        }\n    }\n\n\n    func load(_ providers: [NSItemProvider]) {\n        guard !providers.isEmpty else { return }\n        isLoading = true\n        Task { [weak self] in\n            let dropped = await ShelfDropService.items(from: providers)\n            await MainActor.run {\n                self?.add(dropped)\n                self?.isLoading = false\n            }\n        }\n    }\n\n    func cleanupInvalidItems() {\n        Task { [weak self] in\n            guard let self else { return }\n            var keep: [ShelfItem] = []\n            for item in self.items {\n                switch item.kind {\n                case .file(let data):\n                    let bookmark = Bookmark(data: data)\n                    if await bookmark.validate() {\n                        keep.append(item)\n                    } else {\n                        item.cleanupStoredData()\n                    }\n                default:\n                    keep.append(item)\n                }\n            }\n            await MainActor.run { self.items = keep }\n        }\n    }\n\n\n    func resolveFileURL(for item: ShelfItem) -> URL? {\n        guard case .file(let bookmarkData) = item.kind else { return nil }\n        let bookmark = Bookmark(data: bookmarkData)\n        let result = bookmark.resolve()\n        if let refreshed = result.refreshedData, refreshed != bookmarkData {\n            NSLog(\"Bookmark for \\(item) stale; refreshing\")\n            scheduleDeferredBookmarkUpdate(for: item, bookmark: refreshed)\n        }\n        return result.url\n    }\n\n    func resolveAndUpdateBookmark(for item: ShelfItem) -> URL? {\n        guard case .file(let bookmarkData) = item.kind else { return nil }\n        let bookmark = Bookmark(data: bookmarkData)\n        let result = bookmark.resolve()\n        if let refreshed = result.refreshedData, refreshed != bookmarkData {\n            NSLog(\"Bookmark for \\(item) stale; refreshing\")\n            updateBookmark(for: item, bookmark: refreshed)\n        }\n        return result.url\n    }\n\n    func resolveFileURLs(for items: [ShelfItem]) -> [URL] {\n        var urls: [URL] = []\n        for it in items {\n            if let u = resolveFileURL(for: it) { urls.append(u) }\n        }\n        return urls\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Views/DragPreviewView.swift",
    "content": "import SwiftUI\nimport AppKit\n\nstruct DragPreviewView: View {\n    let thumbnail: NSImage?\n    let displayName: String\n\n    var body: some View {\n        VStack(alignment: .center, spacing: 4) {\n            Image(nsImage: thumbnail ?? NSImage())\n                .resizable()\n                .aspectRatio(contentMode: .fit)\n                .frame(width: 56, height: 56)\n                .clipShape(RoundedRectangle(cornerRadius: 12))\n\n            Text(displayName)\n                .font(.system(size: 12, weight: .medium))\n                .foregroundColor(.white)\n                .lineLimit(2)\n                .truncationMode(.middle)\n                .multilineTextAlignment(.center)\n                .padding(.horizontal, 8)\n                .padding(.vertical, 2)\n                .background(RoundedRectangle(cornerRadius: 4).fill(Color.accentColor))\n                .frame(alignment: .top)\n        }\n        .frame(width: 105)\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Views/FileShareView.swift",
    "content": "//\n//  FileShareView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport AppKit\nimport Defaults\nimport SwiftUI\nimport UniformTypeIdentifiers\n\nstruct FileShareView: View {\n    @EnvironmentObject private var vm: BoringViewModel\n    @StateObject private var quickShare = QuickShareService.shared\n    @Default(.quickShareProvider) var quickShareProvider: String\n\n    @State private var hostView: NSView?\n    @State private var interactionNonce: UUID = .init()\n    @State private var isProcessing = false\n    \n    private var selectedProvider: QuickShareProvider {\n        quickShare.availableProviders.first(where: { $0.id == quickShareProvider }) ?? QuickShareProvider(id: \"System Share Menu\", imageData: nil, supportsRawText: true)\n    }\n\n    var body: some View {\n        dropArea\n            .background(NSViewHost(view: $hostView))\n            .onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data, .image], isTargeted: $vm.dropZoneTargeting) { providers in\n                interactionNonce = .init()\n                vm.dropEvent = true\n                Task { await handleDrop(providers) }\n                return true\n            }\n            .onTapGesture {\n                Task {\n                    await handleClick()\n                }\n            }\n    }\n\n    private var dropArea: some View {\n        ZStack {\n            RoundedRectangle(cornerRadius: 12)\n                .fill(\n                    LinearGradient(colors: [Color.black.opacity(0.35), Color.black.opacity(0.20)], startPoint: .topLeading, endPoint: .bottomTrailing)\n                )\n                .overlay(\n                    RoundedRectangle(cornerRadius: 12)\n                        .stroke(\n                            vm.dropZoneTargeting\n                                ? Color.accentColor.opacity(0.9)\n                                : Color.white.opacity(0.1),\n                            style: StrokeStyle(lineWidth: 3, lineCap: .round, dash: [10])\n                        )\n                )\n                .shadow(color: Color.black.opacity(0.6), radius: 6, x: 0, y: 2)\n\n            // Content\n            VStack(spacing: 5) {\n                ZStack {\n                    Circle()\n                        .fill(Color.white.opacity(\n                            vm.dropZoneTargeting ? 0.11 : 0.09\n                        ))\n                        .frame(width: 55, height: 55)\n                    Image(systemName: \"square.and.arrow.up\")\n                    Group {\n                        if let imgData = selectedProvider.imageData, let nsImg = NSImage(data: imgData) {\n                            Image(nsImage: nsImg)\n                                .resizable()\n                                .aspectRatio(contentMode: .fit)\n                        } else {\n                            Image(systemName: \"square.and.arrow.up\")\n                        }\n                    }\n                    .frame(width: 34, height: 34)\n                        .foregroundStyle(\n                            vm.dropZoneTargeting ? Color.accentColor : Color.gray\n                        )\n                        .scaleEffect(\n                            vm.dropZoneTargeting ? 1.06 : 1.0\n                        )\n                        .animation(.spring(response: 0.36, dampingFraction: 0.7), value: vm.dropZoneTargeting)\n                }\n\n                Text(selectedProvider.id)\n                    .font(.system(.headline, design: .rounded))\n                    .foregroundColor(.white.opacity(0.8))\n\n            }\n            .padding(18)\n            \n            // Loading overlay\n            if isProcessing || quickShare.isPickerOpen {\n                RoundedRectangle(cornerRadius: 12)\n                    .fill(.black.opacity(0.3))\n                    .overlay(\n                        ProgressView()\n                            .progressViewStyle(CircularProgressViewStyle(tint: .white))\n                            .scaleEffect(0.8)\n                    )\n            }\n        }\n        .contentShape(RoundedRectangle(cornerRadius: 12))\n    }\n\n    // MARK: - Actions\n\n    private func handleDrop(_ providers: [NSItemProvider]) async {\n        isProcessing = true\n        defer { isProcessing = false }\n        await quickShare.shareDroppedFiles(providers, using: selectedProvider, from: hostView)\n    }\n    \n    private func handleClick() async {\n        await quickShare.showFilePicker(for: selectedProvider, from: hostView)\n    }\n}\n\n// MARK: - Host NSView extractor for anchoring share sheet\n\nprivate struct NSViewHost: NSViewRepresentable {\n    @Binding var view: NSView?\n    \n    func makeNSView(context: Context) -> NSView {\n        let v = NSView(frame: .zero)\n        DispatchQueue.main.async { self.view = v }\n        return v\n    }\n    \n    func updateNSView(_ nsView: NSView, context: Context) {\n        DispatchQueue.main.async { self.view = nsView }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Views/ShelfItemView.swift",
    "content": "//\n//  ShelfItemView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport AppKit\nimport SwiftUI\nimport Defaults\n\nimport QuickLook\n\nstruct ShelfItemView: View {\n    let item: ShelfItem\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject var selection = ShelfSelectionModel.shared\n    @StateObject private var viewModel: ShelfItemViewModel\n    @EnvironmentObject private var quickLookService: QuickLookService\n    @State private var showStack = false\n    @State private var cachedPreviewImage: NSImage?\n    @State private var debouncedDropTarget = false\n\n    private var isSelected: Bool { viewModel.isSelected }\n    private var shouldHideDuringDrag: Bool { selection.isDragging && selection.isSelected(item.id) && false }\n    \n    init(item: ShelfItem) {\n        self.item = item\n        _viewModel = StateObject(wrappedValue: ShelfItemViewModel(item: item))\n    }\n\n    var body: some View {\n        ZStack {\n            if !shouldHideDuringDrag {\n                VStack(alignment: .center, spacing: 2) {\n                    iconView\n                    textView\n                }\n                .frame(width: 105)\n                .padding(.vertical, 10)\n                .padding(.horizontal, 5)\n                .background(backgroundView)\n                .contentShape(Rectangle())\n                .animation(.easeInOut(duration: 0.1), value: debouncedDropTarget)\n                .animation(.easeInOut(duration: 0.1), value: isSelected)\n\n                DraggableClickHandler(\n                    item: item,\n                    viewModel: viewModel,\n                    cachedPreviewImage: $cachedPreviewImage,\n                    dragPreviewContent: {\n                        DragPreviewView(thumbnail: viewModel.thumbnail ?? item.icon, displayName: item.displayName)\n                    },\n                    onRightClick: viewModel.handleRightClick,\n                    onClick: { event, nsview in\n                        viewModel.handleClick(event: event, view: nsview)\n                    }\n                )\n            } else {\n                Color.clear\n                    .frame(width: 105)\n                    .padding(.vertical, 10)\n                    .padding(.horizontal, 5)\n            }\n        }\n        .onChange(of: viewModel.isDropTargeted) { _, targeted in\n            vm.dragDetectorTargeting = targeted\n            // Debounce drop target state changes\n            Task { @MainActor in\n                try? await Task.sleep(for: .milliseconds(50))\n                debouncedDropTarget = targeted\n            }\n        }\n        .onAppear {\n            Task { \n                await viewModel.loadThumbnail()\n                // Pre-render drag preview once on appear\n                if cachedPreviewImage == nil {\n                    cachedPreviewImage = await renderDragPreview()\n                }\n            }\n            viewModel.onQuickLookRequest = { urls in\n                quickLookService.show(urls: urls, selectFirst: true)\n            }\n        }\n        .onChange(of: viewModel.thumbnail) { _, _ in\n            // Invalidate cached preview when thumbnail changes\n            Task {\n                cachedPreviewImage = await renderDragPreview()\n            }\n        }\n        .quickLookPresenter(using: quickLookService)\n    }\n\n    // MARK: - View Components\n\n    private var iconView: some View {\n        Image(nsImage: viewModel.thumbnail ?? item.icon)\n            .resizable()\n            .aspectRatio(contentMode: .fit)\n            .frame(width: 56, height: 56)\n            .clipShape(RoundedRectangle(cornerRadius: 12))\n            .shadow(color: .black.opacity(0.15), radius: 3, x: 0, y: 2)\n    }\n\n    private var textView: some View {\n        Text(item.displayName)\n            .font(.system(size: 12, weight: .medium))\n            .foregroundStyle(.primary)\n            .lineLimit(2)\n            .truncationMode(.middle)\n            .multilineTextAlignment(.center)\n            .frame(height: 30, alignment: .top)\n    }\n\n    private var backgroundView: some View {\n        RoundedRectangle(cornerRadius: 12, style: .continuous)\n            .fill(backgroundColor)\n            .overlay(\n                RoundedRectangle(cornerRadius: 12, style: .continuous)\n                    .strokeBorder(\n                        strokeColor,\n                        lineWidth: strokeWidth\n                    )\n            )\n    }\n\n    private var backgroundColor: Color {\n        if debouncedDropTarget {\n            return Color.accentColor.opacity(0.25)\n        } else if isSelected {\n            return Color.accentColor.opacity(0.15)\n        } else {\n            return Color.clear\n        }\n    }\n\n    private var strokeColor: Color {\n        if debouncedDropTarget {\n            return Color.accentColor.opacity(0.9)\n        } else if isSelected {\n            return Color.accentColor.opacity(0.8)\n        } else {\n            return Color.clear\n        }\n    }\n\n    private var strokeWidth: CGFloat {\n        if debouncedDropTarget {\n            return 3\n        } else if isSelected {\n            return 2\n        } else {\n            return 1\n        }\n    }\n    \n    // MARK: - Drag Preview Rendering\n    \n    @MainActor\n    private func renderDragPreview() async -> NSImage {\n        let content = DragPreviewView(thumbnail: viewModel.thumbnail ?? item.icon, displayName: item.displayName)\n        let renderer = ImageRenderer(content: content)\n        renderer.scale = NSScreen.main?.backingScaleFactor ?? 2.0\n        return renderer.nsImage ?? (viewModel.thumbnail ?? item.icon)\n    }\n\n    \n}\n\n// MARK: - Draggable Click Handler with NSDraggingSource\nprivate struct DraggableClickHandler<Content: View>: NSViewRepresentable {\n    let item: ShelfItem\n    let viewModel: ShelfItemViewModel\n    @Binding var cachedPreviewImage: NSImage?\n    @ViewBuilder let dragPreviewContent: () -> Content\n    let onRightClick: (NSEvent, NSView) -> Void\n    let onClick: (NSEvent, NSView) -> Void\n    \n    func makeNSView(context: Context) -> DraggableClickView {\n        let view = DraggableClickView()\n        view.item = item\n        view.viewModel = viewModel\n        view.dragPreviewImage = cachedPreviewImage ?? renderDragPreview()\n        view.onRightClick = onRightClick\n        view.onClick = onClick\n        return view\n    }\n    \n    func updateNSView(_ nsView: DraggableClickView, context: Context) {\n        nsView.item = item\n        nsView.viewModel = viewModel\n        // Only update preview if cached version is available\n        if let cached = cachedPreviewImage {\n            nsView.dragPreviewImage = cached\n        }\n        nsView.onRightClick = onRightClick\n        nsView.onClick = onClick\n    }\n    \n    private func renderDragPreview() -> NSImage {\n        let content = dragPreviewContent()\n        let renderer = ImageRenderer(content: content)\n        renderer.scale = NSScreen.main?.backingScaleFactor ?? 2.0\n        \n        if let nsImage = renderer.nsImage {\n            return nsImage\n        }\n        \n        // Fallback to icon if rendering fails\n        return viewModel.thumbnail ?? item.icon\n    }\n    \n    final class DraggableClickView: NSView, NSDraggingSource {\n        var item: ShelfItem!\n        weak var viewModel: ShelfItemViewModel?\n        var dragPreviewImage: NSImage?\n        var onRightClick: ((NSEvent, NSView) -> Void)?\n        var onClick: ((NSEvent, NSView) -> Void)?\n\n        private var mouseDownEvent: NSEvent?\n        private let dragThreshold: CGFloat = 3.0\n        private var draggedURLs: [URL] = []\n        private var draggedItems: [ShelfItem] = []\n        \n        override func rightMouseDown(with event: NSEvent) {\n            onRightClick?(event, self)\n        }\n        \n        override func mouseDown(with event: NSEvent) {\n            mouseDownEvent = event\n            onClick?(event, self)\n        }\n        \n        override func mouseDragged(with event: NSEvent) {\n            guard let mouseDownEvent = mouseDownEvent else {\n                super.mouseDragged(with: event)\n                return\n            }\n            \n            let dragDistance = hypot(\n                event.locationInWindow.x - mouseDownEvent.locationInWindow.x,\n                event.locationInWindow.y - mouseDownEvent.locationInWindow.y\n            )\n            \n            if dragDistance > dragThreshold {\n                startDragSession(with: event)\n                self.mouseDownEvent = nil\n            } else {\n                super.mouseDragged(with: event)\n            }\n        }\n        \n        private func startDragSession(with event: NSEvent) {\n            // Prepare dragging items\n            let selectedItems = ShelfSelectionModel.shared.selectedItems(in: ShelfStateViewModel.shared.items)\n            let itemsToDrag: [ShelfItem]\n\n            if selectedItems.count > 1 && selectedItems.contains(where: { $0.id == item.id }) {\n                itemsToDrag = selectedItems\n            } else {\n                itemsToDrag = [item]\n            }\n\n            // Store items being dragged for auto-remove feature\n            draggedItems = itemsToDrag\n\n            // Create dragging items for AppKit\n            var draggingItems: [NSDraggingItem] = []\n\n            for dragItem in itemsToDrag {\n                if let pasteboardItem = createPasteboardItem(for: dragItem) {\n                    let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardItem)\n\n                    // Use the drag preview image\n                    let image = dragPreviewImage ?? dragItem.icon\n                    let imageFrame = NSRect(\n                        x: 0,\n                        y: 0,\n                        width: image.size.width,\n                        height: image.size.height\n                    )\n                    draggingItem.setDraggingFrame(imageFrame, contents: image)\n\n                    draggingItems.append(draggingItem)\n                }\n            }\n\n            guard !draggingItems.isEmpty else { return }\n\n            beginDraggingSession(with: draggingItems, event: event, source: self)\n        }\n        \n        private func createPasteboardItem(for item: ShelfItem) -> NSPasteboardItem? {\n            let pasteboardItem = NSPasteboardItem()\n\n            switch item.kind {\n            case .file:\n                guard let url = ShelfStateViewModel.shared.resolveAndUpdateBookmark(for: item) else {\n                    pasteboardItem.setString(item.displayName, forType: .string)\n                    return pasteboardItem\n                }\n                \n                // Start accessing security-scoped resource and keep it active during drag\n                if url.startAccessingSecurityScopedResource() {\n                    draggedURLs.append(url)\n                    NSLog(\"🔐 Started security-scoped access for drag: \\(url.path)\")\n                }\n                \n                pasteboardItem.setString(url.absoluteString, forType: .fileURL)\n                pasteboardItem.setString(url.path, forType: .string)\n                return pasteboardItem\n\n            case .text(let string):\n                pasteboardItem.setString(string, forType: .string)\n                return pasteboardItem\n\n            case .link(let url):\n                pasteboardItem.setString(url.absoluteString, forType: .URL)\n                pasteboardItem.setString(url.absoluteString, forType: .string)\n                return pasteboardItem\n            }\n        }\n        \n        // MARK: - NSDraggingSource\n        \n        func draggingSession(_ session: NSDraggingSession, sourceOperationMaskFor context: NSDraggingContext) -> NSDragOperation {\n            // When copyOnDrag is enabled, only allow copy operations\n            if Defaults[.copyOnDrag] {\n                return [.copy]\n            }\n            \n            switch context {\n            case .outsideApplication:\n                return [.copy, .move]\n            case .withinApplication:\n                return [.copy, .move, .generic]\n            @unknown default:\n                return [.copy]\n            }\n        }\n        \n        func draggingSession(_ session: NSDraggingSession, willBeginAt screenPoint: NSPoint) {\n            ShelfSelectionModel.shared.beginDrag()\n        }\n        \n        \n        func draggingSession(_ session: NSDraggingSession, endedAt screenPoint: NSPoint, operation: NSDragOperation) {\n            ShelfSelectionModel.shared.endDrag()\n\n            // Stop accessing security-scoped resources after drag completes\n            for url in draggedURLs {\n                url.stopAccessingSecurityScopedResource()\n                NSLog(\"🔐 Stopped security-scoped access after drag: \\(url.path)\")\n            }\n            draggedURLs.removeAll()\n\n            // Auto-remove items from shelf if enabled and drag succeeded\n            if Defaults[.autoRemoveShelfItems] && !operation.isEmpty {\n                for item in draggedItems {\n                    ShelfStateViewModel.shared.remove(item)\n                }\n            }\n            draggedItems.removeAll()\n        }\n        \n        func ignoreModifierKeys(for session: NSDraggingSession) -> Bool {\n            return false\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Shelf/Views/ShelfView.swift",
    "content": "//\n//  ShelfItemView.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\nimport SwiftUI\nimport AppKit\n\nstruct ShelfView: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @StateObject var tvm = ShelfStateViewModel.shared\n    @StateObject var selection = ShelfSelectionModel.shared\n    @StateObject private var quickLookService = QuickLookService()\n    private let spacing: CGFloat = 8\n\n    var body: some View {\n        HStack(spacing: 12) {\n            FileShareView()\n                .aspectRatio(1, contentMode: .fit)\n                .environmentObject(vm)\n            panel\n                .onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data], isTargeted: $vm.dragDetectorTargeting) { providers in\n                    handleDrop(providers: providers)\n                }\n        }\n        // Bind Quick Look to shelf selection\n        .onChange(of: selection.selectedIDs) {\n            updateQuickLookSelection()\n        }\n        .quickLookPresenter(using: quickLookService)\n    }\n    \n    private func handleDrop(providers: [NSItemProvider]) -> Bool {\n        guard !selection.isDragging else { return false }\n        vm.dropEvent = true\n        ShelfStateViewModel.shared.load(providers)\n        return true\n    }\n    \n    private func updateQuickLookSelection() {\n        guard quickLookService.isQuickLookOpen && !selection.selectedIDs.isEmpty else { return }\n        \n        let selectedItems = selection.selectedItems(in: tvm.items)\n        let urls: [URL] = selectedItems.compactMap { item in\n            if let fileURL = item.fileURL {\n                return fileURL\n            }\n            if case .link(let url) = item.kind {\n                return url\n            }\n            return nil\n        }\n        \n        if !urls.isEmpty {\n            quickLookService.updateSelection(urls: urls)\n        }\n    }\n\n    var panel: some View {\n        RoundedRectangle(cornerRadius: 16)\n            .stroke(\n                vm.dragDetectorTargeting\n                    ? Color.accentColor.opacity(0.9)\n                    : Color.white.opacity(0.1),\n                style: StrokeStyle(lineWidth: 3, lineCap: .round, dash: [10])\n            )\n            .overlay {\n                content\n                    .padding()\n            }\n            .transaction { transaction in\n                transaction.animation = vm.animation\n            }\n            .contentShape(Rectangle())\n            .onTapGesture { selection.clear() }\n    }\n\n    var content: some View {\n        Group {\n            if tvm.isEmpty {\n                VStack(spacing: 10) {\n                    Image(systemName: \"tray.and.arrow.down\")\n                        .symbolVariant(.fill)\n                        .symbolRenderingMode(.hierarchical)\n                        .foregroundStyle(.white, .gray)\n                        .imageScale(.large)\n                    \n                    Text(\"Drop files here\")\n                        .foregroundStyle(.gray)\n                        .font(.system(.title3, design: .rounded))\n                        .fontWeight(.medium)\n                }\n            } else {\n                ScrollView(.horizontal) {\n                    HStack(spacing: spacing) {\n                        ForEach(tvm.items) { item in\n                            ShelfItemView(item: item)\n                                .environmentObject(quickLookService)\n                        }\n                    }\n                }\n                .padding(-spacing)\n                .scrollIndicators(.never)\n                .onDrop(of: [.fileURL, .url, .utf8PlainText, .plainText, .data], isTargeted: $vm.dragDetectorTargeting) { providers in\n                    handleDrop(providers: providers)\n                }\n            }\n        }\n        .onAppear {\n            ShelfStateViewModel.shared.cleanupInvalidItems()\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Tabs/TabButton.swift",
    "content": "//\n//  TabButton.swift\n//  boringNotch\n//\n//  Created by Hugo Persson on 2024-08-24.\n//\n\nimport SwiftUI\n\nstruct TabButton: View {\n    let label: String\n    let icon: String\n    let selected: Bool\n    let onClick: () -> Void\n    \n    var body: some View {\n        Button(action: onClick) {\n            Image(systemName: icon)\n                .padding(.horizontal, 15)\n                .contentShape(Capsule())\n        }\n        .buttonStyle(PlainButtonStyle())\n    }\n}\n\n#Preview {\n    TabButton(label: \"Home\", icon: \"tray.fill\", selected: true) {\n        print(\"Tapped\")\n    }\n}\n"
  },
  {
    "path": "boringNotch/components/Tabs/TabSelectionView.swift",
    "content": "//\n//  TabSelectionView.swift\n//  boringNotch\n//\n//  Created by Hugo Persson on 2024-08-25.\n//\n\nimport SwiftUI\n\nstruct TabModel: Identifiable {\n    let id = UUID()\n    let label: String\n    let icon: String\n    let view: NotchViews\n}\n\nlet tabs = [\n    TabModel(label: \"Home\", icon: \"house.fill\", view: .home),\n    TabModel(label: \"Shelf\", icon: \"tray.fill\", view: .shelf)\n]\n\nstruct TabSelectionView: View {\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @Namespace var animation\n    var body: some View {\n        HStack(spacing: 0) {\n            ForEach(tabs) { tab in\n                    TabButton(label: tab.label, icon: tab.icon, selected: coordinator.currentView == tab.view) {\n                        withAnimation(.smooth) {\n                            coordinator.currentView = tab.view\n                        }\n                    }\n                    .frame(height: 26)\n                    .foregroundStyle(tab.view == coordinator.currentView ? .white : .gray)\n                    .background {\n                        if tab.view == coordinator.currentView {\n                            Capsule()\n                                .fill(coordinator.currentView == tab.view ? Color(nsColor: .secondarySystemFill) : Color.clear)\n                                .matchedGeometryEffect(id: \"capsule\", in: animation)\n                        } else {\n                            Capsule()\n                                .fill(coordinator.currentView == tab.view ? Color(nsColor: .secondarySystemFill) : Color.clear)\n                                .matchedGeometryEffect(id: \"capsule\", in: animation)\n                                .hidden()\n                        }\n                    }\n            }\n        }\n        .clipShape(Capsule())\n    }\n}\n\n#Preview {\n    BoringHeader().environmentObject(BoringViewModel())\n}\n"
  },
  {
    "path": "boringNotch/components/TestView.swift",
    "content": "//\n//  TestView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 14/08/2024.\n//\n\nimport SwiftUI\n\nstruct FluidSlider: View {\n    private let color: Color = Color.white\n    @State private var offset: CGFloat = 0\n    var rectSize = CGSize(width: 300, height: 50)\n    var rectSize2 = CGSize(width: 200, height: 18)\n    var circleSize: CGFloat = 35\n    @GestureState var isDragging: Bool = false\n    @State var previousOffset: CGFloat = 0\n    @State private var isBeating: Bool = false\n    \n    var body: some View {\n        HStack {\n            slider\n                .frame(width: rectSize2.width, height: circleSize)\n        }\n        .padding()\n        .background(.black)\n    }\n    \n    private var slider: some View {\n        ZStack {\n            Canvas { context, size in\n                context.addFilter(.alphaThreshold(min: 0.5, max: 1, color: color))\n                context.addFilter(.blur(radius: 10))\n                \n                context.drawLayer { ctx in\n                    if let rectangle = ctx.resolveSymbol(id: \"Capsule\") {\n                        ctx.draw(rectangle, at: CGPoint(x: size.width/2, y: size.height/2))\n                    }\n                    if let circle = ctx.resolveSymbol(id: \"Circle\") {\n                        ctx.draw(circle, at: CGPoint(x: size.width/2 - rectSize2.width/2 + circleSize/2, y: size.height/2))\n                    }\n                }\n            } symbols: {\n                Capsule()\n                    .frame(width: rectSize2.width, height: rectSize2.height, alignment: .center)\n                    .tag(\"Capsule\")\n                \n                Circle()\n                    .frame(width: circleSize, height: circleSize, alignment: .center)\n                    .offset(x: offset)\n                    .animation(.spring(), value: isDragging)\n                    .tag(\"Circle\")\n            }\n            .simultaneousGesture(\n                DragGesture(minimumDistance: 0)\n                    .updating($isDragging, body: { _, state, _ in\n                        state = true\n                    })\n                    .onChanged({ value in\n                        self.offset = min(max(self.previousOffset + value.translation.width, 0), rectSize2.width - circleSize)\n                    })\n                    .onEnded({ value in\n                        self.previousOffset = self.offset\n                    })\n            )\n            Circle()\n                .fill(Color.black)\n                .frame(width: circleSize * 0.6)\n                .overlay {\n                    Image(systemName: \"speaker.wave.2.fill\")\n                        .imageScale(.small)\n                }\n                .offset(x: (-rectSize2.width/2) + (circleSize/2))\n                .offset(x: offset)\n                .animation(.spring(), value: isDragging)\n                .allowsHitTesting(false)\n        }\n    }\n    \n    \n    private var animation: Animation {\n        .spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0.5)\n    }\n    \n    private var percentage: Int {\n        Int((offset) / (rectSize.width - circleSize) * 100)\n    }\n}\n\n#Preview {\n    FluidSlider()\n}\n"
  },
  {
    "path": "boringNotch/components/Tips/TipStore.swift",
    "content": "//\n//  TipStore.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 15/09/2024.\n//\n\nimport SwiftUI\nimport TipKit\n\nstruct HUDsTip: Tip {\n    var title: Text {\n        Text(\"Enhance your experience with HUDs\")\n    }\n    \n    \n    var message: Text? {\n        Text(\"Unlock advanced features and improve your experience. Upgrade now for more customizations!\")\n    }\n    \n    \n    var image: Image? {\n        AppIcon(for: \"theboringteam.boringNotch\")\n    }\n    \n    var actions: [Action] {\n        Action {\n            Text(\"More\")\n        }\n    }\n}\n\nstruct CBTip: Tip {\n    var title: Text {\n        Text(\"Boost your productivity with Clipboard Manager\")\n    }\n    \n    \n    var message: Text? {\n        Text(\"Easily copy, store, and manage your most-used content. Upgrade now for advanced features like multi-item storage and quick access!\")\n    }\n    \n    \n    var image: Image? {\n        AppIcon(for: \"theboringteam.boringNotch\")\n    }\n    \n    var actions: [Action] {\n        Action {\n            Text(\"More\")\n        }\n    }\n}\n\nstruct TipsView: View {\n    var hudTip = HUDsTip()\n    var cbTip = CBTip()\n    var body: some View {\n        VStack {\n            TipView(hudTip)\n            TipView(cbTip)\n        }\n        .task {\n            try? Tips.configure([\n                .displayFrequency(.immediate),\n                .datastoreLocation(.applicationDefault)\n            ])\n        }\n    }\n}\n\n#Preview {\n    TipsView()\n}\n"
  },
  {
    "path": "boringNotch/components/Webcam/WebcamView.swift",
    "content": "//\n//  WebcamView.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 19/08/24.\n//\n\nimport AVFoundation\nimport Defaults\nimport SwiftUI\n\nstruct CameraPreviewView: View {\n    @EnvironmentObject var vm: BoringViewModel\n    @ObservedObject var webcamManager: WebcamManager\n    \n    // Track if authorization request is in progress to avoid multiple requests\n    @State private var isRequestingAuthorization: Bool = false\n\n    var body: some View {\n        GeometryReader { geometry in\n            ZStack {\n                if let previewLayer = webcamManager.previewLayer {\n                    CameraPreviewLayerView(previewLayer: previewLayer)\n                        .scaleEffect(x: -1, y: 1)\n                        .clipShape(RoundedRectangle(cornerRadius: Defaults[.mirrorShape] == .rectangle ? !Defaults[.cornerRadiusScaling] ? MusicPlayerImageSizes.cornerRadiusInset.closed : MusicPlayerImageSizes.cornerRadiusInset.opened : 100))\n                        .frame(width: geometry.size.width, height: geometry.size.width)\n                        .opacity(webcamManager.isSessionRunning ? 1 : 0)\n                }\n\n                if !webcamManager.isSessionRunning {\n                    ZStack {\n                        RoundedRectangle(cornerRadius: Defaults[.mirrorShape] == .rectangle ? !Defaults[.cornerRadiusScaling] ? MusicPlayerImageSizes.cornerRadiusInset.closed : 12 : 100)\n                            .fill(Color(red: 20/255, green: 20/255, blue: 20/255))\n                            .strokeBorder(.white.opacity(0.04), lineWidth: 1)\n                            .frame(width: geometry.size.width, height: geometry.size.width)\n                        VStack(spacing: 8) {\n                            Image(systemName: webcamManager.authorizationStatus == .denied ? \"exclamationmark.triangle\" : \"web.camera\")\n                                .foregroundStyle(.gray)\n                                .font(.system(size: geometry.size.width/3.5))\n                            Text(webcamManager.authorizationStatus == .denied ? \"Access Denied\" : \"Mirror\")\n                                .font(.caption2)\n                                .foregroundColor(.gray)\n                        }\n                    }\n                }\n            }\n            .onTapGesture {\n                handleCameraTap()\n            }\n            .onDisappear {\n                webcamManager.stopSession()\n            }\n        }\n        .aspectRatio(1, contentMode: .fit)\n    }\n    \n    private func handleCameraTap() {\n        if isRequestingAuthorization {\n            return // Prevent multiple authorization requests\n        }\n        \n        switch webcamManager.authorizationStatus {\n        case .authorized:\n            if webcamManager.isSessionRunning {\n                webcamManager.stopSession()\n            } else if webcamManager.cameraAvailable {\n                webcamManager.startSession()\n            }\n        case .denied, .restricted:\n            DispatchQueue.main.async {\n                let alert = NSAlert()\n                alert.messageText = \"Camera Access Required\"\n                alert.informativeText = \"Please allow camera access in System Settings to use the mirror feature.\"\n                alert.addButton(withTitle: \"Open System Settings\")\n                alert.addButton(withTitle: \"Cancel\")\n\n                if alert.runModal() == .alertFirstButtonReturn {\n                    if let settingsURL = URL(string: \"x-apple.systempreferences:com.apple.preference.security?Privacy_Camera\") {\n                        NSWorkspace.shared.open(settingsURL)\n                    }\n                }\n            }\n        case .notDetermined:\n            isRequestingAuthorization = true\n            webcamManager.checkAndRequestVideoAuthorization()\n            // Reset the request flag after a reasonable delay\n            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n                isRequestingAuthorization = false\n            }\n        @unknown default:\n            break\n        }\n    }\n}\n\nstruct CameraPreviewLayerView: NSViewRepresentable {\n    let previewLayer: AVCaptureVideoPreviewLayer\n\n    func makeNSView(context: Context) -> NSView {\n        let view = NSView(frame: .zero)\n        previewLayer.frame = view.bounds\n        previewLayer.videoGravity = .resizeAspectFill\n        view.layer = previewLayer\n        view.wantsLayer = true\n        return view\n    }\n\n    func updateNSView(_ nsView: NSView, context: Context) {\n        CATransaction.begin()\n        CATransaction.setDisableActions(true)\n        previewLayer.frame = nsView.bounds\n        CATransaction.commit()\n    }\n}\n\n#Preview {\n    CameraPreviewView(webcamManager: .shared)\n}\n"
  },
  {
    "path": "boringNotch/components/WhatsNewView.swift",
    "content": "//\n//  WhatsNewView.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 09/08/2024.\n//\n\nimport SwiftUI\n\nstruct WhatsNewView: View {\n    @Binding var isPresented: Bool\n    \n    var body: some View {\n        VStack(spacing: 20) {\n            Text(\"What's New\")\n                .font(.largeTitle)\n            \n            VStack(alignment: .leading, spacing: 10) {\n                Text(\"• New feature 1\")\n                Text(\"• Improved performance\")\n                Text(\"• Bug fixes\")\n            }\n            \n            Button(\"Got it!\") {\n                isPresented = false\n            }\n        }\n        .frame(width: 300, height: 200)\n        .padding()\n    }\n}\n\n#Preview {\n    WhatsNewView(isPresented: .constant(true))\n}\n"
  },
  {
    "path": "boringNotch/enums/generic.swift",
    "content": "//\n//  generic.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 04/08/24.\n//\n\nimport Foundation\nimport Defaults\n\npublic enum Style {\n    case notch\n    case floating\n}\n\npublic enum ContentType: Int, Codable, Hashable, Equatable {\n    case normal\n    case menu\n    case settings\n}\n\npublic enum NotchState {\n    case closed\n    case open\n}\n\npublic enum NotchViews {\n    case home\n    case shelf\n}\n\nenum SettingsEnum {\n    case general\n    case about\n    case charge\n    case download\n    case mediaPlayback\n    case hud\n    case shelf\n    case extensions\n}\n\nenum DownloadIndicatorStyle: String, Defaults.Serializable {\n    case progress = \"Progress\"\n    case percentage = \"Percentage\"\n}\n\nenum DownloadIconStyle: String, Defaults.Serializable {\n    case onlyAppIcon = \"Only app icon\"\n    case onlyIcon = \"Only download icon\"\n    case iconAndAppIcon = \"Icon and app icon\"\n}\n\nenum MirrorShapeEnum: String, Defaults.Serializable {\n    case rectangle = \"Rectangular\"\n    case circle = \"Circular\"\n}\n\nenum WindowHeightMode: String, Defaults.Serializable {\n    case matchMenuBar = \"Match menubar height\"\n    case matchRealNotchSize = \"Match real notch height\"\n    case custom = \"Custom height\"\n}\n\nenum SliderColorEnum: String, CaseIterable, Defaults.Serializable {\n    case white = \"White\"\n    case albumArt = \"Match album art\"\n    case accent = \"Accent color\"\n}\n"
  },
  {
    "path": "boringNotch/extensions/ActionBar.swift",
    "content": "//\n//  ActionBar.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 15/09/2024.\n//\n\nimport SwiftUI\n\nextension View {\n    func actionBar<Content: View>(padding: CGFloat = 10, @ViewBuilder content: () -> Content) -> some View {\n        self\n            .padding(.bottom, 24)\n            .overlay(alignment: .bottom) {\n                VStack(spacing: -1) {\n                    Divider()\n                    HStack(spacing: 0) {\n                        content()\n                            .buttonStyle(PlainButtonStyle())\n                    }\n                    .frame(height: 16)\n                    .padding(.vertical, 4)\n                    .padding(.horizontal, padding)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n                }\n                .frame(height: 24)\n                .background(.separator)\n            }\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/BundleInfos.swift",
    "content": "//\n//  BundleInfos.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 08/08/2024.\n//\n\nimport SwiftUI\n\nextension Bundle {\n    var releaseVersionNumber: String? {\n        return infoDictionary?[\"CFBundleShortVersionString\"] as? String\n    }\n    var buildVersionNumber: String? {\n        return infoDictionary?[\"CFBundleVersion\"] as? String\n    }\n    var releaseVersionNumberPretty: String {\n        return \"v\\(releaseVersionNumber ?? \"1.0.0\")\"\n    }\n    \n    var iconFileName: String? {\n        guard let icons = infoDictionary?[\"CFBundleIcons\"] as? [String: Any],\n              let primaryIcon = icons[\"CFBundlePrimaryIcon\"] as? [String: Any],\n              let iconFiles = primaryIcon[\"CFBundleIconFiles\"] as? [String],\n              let iconFileName = iconFiles.last\n        else { return nil }\n        return iconFileName\n    }\n}\n\nstruct BundleAppIcon: View {\n    var body: some View {\n        Bundle.main.iconFileName\n            .flatMap { NSImage(named: $0) }\n            .map { Image(nsImage: $0) }\n    }\n}\n\nfunc isNewVersion() -> Bool {\n    let defaults = UserDefaults.standard\n    let currentVersion = Bundle.main.releaseVersionNumber ?? \"1.0\"\n    let savedVersion = defaults.string(forKey: \"LastVersionRun\") ?? \"\"\n    \n    if currentVersion != savedVersion {\n        defaults.set(currentVersion, forKey: \"LastVersionRun\")\n        return true\n    }\n    return false\n}\n\nfunc isExtensionRunning(_ bundleID: String) -> Bool {\n    if let _ = NSWorkspace.shared.runningApplications.first(where: {$0.bundleIdentifier == bundleID}) {\n        return true\n    }\n    \n    return false\n}\n"
  },
  {
    "path": "boringNotch/extensions/Button+Bouncing.swift",
    "content": "//\n//  Button+Bouncing.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 19/08/24.\n//\nimport SwiftUI\nimport Defaults\n\nstruct BouncingButtonStyle: ButtonStyle {\n    let vm: BoringViewModel\n    @State private var isPressed = false\n    \n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n            .padding(12)\n            .background(\n                RoundedRectangle(cornerRadius: Defaults[.cornerRadiusScaling] ? 10 : MusicPlayerImageSizes.cornerRadiusInset.closed)\n                    .fill(Color(red: 20/255, green: 20/255, blue: 20/255))\n                    .strokeBorder(.white.opacity(0.04), lineWidth: 1)\n            )\n            .scaleEffect(isPressed ? 0.9 : 1.0)\n            .onChange(of: configuration.isPressed) { _, _ in\n                withAnimation(.spring(response: 0.3, dampingFraction: 0.3, blendDuration: 0.3)) {\n                    isPressed.toggle()\n                }\n            }\n    }\n}\n\nextension Button {\n    func bouncingStyle(vm: BoringViewModel) -> some View {\n        self.buttonStyle(BouncingButtonStyle(vm: vm))\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/Color+AccentColor.swift",
    "content": "//\n//  Color+AccentColor.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-24.\n//\n\nimport SwiftUI\nimport Defaults\n\nextension Color {\n    static var effectiveAccent: Color {\n        if Defaults[.useCustomAccentColor],\n           let colorData = Defaults[.customAccentColorData],\n           let nsColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData) {\n            return Color(nsColor: nsColor)\n        }\n        return .accentColor\n    }\n    \n    /// Returns a darker version of the accent color suitable for backgrounds\n    static var effectiveAccentBackground: Color {\n        if Defaults[.useCustomAccentColor],\n           let colorData = Defaults[.customAccentColorData],\n           let nsColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData) {\n            return Color(nsColor: nsColor.withSystemEffect(.disabled))\n        }\n        return Color.effectiveAccent.opacity(0.25)\n    }\n}\n\nextension NSColor {\n    static var effectiveAccent: NSColor {\n        if Defaults[.useCustomAccentColor],\n           let colorData = Defaults[.customAccentColorData],\n           let nsColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData) {\n            return nsColor\n        }\n        return NSColor.controlAccentColor\n    }\n    \n    /// Returns a darker version of the accent color as NSColor suitable for backgrounds\n    static var effectiveAccentBackground: NSColor {\n        if Defaults[.useCustomAccentColor],\n           let colorData = Defaults[.customAccentColorData],\n           let nsColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSColor.self, from: colorData) {\n            return nsColor.withSystemEffect(.disabled)\n        }\n        return NSColor.controlAccentColor.withAlphaComponent(0.25)\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/ConditionalModifier.swift",
    "content": "//\n//  ConditionalModifier.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 20/08/2024.\n//\n\nimport SwiftUI\n\nextension View {\n    @ViewBuilder func conditionalModifier<Content: View>(_ condition: Bool, transform: (Self) -> Content) -> some View {\n        if condition {\n            transform(self)\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/DataTypes+Extensions.swift",
    "content": "    //\n    //  DataTypes+Extensions.swift\n    //  boringNotch\n    //\n    //  Created by Harsh Vardhan  Goswami  on 27/08/24.\n    //\n\nimport Foundation\n\n\n\nextension Date {\n    static var yesterday: Date { return Date().dayBefore }\n    static var tomorrow:  Date { return Date().dayAfter }\n    var dayBefore: Date {\n        return Calendar.current.date(byAdding: .day, value: -1, to: noon)!\n    }\n    var dayAfter: Date {\n        return Calendar.current.date(byAdding: .day, value: 1, to: noon)!\n    }\n    var noon: Date {\n        return Calendar.current.date(bySettingHour: 12, minute: 0, second: 0, of: self)!\n    }\n    \n    var date: String {\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"dd\"\n        return dateFormatter.string(from: self)\n    }\n    \n    var month: String {\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"MMMM\"\n        return dateFormatter.string(from: self)\n    }\n    \n    func dayOfTheWeek(dayOfWeek: Int) -> String {\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"EEE\"\n        let date = Calendar.current.date(bySetting: .weekday, value: dayOfWeek, of: self) ?? self\n        return dateFormatter.string(from: date)\n    }\n}\n\nextension NSSize {\n    var s: String { \"\\(width.i)×\\(height.i)\" }\n    \n    var aspectRatio: Double {\n        width / height\n    }\n    func scaled(by factor: Double) -> CGSize {\n        CGSize(width: (width * factor).evenInt, height: (height * factor).evenInt)\n    }\n    \n}\n\nextension Int {\n    var s: String {\n        String(self)\n    }\n    var d: Double {\n        Double(self)\n    }\n}\n\nextension Double {\n    @inline(__always) @inlinable var intround: Int {\n        rounded().i\n    }\n    \n    @inline(__always) @inlinable var i: Int {\n        Int(self)\n    }\n    \n    var evenInt: Int {\n        let x = intround\n        return x + x % 2\n    }\n}\n\nextension CGFloat {\n    @inline(__always) @inlinable var intround: Int {\n        rounded().i\n    }\n    \n    @inline(__always) @inlinable var i: Int {\n        Int(self)\n    }\n    \n    var evenInt: Int {\n        let x = intround\n        return x + x % 2\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/KeyboardShortcutsHelper.swift",
    "content": "//\n//  KeyboardShortcutsHelper.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 16/08/2024.\n//\n\nimport KeyboardShortcuts\nimport SwiftUI\nimport Carbon\n\n\nextension View {\n    \n    public func keyboardShortcut(_ shortcut: KeyboardShortcuts.Name) -> some View {\n        if let shortcut = shortcut.shortcut {\n            if let keyEquivalent = shortcut.toKeyEquivalent() {\n                return AnyView(self.keyboardShortcut(keyEquivalent, modifiers: shortcut.toEventModifiers()))\n            }\n        }\n        \n        return AnyView(self)\n    }\n    \n}\n\nextension KeyboardShortcuts.Shortcut {\n    \n    func toKeyEquivalent() -> KeyEquivalent? {\n        let carbonKeyCode = UInt16(self.carbonKeyCode)\n        let maxNameLength = 4\n        var nameBuffer = [UniChar](repeating: 0, count : maxNameLength)\n        var nameLength = 0\n        \n        let modifierKeys = UInt32(alphaLock >> 8) & 0xFF // Caps Lock\n        var deadKeys: UInt32 = 0\n        let keyboardType = UInt32(LMGetKbdType())\n        \n        let source = TISCopyCurrentKeyboardLayoutInputSource().takeRetainedValue()\n        guard let ptr = TISGetInputSourceProperty(source, kTISPropertyUnicodeKeyLayoutData) else {\n            NSLog(\"Could not get keyboard layout data\")\n            return nil\n        }\n        let layoutData = Unmanaged<CFData>.fromOpaque(ptr).takeUnretainedValue() as Data\n        let osStatus = layoutData.withUnsafeBytes {\n            UCKeyTranslate($0.bindMemory(to: UCKeyboardLayout.self).baseAddress, carbonKeyCode, UInt16(kUCKeyActionDown),\n                           modifierKeys, keyboardType, UInt32(kUCKeyTranslateNoDeadKeysMask),\n                           &deadKeys, maxNameLength, &nameLength, &nameBuffer)\n        }\n        guard osStatus == noErr else {\n            NSLog(\"Code: 0x%04X  Status: %+i\", carbonKeyCode, osStatus);\n            return nil\n        }\n        \n        return KeyEquivalent(Character(String(utf16CodeUnits: nameBuffer, count: nameLength)))\n    }\n    \n    func toEventModifiers() -> SwiftUI.EventModifiers {\n        var modifiers: SwiftUI.EventModifiers = []\n        \n        if self.modifiers.contains(NSEvent.ModifierFlags.command) {\n            modifiers.update(with: EventModifiers.command)\n        }\n        \n        if self.modifiers.contains(NSEvent.ModifierFlags.control) {\n            modifiers.update(with: EventModifiers.control)\n        }\n        \n        if self.modifiers.contains(NSEvent.ModifierFlags.option) {\n            modifiers.update(with: EventModifiers.option)\n        }\n        \n        if self.modifiers.contains(NSEvent.ModifierFlags.shift) {\n            modifiers.update(with: EventModifiers.shift)\n        }\n        \n        if self.modifiers.contains(NSEvent.ModifierFlags.capsLock) {\n            modifiers.update(with: EventModifiers.capsLock)\n        }\n        \n        if self.modifiers.contains(NSEvent.ModifierFlags.numericPad) {\n            modifiers.update(with: EventModifiers.numericPad)\n        }\n        \n        return modifiers\n    }\n    \n}\n"
  },
  {
    "path": "boringNotch/extensions/MouseTracker.swift",
    "content": "//\n//  MouseTracker.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 12/08/2024.\n//\n\nimport SwiftUI\n\nextension NSScreen {\n    static var screenWithMouse: NSScreen? {\n        let mouseLocation = NSEvent.mouseLocation\n        let screens = NSScreen.screens\n        let screenWithMouse = (screens.first { NSMouseInRect(mouseLocation, $0.frame, false) })\n        \n        return screenWithMouse\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/NSImage+Extensions.swift",
    "content": "//\n//  Image2Color.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 07/08/2024.\n//\n\nimport SwiftUI\nimport AppKit\nimport Cocoa\nimport Foundation\nimport CoreImage\nimport CoreGraphics\nimport CoreImage.CIFilterBuiltins\n\nextension NSImage {\n\n    \n    func averageColor(completion: @escaping (NSColor?) -> Void) {\n        DispatchQueue.global(qos: .userInitiated).async {\n            guard let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil) else {\n                DispatchQueue.main.async {\n                    completion(nil)\n                }\n                return\n            }\n            \n            let width = cgImage.width\n            let height = cgImage.height\n            let totalPixels = width * height\n            \n            guard let context = CGContext(data: nil,\n                                          width: width,\n                                          height: height,\n                                          bitsPerComponent: 8,\n                                          bytesPerRow: width * 4,\n                                          space: CGColorSpaceCreateDeviceRGB(),\n                                          bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else {\n                DispatchQueue.main.async {\n                    completion(nil)\n                }\n                return\n            }\n            \n            context.draw(cgImage, in: CGRect(x: 0, y: 0, width: width, height: height))\n            \n            guard let data = context.data else {\n                DispatchQueue.main.async {\n                    completion(nil)\n                }\n                return\n            }\n            \n            let pointer = data.bindMemory(to: UInt32.self, capacity: totalPixels)\n            \n            var totalRed: UInt64 = 0\n            var totalGreen: UInt64 = 0\n            var totalBlue: UInt64 = 0\n            \n            for i in 0..<totalPixels {\n                let color = pointer[i]\n                totalRed += UInt64(color & 0xFF)\n                totalGreen += UInt64((color >> 8) & 0xFF)\n                totalBlue += UInt64((color >> 16) & 0xFF)\n            }\n            \n            let averageRed = CGFloat(totalRed) / CGFloat(totalPixels) / 255.0\n            let averageGreen = CGFloat(totalGreen) / CGFloat(totalPixels) / 255.0\n            let averageBlue = CGFloat(totalBlue) / CGFloat(totalPixels) / 255.0\n            \n            let minBrightness: CGFloat = 0.5\n            let isNearBlack = averageRed < 0.03 && averageGreen < 0.03 && averageBlue < 0.03\n            \n            var finalColor: NSColor\n            \n            if isNearBlack {\n                // If it's near black, just return a gray color with the minimum brightness\n                finalColor = NSColor(white: minBrightness, alpha: 1.0)\n            } else {\n                var color = NSColor(red: averageRed, green: averageGreen, blue: averageBlue, alpha: 1.0)\n                \n                var hue: CGFloat = 0\n                var saturation: CGFloat = 0\n                var brightness: CGFloat = 0\n                var alpha: CGFloat = 0\n                \n                color.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)\n                \n                if brightness < minBrightness {\n                    // Increase brightness while maintaining hue and reducing saturation\n                    let saturationScale = brightness / minBrightness\n                    color = NSColor(hue: hue,\n                                    saturation: saturation * saturationScale,\n                                    brightness: minBrightness,\n                                    alpha: alpha)\n                }\n                \n                finalColor = color\n            }\n            \n            DispatchQueue.main.async {\n                completion(finalColor)\n            }\n        }\n        \n    }\n    \n    func getBrightness() -> CGFloat {\n        guard let cgImage = self.cgImage(forProposedRect: nil, context: nil, hints: nil) else {\n            return 0\n        }\n        \n        let inputImage = CIImage(cgImage: cgImage)\n        \n        let filter = CIFilter.areaAverage()\n        filter.inputImage = inputImage\n        filter.extent = inputImage.extent\n        \n        guard let outputImage = filter.outputImage else {\n            return 0\n        }\n        \n        let context = CIContext(options: nil)\n        \n        var bitmap = [UInt8](repeating: 0, count: 4)\n        context.render(outputImage,\n                       toBitmap: &bitmap,\n                       rowBytes: 4,\n                       bounds: CGRect(x: 0, y: 0, width: 1, height: 1),\n                       format: .RGBA8,\n                       colorSpace: CGColorSpaceCreateDeviceRGB())\n        \n        let brightness = (0.2126 * CGFloat(bitmap[0]) + 0.7152 * CGFloat(bitmap[1]) + 0.0722 * CGFloat(bitmap[2])) / 255.0\n        \n        return brightness\n    }\n}\n\nextension Color {\n    func ensureMinimumBrightness(factor: CGFloat) -> Color {\n        guard factor >= 0 && factor <= 1 else {\n            return self // Return original color if factor is out of bounds\n        }\n        \n        let nsColor = NSColor(self)\n        \n        // Convert to RGB color space\n        guard let rgbColor = nsColor.usingColorSpace(.sRGB) else {\n            return self // Return original color if conversion fails\n        }\n        \n        var red: CGFloat = 0\n        var green: CGFloat = 0\n        var blue: CGFloat = 0\n        var alpha: CGFloat = 0\n        \n        rgbColor.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n        \n        // Calculate perceived brightness using the formula: (0.299*R + 0.587*G + 0.114*B)\n        let perceivedBrightness = (0.2126 * red + 0.7152 * green + 0.0722 * blue)\n        \n        let scale = factor / perceivedBrightness\n        red = min(red * scale, 1.0)\n        green = min(green * scale, 1.0)\n        blue = min(blue * scale, 1.0)\n        \n        return Color(red: Double(red), green: Double(green), blue: Double(blue), opacity: Double(alpha))\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/NSItemProvider+LoadHelpers.swift",
    "content": "//\n//  NSItemProvider+LoadHelpers.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-24.\n//\n\n\nimport AppKit\nimport Foundation\nimport UniformTypeIdentifiers\n\nextension NSItemProvider {\n    \n    func extractItem() async -> URL? {\n        return await loadFileURL(typeIdentifier: UTType.item.identifier)\n    }\n\n    \n    /// Detects if this is a file dragged from the filesystem\n    func extractFileURL() async -> URL? {\n        if hasItemConformingToTypeIdentifier(UTType.fileURL.identifier) {\n            return await loadFileURL(typeIdentifier: UTType.fileURL.identifier)\n        }\n        return nil\n    }\n    \n    /// Loads raw data for the given type identifier\n    func loadData() async -> Data? {\n        NSLog(String(describing: self.registeredTypeIdentifiers))\n        guard hasItemConformingToTypeIdentifier(UTType.data.identifier) else { return nil }\n        return await withCheckedContinuation { (cont: CheckedContinuation<Data?, Never>) in\n            loadItem(forTypeIdentifier: UTType.data.identifier, options: nil) { item, error in\n                if let error = error {\n                    print(\"Error loading data for type \\(UTType.data.identifier): \\(error.localizedDescription)\")\n                    cont.resume(returning: nil)\n                    return\n                }\n                if let url = item as? URL, let data = try? Data(contentsOf: url) {\n                    if !url.absoluteString.contains(\"com.apple.SwiftUI.filePromises\") {\n                        cont.resume(returning: nil)\n                        return\n                    }\n                    self.suggestedName = self.suggestedName ?? url.lastPathComponent\n                    \n                    let fileManager = FileManager.default\n                    let folderURL = url.deletingLastPathComponent()\n\n                    do {\n                        // Delete the file first\n                        try fileManager.removeItem(at: url)\n                        print(\"Deleted file: \\(url.path)\")\n\n                        // Check folder contents\n                        let contents = try fileManager.contentsOfDirectory(atPath: folderURL.path)\n                        if contents.isEmpty {\n                            try fileManager.removeItem(at: folderURL)\n                            print(\"Folder was empty, deleted folder: \\(folderURL.path)\")\n                        } else {\n                            print(\"Folder not deleted — it still contains \\(contents.count) item(s).\")\n                        }\n\n                    } catch {\n                        print(\"Error: \\(error.localizedDescription)\")\n                    }\n                    \n                    cont.resume(returning: data)\n                } else if let data = item as? Data {\n                    cont.resume(returning: data)\n                } else {\n                    cont.resume(returning: nil)\n                }\n            }\n        }\n    }\n\n    /// Attempts to extract a URL (web link) from the provider\n    func extractURL() async -> URL? {\n        if self.hasItemConformingToTypeIdentifier(UTType.url.identifier) {\n            if let url = await loadURL(typeIdentifier: UTType.url.identifier) {\n                //Validate URL\n                guard url.scheme != nil else { return nil }\n                return url\n            }\n        }\n\n        return nil\n    }\n\n    func extractText() async -> String? {\n        let textTypes = [UTType.utf8PlainText.identifier, UTType.plainText.identifier]\n\n        for typeIdentifier in textTypes where self.hasItemConformingToTypeIdentifier(typeIdentifier) {\n            if let text = await loadText(typeIdentifier: typeIdentifier) {\n                return text\n            }\n        }\n\n        return nil\n    }\n\n    /// Loads a file URL from the provider for the given type identifier.\n    func loadFileURL(typeIdentifier: String) async -> URL? {\n        await withCheckedContinuation { (cont: CheckedContinuation<URL?, Never>) in\n            self.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in\n                if let error = error {\n                    print(\"❌ Error loading item for type \\(typeIdentifier): \\(error.localizedDescription)\")\n                    cont.resume(returning: nil)\n                    return\n                }\n                var resolvedURL: URL?\n                if let url = item as? URL {\n                    // Direct URL provided\n                    resolvedURL = url\n                } else if let data = item as? Data {\n                    // Some providers hand out a UTF-8 file URL string, others a bookmark. Prefer parsing string first.\n                    if let string = String(data: data, encoding: .utf8) {\n                        if let url = URL(string: string) {\n                            resolvedURL = url\n                        } else if string.hasPrefix(\"/\") {\n                            // Plain file system path\n                            resolvedURL = URL(fileURLWithPath: string)\n                        }\n                    }\n                    if resolvedURL == nil {\n                        // Fallback: try treating the data as a bookmark\n                        let bookmark = Bookmark(data: data)\n                        resolvedURL = bookmark.resolveURL()\n                    }\n                } else if let string = item as? String {\n                    if let url = URL(string: string) {\n                        resolvedURL = url\n                    } else if string.hasPrefix(\"/\") {\n                        resolvedURL = URL(fileURLWithPath: string)\n                    }\n                }\n                cont.resume(returning: resolvedURL)\n            }\n        }\n    }\n\n    /// Loads a URL from the provider for the given type identifier.\n    func loadURL(typeIdentifier: String) async -> URL? {\n        await withCheckedContinuation { (cont: CheckedContinuation<URL?, Never>) in\n            self.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in\n                if error != nil {\n                    cont.resume(returning: nil)\n                    return\n                }\n\n                if let url = item as? URL {\n                    cont.resume(returning: url)\n                } else if let data = item as? Data {\n                    if let string = String(data: data, encoding: .utf8) {\n                        if let url = URL(string: string) {\n                            cont.resume(returning: url)\n                            return\n                        } else if string.hasPrefix(\"/\") {\n                            cont.resume(returning: URL(fileURLWithPath: string))\n                            return\n                        }\n                    }\n                    cont.resume(returning: nil)\n                } else if let string = item as? String {\n                    if let url = URL(string: string) {\n                        cont.resume(returning: url)\n                    } else if string.hasPrefix(\"/\") {\n                        cont.resume(returning: URL(fileURLWithPath: string))\n                    } else {\n                        cont.resume(returning: nil)\n                    }\n                } else {\n                    cont.resume(returning: nil)\n                }\n            }\n        }\n    }\n\n    /// Loads text from the provider for the given type identifier.\n    func loadText(typeIdentifier: String) async -> String? {\n        await withCheckedContinuation { (cont: CheckedContinuation<String?, Never>) in\n            self.loadItem(forTypeIdentifier: typeIdentifier, options: nil) { item, error in\n                if error != nil {\n                    cont.resume(returning: nil)\n                    return\n                }\n\n                if let string = item as? String {\n                    cont.resume(returning: string)\n                } else if let data = item as? Data,\n                          let string = String(data: data, encoding: .utf8) {\n                    cont.resume(returning: string)\n                } else {\n                    cont.resume(returning: nil)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/NSMenu+AssociatedObject.swift",
    "content": "//\n//  NSMenu+AssociatedObject.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-05.\n//\n\nimport AppKit\n\nprivate final class MenuActionBox: NSObject {\n    let target: AnyObject\n    init(target: AnyObject) { self.target = target }\n}\n\nextension NSMenu {\n    // Each NSMenu instance can store one retained target\n    private static let retainedAction = AssociatedObject<MenuActionBox>()\n\n    func retainActionTarget(_ target: AnyObject) {\n        NSMenu.retainedAction[self] = MenuActionBox(target: target)\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/NSScreen+UUID.swift",
    "content": "//\n//  NSScreen+UUID.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-11-21.\n//\n\nimport AppKit\nimport CoreGraphics\n\nextension NSScreen {\n    /// Returns a persistent UUID for this display\n    var displayUUID: String? {\n        guard let number = deviceDescription[NSDeviceDescriptionKey(\"NSScreenNumber\")] as? NSNumber else {\n            return nil\n        }\n        let displayID = CGDirectDisplayID(number.uint32Value)\n        guard let uuid = CGDisplayCreateUUIDFromDisplayID(displayID) else {\n            return nil\n        }\n        let uuidString = CFUUIDCreateString(nil, uuid.takeRetainedValue()) as String\n        return uuidString\n    }\n    \n    /// Find a screen by its UUID\n    @MainActor static func screen(withUUID uuid: String) -> NSScreen? {\n        return NSScreenUUIDCache.shared.screen(forUUID: uuid)\n    }\n    \n    /// Get UUID to NSScreen mapping for all screens\n    @MainActor static var screensByUUID: [String: NSScreen] {\n        return NSScreenUUIDCache.shared.allScreens\n    }\n}\n\n/// Cache for UUID to NSScreen mappings to avoid repeated lookups\n@MainActor\nfinal class NSScreenUUIDCache {\n    static let shared = NSScreenUUIDCache()\n    \n    private var cache: [String: NSScreen] = [:]\n    private var observer: Any?\n    \n    private init() {\n        rebuildCache()\n        setupObserver()\n    }\n    \n    deinit {\n        if let observer = observer {\n            NotificationCenter.default.removeObserver(observer)\n        }\n    }\n    \n    private func setupObserver() {\n        observer = NotificationCenter.default.addObserver(\n            forName: NSApplication.didChangeScreenParametersNotification,\n            object: nil,\n            queue: .main\n        ) { [weak self] _ in\n            self?.rebuildCache()\n        }\n    }\n    \n    private func rebuildCache() {\n        var newCache: [String: NSScreen] = [:]\n        \n        for screen in NSScreen.screens {\n            if let uuid = screen.displayUUID {\n                newCache[uuid] = screen\n            }\n        }\n        \n        cache = newCache\n    }\n    \n    func screen(forUUID uuid: String) -> NSScreen? {\n        return cache[uuid]\n    }\n    \n    var allScreens: [String: NSScreen] {\n        return cache\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/PanGesture.swift",
    "content": "//\n//  PanGesture.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 21/08/2024.\n//\n\nimport AppKit\nimport SwiftUI\n\nenum PanDirection {\n    case left, right, up, down\n\n    var isHorizontal: Bool { self == .left || self == .right }\n    var sign: CGFloat { (self == .right || self == .down) ? 1 : -1 }\n\n    func signed(from translation: CGSize) -> CGFloat { (isHorizontal ? translation.width : translation.height) * sign }\n    func signed(deltaX: CGFloat, deltaY: CGFloat) -> CGFloat { (isHorizontal ? deltaX : deltaY) * sign }\n}\n\nextension View {\n    func panGesture(direction: PanDirection, threshold: CGFloat = 4, action: @escaping (CGFloat, NSEvent.Phase) -> Void) -> some View {\n        self\n            .gesture(\n                DragGesture(minimumDistance: 0)\n                    .onChanged { value in\n                        let s = direction.signed(from: value.translation)\n                        guard s > 0, s.magnitude >= threshold else { return }\n                        action(s.magnitude, .changed)\n                    }\n                    .onEnded { _ in action(0, .ended) }\n            )\n            .background(ScrollMonitor(direction: direction, threshold: threshold, action: action))\n    }\n}\n\nprivate struct ScrollMonitor: NSViewRepresentable {\n    let direction: PanDirection\n    let threshold: CGFloat\n    let action: (CGFloat, NSEvent.Phase) -> Void\n\n    func makeNSView(context: Context) -> NSView {\n        let view = NSView()\n        context.coordinator.installMonitor(on: view)\n        return view\n    }\n    func updateNSView(_ nsView: NSView, context: Context) {}\n    static func dismantleNSView(_ nsView: NSView, coordinator: Coordinator) { coordinator.removeMonitor() }\n\n    func makeCoordinator() -> Coordinator { \n        Coordinator(direction: direction, threshold: threshold, action: action) \n    }\n\n    @MainActor final class Coordinator: NSObject {\n        private let direction: PanDirection\n        private let threshold: CGFloat\n        private let action: (CGFloat, NSEvent.Phase) -> Void\n        private var monitor: Any?\n        private var accumulated: CGFloat = 0\n        private var active = false\n            private var endTask: Task<Void, Never>?\n        private let noiseThreshold: CGFloat = 0.2\n\n        init(direction: PanDirection, threshold: CGFloat, action: @escaping (CGFloat, NSEvent.Phase) -> Void) {\n            self.direction = direction\n            self.threshold = threshold\n            self.action = action\n        }\n\n        private func scheduleEndTimeout() {\n            // Cancel any existing scheduled end and schedule a new one.\n            endTask?.cancel()\n            endTask = Task { @MainActor in\n                // If no new scroll event arrives within this window, consider the gesture ended.\n                try? await Task.sleep(for: .milliseconds(300))\n                guard !Task.isCancelled else { return }\n                if active {\n                    action(accumulated.magnitude, .ended)\n                } else {\n                    action(0, .ended)\n                }\n                active = false\n                accumulated = 0\n            }\n        }\n\n        func installMonitor(on view: NSView) {\n            removeMonitor()\n            monitor = NSEvent.addLocalMonitorForEvents(matching: [.scrollWheel]) { [weak self, weak view] event in\n                guard let self = self, event.window === view?.window else { return event }\n                self.handleScroll(event)\n                return event\n            }\n        }\n\n        func removeMonitor() {\n            if let monitor = monitor {\n                NSEvent.removeMonitor(monitor)\n                self.monitor = nil\n            }\n            accumulated = 0\n            active = false\n            endTask?.cancel()\n            endTask = nil\n        }\n\n        private func handleScroll(_ event: NSEvent) {\n            if event.phase == .ended || event.momentumPhase == .ended {\n                if active {\n                    action(accumulated.magnitude, .ended)\n                } else {\n                    action(0, .ended)\n                }\n                active = false\n                accumulated = 0\n                return\n            }\n\n            // Only consider scroll events that are primarily along the configured axis.\n            let absDX = abs(event.scrollingDeltaX)\n            let absDY = abs(event.scrollingDeltaY)\n            // Require the movement along the gesture axis to be at least 1.5x the orthogonal axis.\n            let axisDominanceFactor: CGFloat = 1.5\n            let isAxisDominant: Bool = direction.isHorizontal ? (absDX >= axisDominanceFactor * absDY) : (absDY >= axisDominanceFactor * absDX)\n            guard isAxisDominant else { return }\n\n            // Scale non-precise (mouse wheel) scrolling deltas so they feel similar to\n            // trackpad gestures.\n            let raw = direction.signed(deltaX: event.scrollingDeltaX, deltaY: event.scrollingDeltaY)\n            let scale: CGFloat = event.hasPreciseScrollingDeltas ? 1 : 8\n            let s = raw * scale\n            guard s.magnitude > noiseThreshold else { return }\n            accumulated = s > 0 ? accumulated + s : 0\n\n            if !active && accumulated >= threshold {\n                active = true\n                action(accumulated.magnitude, .began)\n            } else if active {\n                action(accumulated.magnitude, .changed)\n            }\n            // Schedule a timeout to end the gesture if no further scroll events arrive.\n            scheduleEndTimeout()\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/extensions/URL+SecurityScoped.swift",
    "content": "//\n//  URL+SecurityScoped.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-07.\n//\n\nimport Foundation\nimport AppKit\n\n// MARK: - Error Types\n\nextension URL {\n    func accessSecurityScopedResource<Value>(accessor: (URL) throws -> Value) rethrows -> Value {\n        let didStartAccessing = startAccessingSecurityScopedResource()\n        defer { \n            if didStartAccessing { \n                stopAccessingSecurityScopedResource() \n            }\n        }\n        return try accessor(self)\n    }\n    \n    /// Async version of accessSecurityScopedResource\n    func accessSecurityScopedResource<Value>(accessor: (URL) async throws -> Value) async rethrows -> Value {\n        let didStartAccessing = startAccessingSecurityScopedResource()\n        defer { \n            if didStartAccessing { \n                stopAccessingSecurityScopedResource() \n            }\n        }\n        return try await accessor(self)\n    }\n}\n\nextension [URL] {\n    func accessSecurityScopedResources<Value>(accessor: ([URL]) async throws -> Value) async rethrows -> Value {\n        let didStart = self.map { $0.startAccessingSecurityScopedResource() }\n        \n        defer {\n            for (url, started) in zip(self, didStart) where started {\n                url.stopAccessingSecurityScopedResource()\n            }\n        }\n        \n        return try await accessor(self)\n    }\n}\n\n"
  },
  {
    "path": "boringNotch/helpers/AppIcons.swift",
    "content": "//\n//  AppIcons.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 16/08/24.\n//\n\nimport SwiftUI\nimport AppKit\n\nstruct AppIcons {\n    \n    func getIcon(file path: String) -> NSImage? {\n        guard FileManager.default.fileExists(atPath: path)\n        else { return nil }\n        \n        return NSWorkspace.shared.icon(forFile: path)\n    }\n    \n    func getIcon(bundleID: String) -> NSImage? {\n        guard let path = NSWorkspace.shared.urlForApplication(\n            withBundleIdentifier: bundleID\n        )?.absoluteString\n        else { return nil }\n        \n        return getIcon(file: path)\n    }\n    \n        /// Easily read Info.plist as a Dictionary from any bundle by accessing .infoDictionary on Bundle\n    func bundle(forBundleID: String) -> Bundle? {\n        guard let url = NSWorkspace.shared.urlForApplication(withBundleIdentifier: forBundleID)\n        else { return nil }\n        \n        return Bundle(url: url)\n    }\n    \n}\n\nfunc AppIcon(for bundleID: String) -> Image {\n    let workspace = NSWorkspace.shared\n    \n    if let appURL = workspace.urlForApplication(withBundleIdentifier: bundleID) {\n        let appIcon = workspace.icon(forFile: appURL.path)\n        return Image(nsImage: appIcon)\n    }\n    \n    return Image(nsImage: workspace.icon(for: .applicationBundle))\n}\n\n\nfunc AppIconAsNSImage(for bundleID: String) -> NSImage? {\n    let workspace = NSWorkspace.shared\n    \n    if let appURL = workspace.urlForApplication(withBundleIdentifier: bundleID) {\n        let appIcon = workspace.icon(forFile: appURL.path)\n        appIcon.size = NSSize(width: 256, height: 256)\n        return appIcon\n    }\n    return nil\n}\n\n"
  },
  {
    "path": "boringNotch/helpers/AppleScriptHelper.swift",
    "content": "//\n//  AppleScriptHelper.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-03-29.\n//\n\nimport Foundation\n\nclass AppleScriptHelper {\n    @discardableResult\n    class func execute(_ scriptText: String) async throws -> NSAppleEventDescriptor? {\n        try await withCheckedThrowingContinuation { continuation in\n            Task.detached(priority: .userInitiated) {\n                let script = NSAppleScript(source: scriptText)\n                var error: NSDictionary?\n                if let descriptor = script?.executeAndReturnError(&error) {\n                    continuation.resume(returning: descriptor)\n                } else if let error = error {\n                    continuation.resume(throwing: NSError(domain: \"AppleScriptError\", code: 1, userInfo: error as? [String: Any]))\n                } else {\n                    continuation.resume(throwing: NSError(domain: \"AppleScriptError\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"Unknown error\"]))\n                }\n            }\n        }\n    }\n    \n    class func executeVoid(_ scriptText: String) async throws {\n        _ = try await execute(scriptText)\n    }\n}\n"
  },
  {
    "path": "boringNotch/helpers/ApplicationRelauncher.swift",
    "content": "//\n//  ApplicationRelauncher.swift\n//  boringNotch\n//\n//  Created by Corentin132 on 03/10/2025.\n//\n\nimport AppKit\n\nenum ApplicationRelauncher {\n    static func restart() {\n        guard let bundleIdentifier = Bundle.main.bundleIdentifier else { return }\n\n        let workspace = NSWorkspace.shared\n\n        guard let appURL = workspace.urlForApplication(withBundleIdentifier: bundleIdentifier)\n        else { return }\n\n        let configuration = NSWorkspace.OpenConfiguration()\n        configuration.createsNewApplicationInstance = true\n\n        workspace.openApplication(at: appURL, configuration: configuration, completionHandler: nil)\n\n        NSApplication.shared.terminate(nil)\n    }\n}\n"
  },
  {
    "path": "boringNotch/helpers/AssociatedObject.swift",
    "content": "//\n//  AssociatedObject.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-05.\n//\n\nimport Foundation\nimport ObjectiveC\n\n\n/// Lightweight helper for Objective-C associated objects.\npublic struct AssociatedObject<Value: AnyObject> {\n    private let key: UnsafeRawPointer\n    private let policy: objc_AssociationPolicy\n\n    public init(_ policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {\n        self.key = UnsafeRawPointer(Unmanaged.passUnretained(UniqueKey()).toOpaque())\n        self.policy = policy\n    }\n\n    private final class UniqueKey {}\n\n    public subscript<Owner: AnyObject>(_ owner: Owner) -> Value? {\n        get { objc_getAssociatedObject(owner, key) as? Value }\n        nonmutating set { objc_setAssociatedObject(owner, key, newValue, policy) }\n    }\n}\n\nextension AssociatedObject: @unchecked Sendable where Value: Sendable {}\n"
  },
  {
    "path": "boringNotch/helpers/AudioPlayer.swift",
    "content": "//\n//  AudioPlayer.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 09/08/24.\n//\n\nimport Foundation\nimport AppKit\n\nclass AudioPlayer {\n    func play(fileName: String, fileExtension: String) {\n        NSSound(contentsOf:Bundle.main.url(forResource: fileName, withExtension: fileExtension)!, byReference: false)?.play()\n    }\n}\n"
  },
  {
    "path": "boringNotch/helpers/Clipboard+Content.swift",
    "content": "import AppKit\n\nfunc getAttributedString(content: Any, type: NSPasteboard.PasteboardType) -> NSAttributedString? {\n    if let stringContent = content as? String {\n        return NSAttributedString(string: stringContent)\n    }\n    if type == .rtf, let data = content as? Data {\n        return NSAttributedString(rtf: data, documentAttributes: nil)\n    } else if type == .html, let data = content as? Data {\n        return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)\n    } else if type.rawValue == \"public.utf8-plain-text\", let data = content as? Data {\n        return try? NSAttributedString(data: data, documentAttributes: nil)\n    } else if type == .string {\n        return NSAttributedString(string: content as? String ?? \"\")\n    } else if type == .fileURL {\n        return NSAttributedString(string: content as? String ?? \"\")\n    } else if type == NSPasteboard.PasteboardType(\"com.apple.webarchive\"), let data = content as? Data {\n        return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.webArchive], documentAttributes: nil)\n    }\n    return nil\n}\n\nfunc isText(type: NSPasteboard.PasteboardType) -> Bool {\n    return type == .string || type == .html || type == .rtf || type == .html || type == .string || type.rawValue == \"public.utf8-plain-text\" || type.rawValue == \"public.utf16-external-plain-text\" || type.rawValue == \"com.apple.webarchive\"\n}\n"
  },
  {
    "path": "boringNotch/helpers/MediaChecker.swift",
    "content": "//\n//  MediaChecker.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-07-26.\n//\n\nimport Foundation\n\nfinal class MediaChecker: Sendable {\n\n    enum MediaCheckerError: Error {\n        case missingResources\n        case processExecutionFailed\n        case timeout\n    }\n\n    func checkDeprecationStatus() async throws -> Bool {\n        try await Task.detached(priority: .userInitiated) {\n            guard let scriptURL = Bundle.main.url(forResource: \"mediaremote-adapter\", withExtension: \"pl\"),\n                  let nowPlayingTestClientPath = Bundle.main.url(forResource: \"MediaRemoteAdapterTestClient\", withExtension: nil)?.path,\n                  let frameworkPath = Bundle.main.privateFrameworksPath?.appending(\"/MediaRemoteAdapter.framework\")\n            else {\n                throw MediaCheckerError.missingResources\n            }\n\n            let process = Process()\n            process.executableURL = URL(fileURLWithPath: \"/usr/bin/perl\")\n            process.arguments = [scriptURL.path, frameworkPath, nowPlayingTestClientPath, \"test\"]\n\n            do {\n                try process.run()\n            } catch {\n                throw MediaCheckerError.processExecutionFailed\n            }\n\n            // Timeout after 10 seconds\n            let didExit: Bool = try await withThrowingTaskGroup(of: Bool.self) { group in\n                group.addTask {\n                    process.waitUntilExit()\n                    return true\n                }\n                group.addTask {\n                    try await Task.sleep(for: .seconds(10))\n                    if process.isRunning {\n                        process.terminate()\n                    }\n                    return false // Timed out\n                }\n                for try await exited in group {\n                    if exited {\n                        group.cancelAll()\n                        return true\n                    }\n                }\n                throw MediaCheckerError.timeout\n            }\n\n            if !didExit {\n                throw MediaCheckerError.timeout\n            }\n\n            let isDeprecated = process.terminationStatus == 1\n            return isDeprecated\n        }.value\n    }\n}\n"
  },
  {
    "path": "boringNotch/managers/BatteryActivityManager.swift",
    "content": "import Foundation\nimport IOKit.ps\n\n/// Manages and monitors battery status changes on the device\n/// - Note: This class uses the IOKit framework to monitor battery status\nclass BatteryActivityManager {\n\n    static let shared = BatteryActivityManager()\n\n    var onBatteryLevelChange: ((Float) -> Void)?\n    var onMaxCapacityChange: ((Float) -> Void)?\n    var onPowerModeChange: ((Bool) -> Void)?\n    var onPowerSourceChange: ((Bool) -> Void)?\n    var onChargingChange: ((Bool) -> Void)?\n    var onTimeToFullChargeChange: ((Int) -> Void)?\n\n    private var batterySource: CFRunLoopSource?\n    private var observers: [(BatteryEvent) -> Void] = []\n    private var previousBatteryInfo: BatteryInfo?\n    private var notificationQueue: [BatteryEvent] = []\n    private var isProcessingNotifications = false\n\n    enum BatteryEvent {\n        case powerSourceChanged(isPluggedIn: Bool)\n        case batteryLevelChanged(level: Float)\n        case lowPowerModeChanged(isEnabled: Bool)\n        case isChargingChanged(isCharging: Bool)\n        case timeToFullChargeChanged(time: Int)\n        case maxCapacityChanged(capacity: Float)\n        case error(description: String)\n    }\n\n    enum BatteryError: Error {\n        case powerSourceUnavailable\n        case batteryInfoUnavailable(String)\n        case batteryParameterMissing(String)\n    }\n\n    private let defaultBatteryInfo = BatteryInfo(\n        isPluggedIn: false,\n        isCharging: false,\n        currentCapacity: 0,\n        maxCapacity: 0,\n        isInLowPowerMode: false,\n        timeToFullCharge: 0\n    )\n\n    private init() {\n        startMonitoring()\n        setupLowPowerModeObserver()\n    }\n    \n    /// Setup observer for low power mode changes\n    private func setupLowPowerModeObserver() {\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(lowPowerModeChanged),\n            name: NSNotification.Name.NSProcessInfoPowerStateDidChange,\n            object: nil\n        )\n    }\n\n    /// Called when low power mode is enabled or disabled\n    @objc private func lowPowerModeChanged() {\n        notifyBatteryChanges()\n    }\n    \n    /// Starts monitoring battery changes\n    private func startMonitoring() {\n        guard let powerSource = IOPSNotificationCreateRunLoopSource({ context in\n            guard let context = context else { return }\n            let manager = Unmanaged<BatteryActivityManager>.fromOpaque(context).takeUnretainedValue()\n            manager.notifyBatteryChanges()\n        }, Unmanaged.passUnretained(self).toOpaque())?.takeRetainedValue() else {\n            return\n        }\n        batterySource = powerSource\n        CFRunLoopAddSource(CFRunLoopGetCurrent(), powerSource, .defaultMode)\n    }\n\n    /// Stops monitoring battery changes\n    private func stopMonitoring() {\n        if let powerSource = batterySource {\n            CFRunLoopRemoveSource(CFRunLoopGetCurrent(), powerSource, .defaultMode)\n            batterySource = nil\n        }\n    }\n\n    /// Checks for changes in a property and notifies observers\n    private func checkAndNotify<T: Equatable>(\n        previous: T, \n        current: T, \n        eventGenerator: (T) -> BatteryEvent\n    ) {\n        if previous != current {\n            enqueueNotification(eventGenerator(current))\n        }\n    }\n    \n    /// Notifies the observers of battery changes\n    /// Checks for changes in battery status and notifies observers\n    private func notifyBatteryChanges() {\n        let batteryInfo = getBatteryInfo()\n        \n        // Check for changes\n        if let previousInfo = previousBatteryInfo {\n            // Usar la función auxiliar para cada propiedad\n            checkAndNotify(\n                previous: previousInfo.isPluggedIn,\n                current: batteryInfo.isPluggedIn,\n                eventGenerator: { .powerSourceChanged(isPluggedIn: $0) }\n            )\n            \n            checkAndNotify(\n                previous: previousInfo.currentCapacity,\n                current: batteryInfo.currentCapacity,\n                eventGenerator: { .batteryLevelChanged(level: $0) }\n            )\n            \n            checkAndNotify(\n                previous: previousInfo.isCharging,\n                current: batteryInfo.isCharging,\n                eventGenerator: { .isChargingChanged(isCharging: $0) }\n            )\n            \n            checkAndNotify(\n                previous: previousInfo.isInLowPowerMode,\n                current: batteryInfo.isInLowPowerMode,\n                eventGenerator: { .lowPowerModeChanged(isEnabled: $0) }\n            )\n            \n            checkAndNotify(\n                previous: previousInfo.timeToFullCharge,\n                current: batteryInfo.timeToFullCharge,\n                eventGenerator: { .timeToFullChargeChanged(time: $0) }\n            )\n            \n            checkAndNotify(\n                previous: previousInfo.maxCapacity,\n                current: batteryInfo.maxCapacity,\n                eventGenerator: { .maxCapacityChanged(capacity: $0) }\n            )\n        } else {\n            // First time notification\n            enqueueNotification(.powerSourceChanged(isPluggedIn: batteryInfo.isPluggedIn))\n            enqueueNotification(.batteryLevelChanged(level: batteryInfo.currentCapacity))\n            enqueueNotification(.isChargingChanged(isCharging: batteryInfo.isCharging))\n            enqueueNotification(.lowPowerModeChanged(isEnabled: batteryInfo.isInLowPowerMode))\n            enqueueNotification(.timeToFullChargeChanged(time: batteryInfo.timeToFullCharge))\n            enqueueNotification(.maxCapacityChanged(capacity: batteryInfo.maxCapacity))\n        }\n\n        // Update previous battery info\n        previousBatteryInfo = batteryInfo\n\n        // Trigger optional callbacks\n        DispatchQueue.main.async { [weak self] in\n            guard let self = self else { return }\n            self.onBatteryLevelChange?(batteryInfo.currentCapacity)\n            self.onPowerSourceChange?(batteryInfo.isPluggedIn)\n            self.onChargingChange?(batteryInfo.isCharging)\n            self.onPowerModeChange?(batteryInfo.isInLowPowerMode)\n            self.onTimeToFullChargeChange?(batteryInfo.timeToFullCharge)\n            self.onMaxCapacityChange?(batteryInfo.maxCapacity)\n        }\n    }\n\n    /// Enqueues a notification to be processed\n    /// - Parameter event: The battery event\n    private func enqueueNotification(_ event: BatteryEvent) {\n        notificationQueue.append(event)\n        processNextNotification()\n    }\n    \n    /// Processes the next notification in the queue\n    /// If there are no more notifications, the queue is cleared\n    /// and the processing flag is set to false\n    private func processNextNotification() {\n        guard !isProcessingNotifications, !notificationQueue.isEmpty else { return }\n        isProcessingNotifications = true\n        \n        let event = notificationQueue.removeFirst()\n        DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { [weak self] in\n            guard let self = self else { return }\n            self.notifyObservers(event: event)\n            self.isProcessingNotifications = false\n            \n            // Check if there are more items in the queue\n            if !self.notificationQueue.isEmpty {\n                self.processNextNotification()\n            }\n        }\n    }\n    \n    /// Initializes the battery information when the manager starts\n    /// - Returns: Current battery information\n    func initializeBatteryInfo() -> BatteryInfo {\n        previousBatteryInfo = getBatteryInfo()\n        guard let batteryInfo = previousBatteryInfo else {\n            return BatteryInfo(\n                isPluggedIn: false,\n                isCharging: false,\n                currentCapacity: 0,\n                maxCapacity: 0,\n                isInLowPowerMode: false,\n                timeToFullCharge: 0\n            )\n        }\n        return batteryInfo\n    }\n\n    /// Get the current battery information\n    /// - Returns: The current battery information\n    private func getBatteryInfo() -> BatteryInfo {\n        do {\n            // Get power source information\n            guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue() else {\n                throw BatteryError.powerSourceUnavailable\n            }\n            \n            guard let sources = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue() as? [CFTypeRef],\n                !sources.isEmpty else {\n                throw BatteryError.batteryInfoUnavailable(\"No power sources available\")\n            }\n            \n            let source = sources.first!\n            \n            guard let description = IOPSGetPowerSourceDescription(snapshot, source)?.takeUnretainedValue() as? [String: Any] else {\n                throw BatteryError.batteryInfoUnavailable(\"Could not get power source description\")\n            }\n            \n            // Extract required battery parameters with error handling\n            guard let currentCapacity = description[kIOPSCurrentCapacityKey] as? Float else {\n                throw BatteryError.batteryParameterMissing(\"Current capacity\")\n            }\n            \n            guard let maxCapacity = description[kIOPSMaxCapacityKey] as? Float else {\n                throw BatteryError.batteryParameterMissing(\"Max capacity\")\n            }\n            \n            guard let isCharging = description[\"Is Charging\"] as? Bool else {\n                throw BatteryError.batteryParameterMissing(\"Charging state\")\n            }\n            \n            guard let powerSource = description[kIOPSPowerSourceStateKey] as? String else {\n                throw BatteryError.batteryParameterMissing(\"Power source state\")\n            }\n            \n            // Create battery info with the extracted parameters\n            var batteryInfo = BatteryInfo(\n                isPluggedIn: powerSource == kIOPSACPowerValue,\n                isCharging: isCharging,\n                currentCapacity: currentCapacity,\n                maxCapacity: maxCapacity,\n                isInLowPowerMode: ProcessInfo.processInfo.isLowPowerModeEnabled,\n                timeToFullCharge: 0\n            )\n            \n            // Optional parameters\n            if let timeToFullCharge = description[kIOPSTimeToFullChargeKey] as? Int {\n                batteryInfo.timeToFullCharge = timeToFullCharge\n            }\n            \n            return batteryInfo\n            \n        } catch BatteryError.powerSourceUnavailable {\n            print(\"⚠️ Error: Power source information unavailable\")\n            return defaultBatteryInfo\n        } catch BatteryError.batteryInfoUnavailable(let reason) {\n            print(\"⚠️ Error: Battery information unavailable - \\(reason)\")\n            return defaultBatteryInfo\n        } catch BatteryError.batteryParameterMissing(let parameter) {\n            print(\"⚠️ Error: Battery parameter missing - \\(parameter)\")\n            return defaultBatteryInfo\n        } catch {\n            print(\"⚠️ Error: Unexpected error getting battery info - \\(error.localizedDescription)\")\n            return defaultBatteryInfo\n        }\n    }\n    \n    /// Adds an observer to listen to battery changes\n    /// - Parameter observer: The observer closure to be called on battery events\n    /// - Returns: The ID of the observer for later removal\n    func addObserver(_ observer: @escaping (BatteryEvent) -> Void) -> Int {\n        observers.append(observer)\n        return observers.count - 1\n    }\n\n    /// Removes an observer by its ID\n    /// - Parameter id: The ID of the observer to be removed\n    func removeObserver(byId id: Int) {\n        guard id >= 0 && id < observers.count else { return }\n        observers.remove(at: id)\n    }\n    \n    /// Notifies all observers of a battery event\n    /// - Parameter event: The battery event to notify\n    private func notifyObservers(event: BatteryEvent) {\n        DispatchQueue.main.async { [weak self] in\n            guard let self = self else { return }\n            for observer in self.observers {\n                observer(event)\n            }\n        }\n    }\n    \n    deinit {\n        stopMonitoring()\n        NotificationCenter.default.removeObserver(self)\n    }\n    \n}\n\n/// Struct to hold battery information\nstruct BatteryInfo {\n    var isPluggedIn: Bool\n    var isCharging: Bool\n    var currentCapacity: Float\n    var maxCapacity: Float\n    var isInLowPowerMode: Bool\n    var timeToFullCharge: Int\n}\n"
  },
  {
    "path": "boringNotch/managers/BrightnessManager.swift",
    "content": "//  BrightnessManager.swift\n//  boringNotch\n//\n//  Created by JeanLouis on 08/22/24.\n\nimport AppKit\n\nfinal class BrightnessManager: ObservableObject {\n\tstatic let shared = BrightnessManager()\n\n\t@Published private(set) var rawBrightness: Float = 0\n\t@Published private(set) var animatedBrightness: Float = 0\n\t@Published private(set) var lastChangeAt: Date = .distantPast\n\n\tprivate let visibleDuration: TimeInterval = 1.2\n\tprivate let client = XPCHelperClient.shared\n\n\tprivate init() { refresh() }\n\n\tvar shouldShowOverlay: Bool { Date().timeIntervalSince(lastChangeAt) < visibleDuration }\n\n\tfunc refresh() {\n\t\tTask { @MainActor in\n\t\t\tif let current = await client.currentScreenBrightness() {\n\t\t\t\tpublish(brightness: current, touchDate: false)\n\t\t\t}\n\t\t}\n\t}\n\n\t@MainActor func setRelative(delta: Float) {\n\t\tTask { @MainActor in\n\t\t\tlet starting = await client.currentScreenBrightness() ?? rawBrightness\n\t\t\tlet target = max(0, min(1, starting + delta))\n\t\t\tlet ok = await client.setScreenBrightness(target)\n\t\t\tif ok {\n\t\t\t\tpublish(brightness: target, touchDate: true)\n\t\t\t} else {\n\t\t\t\trefresh()\n\t\t\t}\n\t\t\tBoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .brightness, value: CGFloat(target))\n\t\t}\n\t}\n\n\tfunc setAbsolute(value: Float) {\n\t\tlet clamped = max(0, min(1, value))\n\t\tTask { @MainActor in\n\t\t\tlet ok = await client.setScreenBrightness(clamped)\n\t\t\tif ok {\n\t\t\t\tpublish(brightness: clamped, touchDate: true)\n\t\t\t} else {\n\t\t\t\trefresh()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate func publish(brightness: Float, touchDate: Bool) {\n\t\tDispatchQueue.main.async {\n\t\t\tif self.rawBrightness != brightness || touchDate {\n\t\t\t\tif touchDate { self.lastChangeAt = Date() }\n\t\t\t\tself.rawBrightness = brightness\n\t\t\t\tself.animatedBrightness = brightness\n\t\t\t}\n\t\t}\n\t}\n}\n\n// (DisplayServices helpers moved into XPC helper)\n\n// MARK: - Keyboard Backlight Controller\nfinal class KeyboardBacklightManager: ObservableObject {\n\tstatic let shared = KeyboardBacklightManager()\n\n\t@Published private(set) var rawBrightness: Float = 0\n\t@Published private(set) var lastChangeAt: Date = .distantPast\n\n\tprivate let visibleDuration: TimeInterval = 1.2\n\tprivate let client = XPCHelperClient.shared\n\n\tprivate init() { refresh() }\n\n\tvar shouldShowOverlay: Bool { Date().timeIntervalSince(lastChangeAt) < visibleDuration }\n\n\tfunc refresh() {\n\t\tTask { @MainActor in\n\t\t\tif let current = await client.currentKeyboardBrightness() {\n\t\t\t\tpublish(brightness: current, touchDate: false)\n\t\t\t}\n\t\t}\n\t}\n\n\t@MainActor func setRelative(delta: Float) {\n\t\tTask { @MainActor in\n\t\t\tlet starting = await client.currentKeyboardBrightness() ?? rawBrightness\n\t\t\tlet target = max(0, min(1, starting + delta))\n\t\t\tlet ok = await client.setKeyboardBrightness(target)\n\t\t\tif ok {\n\t\t\t\tpublish(brightness: target, touchDate: true)\n\t\t\t} else {\n\t\t\t\trefresh()\n\t\t\t}\n\t\t\tBoringViewCoordinator.shared.toggleSneakPeek(\n\t\t\t\tstatus: true,\n\t\t\t\ttype: .backlight,\n\t\t\t\tvalue: CGFloat(target)\n\t\t\t)\n\t\t}\n\t}\n\n\tfunc setAbsolute(value: Float) {\n\t\tlet clamped = max(0, min(1, value))\n\t\tTask { @MainActor in\n\t\t\tlet ok = await client.setKeyboardBrightness(clamped)\n\t\t\tif ok {\n\t\t\t\tpublish(brightness: clamped, touchDate: true)\n\t\t\t} else {\n\t\t\t\trefresh()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate func publish(brightness: Float, touchDate: Bool) {\n\t\tDispatchQueue.main.async {\n\t\t\tif self.rawBrightness != brightness || touchDate {\n\t\t\t\tif touchDate { self.lastChangeAt = Date() }\n\t\t\t\tself.rawBrightness = brightness\n\t\t\t}\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "boringNotch/managers/CalendarManager.swift",
    "content": "//\n//  CalendarManager.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 08/09/24.\n//\n\nimport Defaults\nimport EventKit\nimport SwiftUI\n\n// MARK: - CalendarManager\n\n@MainActor\nclass CalendarManager: ObservableObject {\n    static let shared = CalendarManager()\n\n    @Published var currentWeekStartDate: Date\n    @Published var events: [EventModel] = []\n    @Published var allCalendars: [CalendarModel] = []\n    @Published var eventCalendars: [CalendarModel] = []\n    @Published var reminderLists: [CalendarModel] = []\n    @Published var selectedCalendarIDs: Set<String> = []\n    @Published var calendarAuthorizationStatus: EKAuthorizationStatus = .notDetermined\n    @Published var reminderAuthorizationStatus: EKAuthorizationStatus = .notDetermined\n    private var selectedCalendars: [CalendarModel] = []\n    private let calendarService = CalendarService()\n\n    private var eventStoreChangedObserver: NSObjectProtocol?\n\n    private init() {\n        self.currentWeekStartDate = CalendarManager.startOfDay(Date())\n        setupEventStoreChangedObserver()\n        Task {\n            await reloadCalendarAndReminderLists()\n        }\n    }\n\n    deinit {\n        if let observer = eventStoreChangedObserver {\n            NotificationCenter.default.removeObserver(observer)\n        }\n    }\n\n    private func setupEventStoreChangedObserver() {\n        eventStoreChangedObserver = NotificationCenter.default.addObserver(\n            forName: .EKEventStoreChanged,\n            object: nil,\n            queue: .main\n        ) { [weak self] _ in\n            Task {\n                await self?.reloadCalendarAndReminderLists()\n            }\n        }\n    }\n\n    @MainActor\n    func reloadCalendarAndReminderLists() async {\n        let all = await calendarService.calendars()\n        self.eventCalendars = all.filter { !$0.isReminder }\n        self.reminderLists = all.filter { $0.isReminder }\n        self.allCalendars = all // for legacy compatibility, can be removed if not needed\n        updateSelectedCalendars()\n    }\n\n    func checkCalendarAuthorization() async {\n        let status = EKEventStore.authorizationStatus(for: .event)\n        DispatchQueue.main.async {\n            print(\"📅 Current calendar authorization status: \\(status)\")\n            self.calendarAuthorizationStatus = status\n        }\n\n        switch status {\n        case .notDetermined:\n            guard let granted = try? await calendarService.requestAccess(to: .event) else {\n                self.calendarAuthorizationStatus = .notDetermined\n                return\n            }\n            self.calendarAuthorizationStatus = granted ? .fullAccess : .denied\n            if granted {\n                await reloadCalendarAndReminderLists()\n                events = await calendarService.events(\n                    from: currentWeekStartDate,\n                    to: Calendar.current.date(byAdding: .day, value: 1, to: currentWeekStartDate)!,\n                    calendars: selectedCalendars.map { $0.id })\n            }\n        case .restricted, .denied:\n            NSLog(\"Calendar access denied or restricted\")\n        case .fullAccess:\n            NSLog(\"Full access\")\n            await reloadCalendarAndReminderLists()\n            events = await calendarService.events(\n                from: currentWeekStartDate,\n                to: Calendar.current.date(byAdding: .day, value: 1, to: currentWeekStartDate)!,\n                calendars: selectedCalendars.map { $0.id })\n        case .writeOnly:\n            NSLog(\"Write only\")\n        @unknown default:\n            print(\"Unknown authorization status\")\n        }\n    }\n    \n    func checkReminderAuthorization() async {\n        let status = EKEventStore.authorizationStatus(for: .reminder)\n        DispatchQueue.main.async {\n            print(\"📅 Current reminder authorization status: \\(status)\")\n            self.reminderAuthorizationStatus = status\n        }\n\n        switch status {\n        case .notDetermined:\n            guard let granted = try? await calendarService.requestAccess(to: .reminder) else {\n                self.reminderAuthorizationStatus = .notDetermined\n                return\n            }\n            self.reminderAuthorizationStatus = granted ? .fullAccess : .denied\n            if granted {\n                await reloadCalendarAndReminderLists()\n            }\n        case .restricted, .denied:\n            NSLog(\"Reminder access denied or restricted\")\n        case .fullAccess:\n            NSLog(\"Full access\")\n            await reloadCalendarAndReminderLists()\n        case .writeOnly:\n            NSLog(\"Write only\")\n        @unknown default:\n            print(\"Unknown authorization status\")\n        }\n    }\n        \n\n    func updateSelectedCalendars() {\n        // Populate selectedCalendarIDs based on Defaults calendar selection state\n        switch Defaults[.calendarSelectionState] {\n        case .all:\n            selectedCalendarIDs = Set(allCalendars.map { $0.id })\n        case .selected(let identifiers):\n            selectedCalendarIDs = identifiers\n        }\n\n        // Update the local calendar objects that correspond to the selected ids\n        selectedCalendars = allCalendars.filter { selectedCalendarIDs.contains($0.id) }\n    }\n\n    func getCalendarSelected(_ calendar: CalendarModel) -> Bool {\n        return selectedCalendarIDs.contains(calendar.id)\n    }\n\n    func setCalendarSelected(_ calendar: CalendarModel, isSelected: Bool) async {\n        var selectionState = Defaults[.calendarSelectionState]\n\n        switch selectionState {\n        case .all:\n            if !isSelected {\n                let identifiers = Set(allCalendars.map { $0.id }).subtracting([calendar.id])\n                selectionState = .selected(identifiers)\n            }\n\n        case .selected(var identifiers):\n            if isSelected {\n                identifiers.insert(calendar.id)\n            } else {\n                identifiers.remove(calendar.id)\n            }\n\n            selectionState =\n                identifiers.isEmpty\n                ? .all : identifiers.count == allCalendars.count ? .all : .selected(identifiers)  // if empty, select all\n        }\n\n        Defaults[.calendarSelectionState] = selectionState\n        updateSelectedCalendars()\n        await updateEvents()\n    }\n\n    static func startOfDay(_ date: Date) -> Date {\n        return Calendar.current.startOfDay(for: date)\n    }\n\n    func updateCurrentDate(_ date: Date) async {\n        currentWeekStartDate = Calendar.current.startOfDay(for: date)\n        await updateEvents()\n    }\n\n    private func updateEvents() async {\n        let calendarIDs = selectedCalendars.map { $0.id }\n        let eventsResult = await calendarService.events(\n            from: currentWeekStartDate,\n            to: Calendar.current.date(byAdding: .day, value: 1, to: currentWeekStartDate)!,\n            calendars: calendarIDs\n        )\n        self.events = eventsResult\n    }\n    \n    func setReminderCompleted(reminderID: String, completed: Bool) async {\n        await calendarService.setReminderCompleted(reminderID: reminderID, completed: completed)\n        // Refresh events after updating\n        events = await calendarService.events(\n            from: currentWeekStartDate,\n            to: Calendar.current.date(byAdding: .day, value: 1, to: currentWeekStartDate)!,\n            calendars: selectedCalendars.map { $0.id })\n    }\n}\n"
  },
  {
    "path": "boringNotch/managers/ImageService.swift",
    "content": "//\n//  ImageService.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-09-13.\n//\n\nimport Foundation\nimport Defaults\n\npublic protocol ImageServiceProtocol {\n    func fetchImageData(from url: URL) async throws -> Data\n}\n\npublic final class ImageService: ImageServiceProtocol {\n    public static let shared = ImageService()\n\n    private let session: URLSession\n\n    private init() {\n        let config = URLSessionConfiguration.default\n        let cache = URLCache(memoryCapacity: 50 * 1024 * 1024, // 50MB\n                             diskCapacity: 100 * 1024 * 1024, // 100MB\n                             diskPath: \"artwork_cache\")\n        config.urlCache = cache\n        config.timeoutIntervalForRequest = 15\n        config.timeoutIntervalForResource = 30\n        config.httpShouldSetCookies = false\n        self.session = URLSession(configuration: config)\n\n        performLegacyCacheCleanupIfNeeded()\n    }\n\n    private func performLegacyCacheCleanupIfNeeded() {\n\n        if !Defaults[.didClearLegacyURLCacheV1] {\n            URLCache.shared.removeAllCachedResponses()\n            Defaults[.didClearLegacyURLCacheV1] = true\n        }\n    }\n\n    public func fetchImageData(from url: URL) async throws -> Data {\n        guard let scheme = url.scheme?.lowercased(), scheme == \"http\" || scheme == \"https\" else {\n            throw URLError(.unsupportedURL)\n        }\n        let (data, _) = try await session.data(from: url)\n        return data\n    }\n}\n"
  },
  {
    "path": "boringNotch/managers/MusicManager.swift",
    "content": "//\n//  MusicManager.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 03/08/24.\n//\nimport AppKit\nimport Combine\nimport Defaults\nimport SwiftUI\n\nlet defaultImage: NSImage = .init(\n    systemSymbolName: \"heart.fill\",\n    accessibilityDescription: \"Album Art\"\n)!\n\nclass MusicManager: ObservableObject {\n    // MARK: - Properties\n    static let shared = MusicManager()\n    private var cancellables = Set<AnyCancellable>()\n    private var controllerCancellables = Set<AnyCancellable>()\n    private var debounceIdleTask: Task<Void, Never>?\n\n    // Helper to check if macOS has removed support for NowPlayingController\n    public private(set) var isNowPlayingDeprecated: Bool = false\n    private let mediaChecker = MediaChecker()\n\n    // Active controller\n    private var activeController: (any MediaControllerProtocol)?\n\n    // Published properties for UI\n    @Published var songTitle: String = \"I'm Handsome\"\n    @Published var artistName: String = \"Me\"\n    @Published var albumArt: NSImage = defaultImage\n    @Published var isPlaying = false\n    @Published var album: String = \"Self Love\"\n    @Published var isPlayerIdle: Bool = true\n    @Published var animations: BoringAnimations = .init()\n    @Published var avgColor: NSColor = .white\n    @Published var bundleIdentifier: String? = nil\n    @Published var songDuration: TimeInterval = 0\n    @Published var elapsedTime: TimeInterval = 0\n    @Published var timestampDate: Date = .init()\n    @Published var playbackRate: Double = 1\n    @Published var isShuffled: Bool = false\n    @Published var repeatMode: RepeatMode = .off\n    @Published var volume: Double = 0.5\n    @Published var volumeControlSupported: Bool = true\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @Published var usingAppIconForArtwork: Bool = false\n    @Published var currentLyrics: String = \"\"\n    @Published var isFetchingLyrics: Bool = false\n    @Published var syncedLyrics: [(time: Double, text: String)] = []\n    @Published var canFavoriteTrack: Bool = false\n    @Published var isFavoriteTrack: Bool = false\n\n    private var artworkData: Data? = nil\n\n    // Store last values at the time artwork was changed\n    private var lastArtworkTitle: String = \"I'm Handsome\"\n    private var lastArtworkArtist: String = \"Me\"\n    private var lastArtworkAlbum: String = \"Self Love\"\n    private var lastArtworkBundleIdentifier: String? = nil\n\n    @Published var isFlipping: Bool = false\n    private var flipWorkItem: DispatchWorkItem?\n\n    @Published var isTransitioning: Bool = false\n    private var transitionWorkItem: DispatchWorkItem?\n\n    // MARK: - Initialization\n    init() {\n        // Listen for changes to the default controller preference\n        NotificationCenter.default.publisher(for: Notification.Name.mediaControllerChanged)\n            .sink { [weak self] _ in\n                self?.setActiveControllerBasedOnPreference()\n            }\n            .store(in: &cancellables)\n\n        // Initialize deprecation check asynchronously\n        Task { @MainActor in\n            do {\n                self.isNowPlayingDeprecated = try await self.mediaChecker.checkDeprecationStatus()\n                print(\"Deprecation check completed: \\(self.isNowPlayingDeprecated)\")\n            } catch {\n                print(\"Failed to check deprecation status: \\(error). Defaulting to false.\")\n                self.isNowPlayingDeprecated = false\n            }\n            \n            // Initialize the active controller after deprecation check\n            self.setActiveControllerBasedOnPreference()\n        }\n    }\n\n    deinit {\n        destroy()\n    }\n    \n    public func destroy() {\n        debounceIdleTask?.cancel()\n        cancellables.removeAll()\n        controllerCancellables.removeAll()\n        flipWorkItem?.cancel()\n        transitionWorkItem?.cancel()\n\n        // Release active controller\n        activeController = nil\n    }\n\n    // MARK: - Setup Methods\n    private func createController(for type: MediaControllerType) -> (any MediaControllerProtocol)? {\n        // Cleanup previous controller\n        if activeController != nil {\n            controllerCancellables.removeAll()\n            activeController = nil\n        }\n\n        let newController: (any MediaControllerProtocol)?\n\n        switch type {\n        case .nowPlaying:\n            // Only create NowPlayingController if not deprecated on this macOS version\n            if !self.isNowPlayingDeprecated {\n                newController = NowPlayingController()\n            } else {\n                return nil\n            }\n        case .appleMusic:\n            newController = AppleMusicController()\n        case .spotify:\n            newController = SpotifyController()\n        case .youtubeMusic:\n            newController = YouTubeMusicController()\n        }\n\n        // Set up state observation for the new controller\n        if let controller = newController {\n            controller.playbackStatePublisher\n                .receive(on: DispatchQueue.main)\n                .sink { [weak self] state in\n                    guard let self = self,\n                          self.activeController === controller else { return }\n                    self.updateFromPlaybackState(state)\n                }\n                .store(in: &controllerCancellables)\n        }\n\n        return newController\n    }\n\n    private func setActiveControllerBasedOnPreference() {\n        let preferredType = Defaults[.mediaController]\n        print(\"Preferred Media Controller: \\(preferredType)\")\n\n        // If NowPlaying is deprecated but that's the preference, use Apple Music instead\n        let controllerType = (self.isNowPlayingDeprecated && preferredType == .nowPlaying)\n            ? .appleMusic\n            : preferredType\n\n        if let controller = createController(for: controllerType) {\n            setActiveController(controller)\n        } else if controllerType != .appleMusic, let fallbackController = createController(for: .appleMusic) {\n            // Fallback to Apple Music if preferred controller couldn't be created\n            setActiveController(fallbackController)\n        }\n    }\n\n    private func setActiveController(_ controller: any MediaControllerProtocol) {\n        // Cancel any existing flip animation\n        flipWorkItem?.cancel()\n\n        // Set new active controller\n        activeController = controller\n        \n        self.canFavoriteTrack = controller.supportsFavorite\n\n        // Get current state from active controller\n        forceUpdate()\n    }\n\n    // MARK: - Update Methods\n    @MainActor\n    private func updateFromPlaybackState(_ state: PlaybackState) {\n        // Check for playback state changes (playing/paused)\n        if state.isPlaying != self.isPlaying {\n            NSLog(\"Playback state changed: \\(state.isPlaying ? \"Playing\" : \"Paused\")\")\n            withAnimation(.smooth) {\n                self.isPlaying = state.isPlaying\n                self.updateIdleState(state: state.isPlaying)\n            }\n\n            if state.isPlaying && !state.title.isEmpty && !state.artist.isEmpty {\n                self.updateSneakPeek()\n            }\n        }\n\n        // Check for changes in track metadata using last artwork change values\n        let titleChanged = state.title != self.lastArtworkTitle\n        let artistChanged = state.artist != self.lastArtworkArtist\n        let albumChanged = state.album != self.lastArtworkAlbum\n        let bundleChanged = state.bundleIdentifier != self.lastArtworkBundleIdentifier\n\n        // Check for artwork changes\n        let artworkChanged = state.artwork != nil && state.artwork != self.artworkData\n        let hasContentChange = titleChanged || artistChanged || albumChanged || artworkChanged || bundleChanged\n\n        // Handle artwork and visual transitions for changed content\n        if hasContentChange {\n            self.triggerFlipAnimation()\n\n            if artworkChanged, let artwork = state.artwork {\n                self.updateArtwork(artwork)\n            } else if state.artwork == nil {\n                // Try to use app icon if no artwork but track changed\n                if let appIconImage = AppIconAsNSImage(for: state.bundleIdentifier) {\n                    self.usingAppIconForArtwork = true\n                    self.updateAlbumArt(newAlbumArt: appIconImage)\n                }\n            }\n            self.artworkData = state.artwork\n\n            if artworkChanged || state.artwork == nil {\n                // Update last artwork change values\n                self.lastArtworkTitle = state.title\n                self.lastArtworkArtist = state.artist\n                self.lastArtworkAlbum = state.album\n                self.lastArtworkBundleIdentifier = state.bundleIdentifier\n            }\n\n            // Only update sneak peek if there's actual content and something changed\n            if !state.title.isEmpty && !state.artist.isEmpty && state.isPlaying {\n                self.updateSneakPeek()\n            }\n\n            // Fetch lyrics on content change\n            self.fetchLyricsIfAvailable(bundleIdentifier: state.bundleIdentifier, title: state.title, artist: state.artist)\n        }\n\n        let timeChanged = state.currentTime != self.elapsedTime\n        let durationChanged = state.duration != self.songDuration\n        let playbackRateChanged = state.playbackRate != self.playbackRate\n        let shuffleChanged = state.isShuffled != self.isShuffled\n        let repeatModeChanged = state.repeatMode != self.repeatMode\n        let volumeChanged = state.volume != self.volume\n        \n        if state.title != self.songTitle {\n            self.songTitle = state.title\n        }\n\n        if state.artist != self.artistName {\n            self.artistName = state.artist\n        }\n\n        if state.album != self.album {\n            self.album = state.album\n        }\n\n        if timeChanged {\n            self.elapsedTime = state.currentTime\n        }\n\n        if durationChanged {\n            self.songDuration = state.duration\n        }\n\n        if playbackRateChanged {\n            self.playbackRate = state.playbackRate\n        }\n        \n        if shuffleChanged {\n            self.isShuffled = state.isShuffled\n        }\n\n        if state.bundleIdentifier != self.bundleIdentifier {\n            self.bundleIdentifier = state.bundleIdentifier\n            // Update volume control support from active controller\n            self.volumeControlSupported = activeController?.supportsVolumeControl ?? false\n        }\n\n        if repeatModeChanged {\n            self.repeatMode = state.repeatMode\n        }\n        if state.isFavorite != self.isFavoriteTrack {\n            self.isFavoriteTrack = state.isFavorite\n        }\n        \n        if volumeChanged {\n            self.volume = state.volume\n        }\n        \n        self.timestampDate = state.lastUpdated\n    }\n\n    func toggleFavoriteTrack() {\n        guard canFavoriteTrack else { return }\n        // Toggle based on current state\n        setFavorite(!isFavoriteTrack)\n    }\n\n    @MainActor\n    private func toggleAppleMusicFavorite() async {\n        let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.Music\")\n        guard !runningApps.isEmpty else { return }\n\n        let script = \"\"\"\n        tell application \\\"Music\\\"\n            if it is running then\n                try\n                    set loved of current track to (not loved of current track)\n                    return loved of current track\n                on error\n                    return false\n                end try\n            else\n                return false\n            end if\n        end tell\n        \"\"\"\n\n        if let result = try? await AppleScriptHelper.execute(script) {\n            let loved = result.booleanValue\n            self.isFavoriteTrack = loved\n            self.forceUpdate()\n        }\n    }\n\n    func setFavorite(_ favorite: Bool) {\n        guard canFavoriteTrack else { return }\n        guard let controller = activeController else { return }\n\n        Task { @MainActor in\n            await controller.setFavorite(favorite)\n            try? await Task.sleep(for: .milliseconds(150))\n            await controller.updatePlaybackInfo()\n        }\n    }\n\n    /// Placeholder dislike function\n    func dislikeCurrentTrack() {\n        setFavorite(false)\n    }\n\n    // MARK: - Lyrics\n    private func fetchLyricsIfAvailable(bundleIdentifier: String?, title: String, artist: String) {\n        guard Defaults[.enableLyrics], !title.isEmpty else {\n            DispatchQueue.main.async {\n                self.isFetchingLyrics = false\n                self.currentLyrics = \"\"\n            }\n            return\n        }\n\n        // Prefer native Apple Music lyrics when available\n        if let bundleIdentifier = bundleIdentifier, bundleIdentifier.contains(\"com.apple.Music\") {\n            Task { @MainActor in\n                let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier: \"com.apple.Music\")\n                guard !runningApps.isEmpty else {\n                    await self.fetchLyricsFromWeb(title: title, artist: artist)\n                    return\n                }\n\n                self.isFetchingLyrics = true\n                self.currentLyrics = \"\"\n                do {\n                    let script = \"\"\"\n                    tell application \\\"Music\\\"\n                        if it is running then\n                            if player state is playing or player state is paused then\n                                try\n                                    set l to lyrics of current track\n                                    if l is missing value then\n                                        return \\\"\\\"\n                                    else\n                                        return l\n                                    end if\n                                on error\n                                    return \\\"\\\"\n                                end try\n                            else\n                                return \\\"\\\"\n                            end if\n                        else\n                            return \\\"\\\"\n                        end if\n                    end tell\n                    \"\"\"\n                    if let result = try await AppleScriptHelper.execute(script), let lyricsString = result.stringValue, !lyricsString.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {\n                        self.currentLyrics = lyricsString.trimmingCharacters(in: .whitespacesAndNewlines)\n                        self.isFetchingLyrics = false\n                        self.syncedLyrics = []\n                        return\n                    }\n                } catch {\n                    // fall through to web lookup\n                }\n                await self.fetchLyricsFromWeb(title: title, artist: artist)\n            }\n        } else {\n            Task { @MainActor in\n                self.isFetchingLyrics = true\n                self.currentLyrics = \"\"\n                await self.fetchLyricsFromWeb(title: title, artist: artist)\n            }\n        }\n    }\n\n    private func normalizedQuery(_ string: String) -> String {\n        string\n            .folding(options: .diacriticInsensitive, locale: .current)\n            .replacingOccurrences(of: \"\\u{FFFD}\", with: \"\")\n    }\n\n    @MainActor\n    private func fetchLyricsFromWeb(title: String, artist: String) async {\n        let cleanTitle = normalizedQuery(title)\n        let cleanArtist = normalizedQuery(artist)\n        guard let encodedTitle = cleanTitle.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed),\n              let encodedArtist = cleanArtist.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {\n            self.currentLyrics = \"\"\n            self.isFetchingLyrics = false\n            return\n        }\n\n        // LRCLIB simple search (no auth): https://lrclib.net/api/search?track_name=...&artist_name=...\n        let urlString = \"https://lrclib.net/api/search?track_name=\\(encodedTitle)&artist_name=\\(encodedArtist)\"\n        guard let url = URL(string: urlString) else {\n            self.currentLyrics = \"\"\n            self.isFetchingLyrics = false\n            return\n        }\n        do {\n            let (data, response) = try await URLSession.shared.data(from: url)\n            guard let http = response as? HTTPURLResponse, http.statusCode == 200 else {\n                self.currentLyrics = \"\"\n                self.isFetchingLyrics = false\n                return\n            }\n            if let jsonArray = try JSONSerialization.jsonObject(with: data) as? [[String: Any]],\n               let first = jsonArray.first {\n                // Prefer plain lyrics (syncedLyrics may also be present)\n                let plain = (first[\"plainLyrics\"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n                let synced = (first[\"syncedLyrics\"] as? String)?.trimmingCharacters(in: .whitespacesAndNewlines) ?? \"\"\n                let resolved = plain.isEmpty ? synced : plain\n                self.currentLyrics = resolved\n                self.isFetchingLyrics = false\n                if !synced.isEmpty {\n                    self.syncedLyrics = self.parseLRC(synced)\n                } else {\n                    self.syncedLyrics = []\n                }\n            } else {\n                self.currentLyrics = \"\"\n                self.isFetchingLyrics = false\n                self.syncedLyrics = []\n            }\n        } catch {\n            self.currentLyrics = \"\"\n            self.isFetchingLyrics = false\n            self.syncedLyrics = []\n        }\n    }\n\n    // MARK: - Synced lyrics helpers\n    private func parseLRC(_ lrc: String) -> [(time: Double, text: String)] {\n        var result: [(Double, String)] = []\n        lrc.split(separator: \"\\n\").forEach { lineSub in\n            let line = String(lineSub)\n            // Match [mm:ss.xx] or [m:ss]\n            let pattern = #\"\\[(\\d{1,2}):(\\d{2})(?:\\.(\\d{1,2}))?\\]\"#\n            guard let regex = try? NSRegularExpression(pattern: pattern) else { return }\n            let nsLine = line as NSString\n            if let match = regex.firstMatch(in: line, range: NSRange(location: 0, length: nsLine.length)) {\n                let minStr = nsLine.substring(with: match.range(at: 1))\n                let secStr = nsLine.substring(with: match.range(at: 2))\n                let csRange = match.range(at: 3)\n                let centiStr = csRange.location != NSNotFound ? nsLine.substring(with: csRange) : \"0\"\n                let minutes = Double(minStr) ?? 0\n                let seconds = Double(secStr) ?? 0\n                let centis = Double(centiStr) ?? 0\n                let time = minutes * 60 + seconds + centis / 100.0\n                let textStart = match.range.location + match.range.length\n                let text = nsLine.substring(from: textStart).trimmingCharacters(in: .whitespaces)\n                if !text.isEmpty {\n                    result.append((time, text))\n                }\n            }\n        }\n        return result.sorted { $0.0 < $1.0 }\n    }\n\n    func lyricLine(at elapsed: Double) -> String {\n        guard !syncedLyrics.isEmpty else { return currentLyrics }\n        // Binary search for last line with time <= elapsed\n        var low = 0\n        var high = syncedLyrics.count - 1\n        var idx = 0\n        while low <= high {\n            let mid = (low + high) / 2\n            if syncedLyrics[mid].time <= elapsed {\n                idx = mid\n                low = mid + 1\n            } else {\n                high = mid - 1\n            }\n        }\n        return syncedLyrics[idx].text\n    }\n\n    private func triggerFlipAnimation() {\n        // Cancel any existing animation\n        flipWorkItem?.cancel()\n\n        // Create a new animation\n        let workItem = DispatchWorkItem { [weak self] in\n            self?.isFlipping = true\n            DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) {\n                self?.isFlipping = false\n            }\n        }\n\n        flipWorkItem = workItem\n        DispatchQueue.main.async(execute: workItem)\n    }\n\n    private func updateArtwork(_ artworkData: Data) {\n        DispatchQueue.global(qos: .userInitiated).async { [weak self] in\n            guard let self = self else { return }\n\n            if let artworkImage = NSImage(data: artworkData) {\n                DispatchQueue.main.async { [weak self] in\n                    self?.usingAppIconForArtwork = false\n                    self?.updateAlbumArt(newAlbumArt: artworkImage)\n                }\n            }\n        }\n    }\n\n    private func updateIdleState(state: Bool) {\n        if state {\n            isPlayerIdle = false\n            debounceIdleTask?.cancel()\n        } else {\n            debounceIdleTask?.cancel()\n            debounceIdleTask = Task { [weak self] in\n                guard let self = self else { return }\n                try? await Task.sleep(for: .seconds(Defaults[.waitInterval]))\n                withAnimation {\n                    self.isPlayerIdle = !self.isPlaying\n                }\n            }\n        }\n    }\n\n    private var workItem: DispatchWorkItem?\n\n    func updateAlbumArt(newAlbumArt: NSImage) {\n        workItem?.cancel()\n        withAnimation(.smooth) {\n            self.albumArt = newAlbumArt\n            if Defaults[.coloredSpectrogram] {\n                self.calculateAverageColor()\n            }\n        }\n    }\n\n    // MARK: - Playback Position Estimation\n    public func estimatedPlaybackPosition(at date: Date = Date()) -> TimeInterval {\n        guard isPlaying else { return min(elapsedTime, songDuration) }\n\n        let timeDifference = date.timeIntervalSince(timestampDate)\n        let estimated = elapsedTime + (timeDifference * playbackRate)\n        return min(max(0, estimated), songDuration)\n    }\n\n    func calculateAverageColor() {\n        albumArt.averageColor { [weak self] color in\n            DispatchQueue.main.async {\n                withAnimation(.smooth) {\n                    self?.avgColor = color ?? .white\n                }\n            }\n        }\n    }\n\n    private func updateSneakPeek() {\n        if isPlaying && Defaults[.enableSneakPeek] {\n            if Defaults[.sneakPeekStyles] == .standard {\n                coordinator.toggleSneakPeek(status: true, type: .music)\n            } else {\n                coordinator.toggleExpandingView(status: true, type: .music)\n            }\n        }\n    }\n\n    // MARK: - Public Methods for controlling playback\n    func playPause() {\n        Task {\n            await activeController?.togglePlay()\n        }\n    }\n\n    func play() {\n        Task {\n            await activeController?.play()\n        }\n    }\n\n    func pause() {\n        Task {\n            await activeController?.pause()\n        }\n    }\n\n    func toggleShuffle() {\n        Task {\n            await activeController?.toggleShuffle()\n        }\n    }\n\n    func toggleRepeat() {\n        Task {\n            await activeController?.toggleRepeat()\n        }\n    }\n    \n    func togglePlay() {\n        Task {\n            await activeController?.togglePlay()\n        }\n    }\n\n    func nextTrack() {\n        Task {\n            await activeController?.nextTrack()\n        }\n    }\n\n    func previousTrack() {\n        Task {\n            await activeController?.previousTrack()\n        }\n    }\n\n    func seek(to position: TimeInterval) {\n        Task {\n            await activeController?.seek(to: position)\n        }\n    }\n    func skip(seconds: TimeInterval) {\n        let newPos = min(max(0, elapsedTime + seconds), songDuration)\n        seek(to: newPos)\n    }\n    \n    func setVolume(to level: Double) {\n        if let controller = activeController {\n            Task {\n                await controller.setVolume(level)\n            }\n        }\n    }\n    func openMusicApp() {\n        guard let bundleID = bundleIdentifier else {\n            print(\"Error: appBundleIdentifier is nil\")\n            return\n        }\n\n        let workspace = NSWorkspace.shared\n        if let appURL = workspace.urlForApplication(withBundleIdentifier: bundleID) {\n            let configuration = NSWorkspace.OpenConfiguration()\n            workspace.openApplication(at: appURL, configuration: configuration) { (app, error) in\n                if let error = error {\n                    print(\"Failed to launch app with bundle ID: \\(bundleID), error: \\(error)\")\n                } else {\n                    print(\"Launched app with bundle ID: \\(bundleID)\")\n                }\n            }\n        } else {\n            print(\"Failed to find app with bundle ID: \\(bundleID)\")\n        }\n    }\n\n    func forceUpdate() {\n        // Request immediate update from the active controller\n        Task { [weak self] in\n            if self?.activeController?.isActive() == true {\n                if let youtubeController = self?.activeController as? YouTubeMusicController {\n                    await youtubeController.pollPlaybackState()\n                } else {\n                    await self?.activeController?.updatePlaybackInfo()\n                }\n            }\n        }\n    }\n    \n    \n    func syncVolumeFromActiveApp() async {\n        // Check if bundle identifier is valid and if the app is actually running\n        guard let bundleID = bundleIdentifier, !bundleID.isEmpty,\n              NSWorkspace.shared.runningApplications.contains(where: { $0.bundleIdentifier == bundleID }) else { return }\n        \n        var script: String?\n        if bundleID == \"com.apple.Music\" {\n            script = \"\"\"\n            tell application \"Music\"\n                if it is running then\n                    get sound volume\n                else\n                    return 50\n                end if\n            end tell\n            \"\"\"\n        } else if bundleID == \"com.spotify.client\" {\n            script = \"\"\"\n            tell application \"Spotify\"\n                if it is running then\n                    get sound volume\n                else\n                    return 50\n                end if\n            end tell\n            \"\"\"\n        } else {\n            // For unsupported apps, don't sync volume\n            return\n        }\n        \n        if let volumeScript = script,\n           let result = try? await AppleScriptHelper.execute(volumeScript) {\n            let volumeValue = result.int32Value\n            let currentVolume = Double(volumeValue) / 100.0\n            \n            await MainActor.run {\n                if abs(currentVolume - self.volume) > 0.01 {\n                    self.volume = currentVolume\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/managers/NotchSpaceManager.swift",
    "content": "//\n//  NotchSpaceManager.swift\n//  boringNotch\n//\n//  Created by Alexander on 2024-10-27.\n//\n\nimport Foundation\n\nclass NotchSpaceManager {\n    static let shared = NotchSpaceManager()\n    let notchSpace: CGSSpace\n    private var eventTap: CFMachPort?\n    private var runLoopSource: CFRunLoopSource?\n    \n    private init() {\n        notchSpace = CGSSpace(level: 2147483647) // Max level\n    }\n}\n"
  },
  {
    "path": "boringNotch/managers/VolumeManager.swift",
    "content": "//\n//  VolumeManager.swift\n//  boringNotch\n//\n//  Created by JeanLouis on 22/08/2025.\n//\n\nimport AppKit\nimport Combine\nimport CoreAudio\nimport Foundation\n\nfinal class VolumeManager: NSObject, ObservableObject {\n    static let shared = VolumeManager()\n\n    @Published private(set) var rawVolume: Float = 0\n    @Published private(set) var isMuted: Bool = false\n    @Published private(set) var lastChangeAt: Date = .distantPast\n\n    let visibleDuration: TimeInterval = 1.2\n\n    private var didInitialFetch = false\n    private let step: Float32 = 1.0 / 16.0\n    // Fallback software if hardware mute is not supported\n    private var previousVolumeBeforeMute: Float32 = 0.2\n    private var softwareMuted: Bool = false\n\n    private override init() {\n        super.init()\n        setupAudioListener()\n        fetchCurrentVolume()\n    }\n\n    var shouldShowOverlay: Bool { Date().timeIntervalSince(lastChangeAt) < visibleDuration }\n\n    // MARK: - Public Control API\n    @MainActor func increase(stepDivisor: Float = 1.0) {\n        let divisor = max(stepDivisor, 0.25)\n        let delta = step / Float32(divisor)\n        let current = readVolumeInternal() ?? rawVolume\n        let target = max(0, min(1, current + delta))\n        setAbsolute(target)\n        BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .volume, value: CGFloat(target))\n    }\n\n    @MainActor func decrease(stepDivisor: Float = 1.0) {\n        let divisor = max(stepDivisor, 0.25)\n        let delta = step / Float32(divisor)\n        let current = readVolumeInternal() ?? rawVolume\n        let target = max(0, min(1, current - delta))\n        setAbsolute(target)\n        BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .volume, value: CGFloat(target))\n    }\n\n    @MainActor func toggleMuteAction() {\n        // Determine expected resulting state immediately and show HUD with that value\n        let deviceID = systemOutputDeviceID()\n        var willBeMuted = false\n        var resultingVolume: Float32 = rawVolume\n\n        if deviceID == kAudioObjectUnknown {\n            willBeMuted = !softwareMuted\n            resultingVolume = willBeMuted ? 0 : previousVolumeBeforeMute\n        } else {\n            let currentMuted = isMutedInternal()\n            willBeMuted = !currentMuted\n            resultingVolume = willBeMuted ? 0 : (readVolumeInternal() ?? rawVolume)\n        }\n\n        toggleMuteInternal()\n        BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .volume, value: CGFloat(willBeMuted ? 0 : resultingVolume))\n    }\n    \n    func refresh() { fetchCurrentVolume() }\n\n    func adjustRelative(delta: Float32) {\n        if isMutedInternal() { toggleMuteInternal() }\n        guard let current = readVolumeInternal() else {\n            fetchCurrentVolume()\n            return\n        }\n        let target = max(0, min(1, current + delta))\n        writeVolumeInternal(target)  \n        publish(volume: target, muted: isMutedInternal(), touchDate: true)\n    }\n\n    @MainActor func setAbsolute(_ value: Float32) {\n        let clamped = max(0, min(1, value))\n        let currentlyMuted = isMutedInternal()\n        if currentlyMuted && clamped > 0 {\n            toggleMuteInternal()\n        }\n\n        writeVolumeInternal(clamped)\n\n        if clamped == 0 && !currentlyMuted {\n            toggleMuteInternal()\n        }\n\n        publish(volume: clamped, muted: isMutedInternal(), touchDate: true)\n    }\n\n    // MARK: - CoreAudio Helpers\n    private func systemOutputDeviceID() -> AudioObjectID {\n        var defaultDeviceID = kAudioObjectUnknown\n        var propertyAddress = AudioObjectPropertyAddress(\n            mSelector: kAudioHardwarePropertyDefaultOutputDevice,\n            mScope: kAudioObjectPropertyScopeGlobal,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        var dataSize = UInt32(MemoryLayout<AudioObjectID>.size)\n        let status = AudioObjectGetPropertyData(\n            AudioObjectID(kAudioObjectSystemObject),\n            &propertyAddress,\n            0,\n            nil,\n            &dataSize,\n            &defaultDeviceID\n        )\n        if status != noErr { return kAudioObjectUnknown }\n        return defaultDeviceID\n    }\n\n    private func fetchCurrentVolume() {\n        let deviceID = systemOutputDeviceID()\n        guard deviceID != kAudioObjectUnknown else { return }\n        var volumes: [Float32] = []\n        let candidateElements: [UInt32] = [kAudioObjectPropertyElementMain, 1, 2, 3, 4]\n        for element in candidateElements {\n            if let v = readValidatedScalar(deviceID: deviceID, element: element) {\n                volumes.append(v)\n            }\n        }\n        if !volumes.isEmpty {\n            let avg = max(0, min(1, volumes.reduce(0, +) / Float32(volumes.count)))\n            DispatchQueue.main.async {\n                if self.rawVolume != avg {  \n                    if self.didInitialFetch {\n                        self.lastChangeAt = Date()\n                    }\n                }\n                self.rawVolume = avg\n                self.didInitialFetch = true\n\n            }\n        }\n\n        var muteAddr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyMute,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        if AudioObjectHasProperty(deviceID, &muteAddr) {\n            var sizeNeeded: UInt32 = 0\n            if AudioObjectGetPropertyDataSize(deviceID, &muteAddr, 0, nil, &sizeNeeded) == noErr,\n                sizeNeeded == UInt32(MemoryLayout<UInt32>.size)\n            {\n                var muted: UInt32 = 0\n                var mSize = sizeNeeded\n                if AudioObjectGetPropertyData(deviceID, &muteAddr, 0, nil, &mSize, &muted) == noErr\n                {\n                    let newMuted = muted != 0\n                    DispatchQueue.main.async {\n                        if self.isMuted != newMuted { self.lastChangeAt = Date() }\n                        self.isMuted = newMuted\n                    }\n                }\n            }\n        }\n    }\n\n    private func setupAudioListener() {\n        let deviceID = systemOutputDeviceID()\n        guard deviceID != kAudioObjectUnknown else { return }\n\n        var defaultDevAddr = AudioObjectPropertyAddress(\n            mSelector: kAudioHardwarePropertyDefaultOutputDevice,\n            mScope: kAudioObjectPropertyScopeGlobal,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        AudioObjectAddPropertyListenerBlock(\n            AudioObjectID(kAudioObjectSystemObject), &defaultDevAddr, nil\n        ) { _, _ in\n            self.fetchCurrentVolume()\n        }\n\n        var masterAddr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyVolumeScalar,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        if AudioObjectHasProperty(deviceID, &masterAddr) {\n            AudioObjectAddPropertyListenerBlock(deviceID, &masterAddr, nil) { _, _ in\n                self.fetchCurrentVolume()\n            }\n        } else {\n            for ch in [UInt32(1), UInt32(2)] {\n                var chAddr = AudioObjectPropertyAddress(\n                    mSelector: kAudioDevicePropertyVolumeScalar,\n                    mScope: kAudioDevicePropertyScopeOutput,\n                    mElement: ch\n                )\n                if AudioObjectHasProperty(deviceID, &chAddr) {\n                    AudioObjectAddPropertyListenerBlock(deviceID, &chAddr, nil) { _, _ in\n                        self.fetchCurrentVolume()\n                    }\n                }\n            }\n        }\n\n        // Mute\n        var muteAddr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyMute,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        if AudioObjectHasProperty(deviceID, &muteAddr) {\n            AudioObjectAddPropertyListenerBlock(deviceID, &muteAddr, nil) { _, _ in\n                self.fetchCurrentVolume()\n            }\n        }\n    }\n\n    private func readVolumeInternal() -> Float32? {\n        let deviceID = systemOutputDeviceID()\n        if deviceID == kAudioObjectUnknown { return nil }\n        var collected: [Float32] = []\n        for el in [kAudioObjectPropertyElementMain, 1, 2, 3, 4] {\n            if let v = readValidatedScalar(deviceID: deviceID, element: el) { collected.append(v) }\n        }\n        guard !collected.isEmpty else { return nil }\n        return collected.reduce(0, +) / Float32(collected.count)\n    }\n\n    private func writeVolumeInternal(_ value: Float32) {\n        let deviceID = systemOutputDeviceID()\n        if deviceID == kAudioObjectUnknown { return }\n        let newVal = max(0, min(1, value))\n\n        var written = false\n        if writeValidatedScalar(\n            deviceID: deviceID, element: kAudioObjectPropertyElementMain, value: newVal)\n        {\n            written = true\n        } else {\n            var any = false\n            for el in [UInt32](1...4) {\n                if writeValidatedScalar(deviceID: deviceID, element: el, value: newVal) {\n                    any = true\n                }\n            }\n            written = any\n        }\n        if !written {\n            // silent fail\n        }\n    }\n\n    private func isMutedInternal() -> Bool {\n        let deviceID = systemOutputDeviceID()\n        if deviceID == kAudioObjectUnknown { return softwareMuted }\n        var muteAddr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyMute,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        guard AudioObjectHasProperty(deviceID, &muteAddr) else { return softwareMuted }\n        var sizeNeeded: UInt32 = 0\n        guard AudioObjectGetPropertyDataSize(deviceID, &muteAddr, 0, nil, &sizeNeeded) == noErr,\n            sizeNeeded == UInt32(MemoryLayout<UInt32>.size)\n        else { return softwareMuted }\n        var muted: UInt32 = 0\n        var size = sizeNeeded\n        if AudioObjectGetPropertyData(deviceID, &muteAddr, 0, nil, &size, &muted) == noErr {\n            return muted != 0\n        }\n        return softwareMuted\n    }\n\n    private func toggleMuteInternal() {\n        let deviceID = systemOutputDeviceID()\n        if deviceID == kAudioObjectUnknown {\n            performSoftwareMuteToggle(currentVolume: rawVolume)\n            return\n        }\n        var muteAddr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyMute,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: kAudioObjectPropertyElementMain\n        )\n        if !AudioObjectHasProperty(deviceID, &muteAddr) {\n            let currentVol = readVolumeInternal() ?? rawVolume\n            performSoftwareMuteToggle(currentVolume: currentVol)\n            return\n        }\n        var sizeNeeded: UInt32 = 0\n        guard AudioObjectGetPropertyDataSize(deviceID, &muteAddr, 0, nil, &sizeNeeded) == noErr,\n            sizeNeeded == UInt32(MemoryLayout<UInt32>.size)\n        else {\n            let currentVol = readVolumeInternal() ?? rawVolume\n            performSoftwareMuteToggle(currentVolume: currentVol)\n            return\n        }\n        var muted: UInt32 = 0\n        var size = sizeNeeded\n        if AudioObjectGetPropertyData(deviceID, &muteAddr, 0, nil, &size, &muted) == noErr {\n            var newVal: UInt32 = muted == 0 ? 1 : 0\n            AudioObjectSetPropertyData(deviceID, &muteAddr, 0, nil, size, &newVal)\n            let vol = readVolumeInternal() ?? rawVolume\n            publish(volume: vol, muted: newVal != 0, touchDate: true)\n        } else {\n            let currentVol = readVolumeInternal() ?? rawVolume\n            performSoftwareMuteToggle(currentVolume: currentVol)\n        }\n    }\n\n    private func performSoftwareMuteToggle(currentVolume: Float32) {\n        if softwareMuted {\n            let restore = max(0, min(1, previousVolumeBeforeMute))\n            writeVolumeInternal(restore)\n            softwareMuted = false\n            publish(volume: restore, muted: false, touchDate: true)\n        } else {\n            if currentVolume > 0.001 { previousVolumeBeforeMute = currentVolume }\n            writeVolumeInternal(0)\n            softwareMuted = true\n            publish(volume: 0, muted: true, touchDate: true)\n        }\n    }\n\n    private func readValidatedScalar(deviceID: AudioObjectID, element: UInt32) -> Float32? {\n        var addr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyVolumeScalar,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: element\n        )\n        guard AudioObjectHasProperty(deviceID, &addr) else { return nil }\n        var sizeNeeded: UInt32 = 0\n        guard AudioObjectGetPropertyDataSize(deviceID, &addr, 0, nil, &sizeNeeded) == noErr,\n            sizeNeeded == UInt32(MemoryLayout<Float32>.size)\n        else { return nil }\n        var vol = Float32(0)\n        var size = sizeNeeded\n        let status = AudioObjectGetPropertyData(deviceID, &addr, 0, nil, &size, &vol)\n        return status == noErr ? vol : nil\n    }\n\n    private func writeValidatedScalar(deviceID: AudioObjectID, element: UInt32, value: Float32)\n        -> Bool\n    {\n        var addr = AudioObjectPropertyAddress(\n            mSelector: kAudioDevicePropertyVolumeScalar,\n            mScope: kAudioDevicePropertyScopeOutput,\n            mElement: element\n        )\n        guard AudioObjectHasProperty(deviceID, &addr) else { return false }\n        var sizeNeeded: UInt32 = 0\n        guard AudioObjectGetPropertyDataSize(deviceID, &addr, 0, nil, &sizeNeeded) == noErr,\n            sizeNeeded == UInt32(MemoryLayout<Float32>.size)\n        else { return false }\n        var val = value\n        return AudioObjectSetPropertyData(deviceID, &addr, 0, nil, sizeNeeded, &val) == noErr\n    }\n\n    private func publish(volume: Float32, muted: Bool, touchDate: Bool) {\n        DispatchQueue.main.async {\n            if touchDate { self.lastChangeAt = Date() }\n            self.rawVolume = volume\n            self.isMuted = muted\n        }\n    }\n}\n\nextension Array where Element == Float32 {\n    fileprivate var average: Float32? { isEmpty ? nil : reduce(0, +) / Float32(count) }\n}\n\n\n"
  },
  {
    "path": "boringNotch/managers/WebcamManager.swift",
    "content": "//\n//  WebcamManager.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 19/08/24.\n//\nimport AVFoundation\nimport SwiftUI\n\nclass WebcamManager: NSObject, ObservableObject {\n    static let shared = WebcamManager()\n    \n    @Published var previewLayer: AVCaptureVideoPreviewLayer? {\n        didSet {\n            objectWillChange.send()\n        }\n    }\n    \n    private var captureSession: AVCaptureSession?\n    @Published var isSessionRunning: Bool = false {\n        didSet {\n            objectWillChange.send()\n        }\n    }\n    \n    @Published var authorizationStatus: AVAuthorizationStatus = .notDetermined {\n        didSet {\n            objectWillChange.send()\n        }\n    }\n    \n    @Published var cameraAvailable: Bool = false {\n        didSet {\n            objectWillChange.send()\n        }\n    }\n\n    private let sessionQueue = DispatchQueue(label: \"BoringNotch.WebcamManager.SessionQueue\", qos: .userInitiated)\n    \n    private var isCleaningUp: Bool = false\n    \n    // MARK: - Constants\n    \n    enum WebcamError: Error, LocalizedError {\n        case deviceUnavailable\n        case accessDenied\n        case configurationFailed(String)\n        \n        var errorDescription: String? {\n            switch self {\n            case .deviceUnavailable:\n                return \"No camera devices available\"\n            case .accessDenied:\n                return \"Camera access denied\"\n            case .configurationFailed(let message):\n                return \"Camera configuration failed: \\(message)\"\n            }\n        }\n    }\n    \n    // MARK: - Properties\n    \n    private override init() {\n        super.init()\n        NotificationCenter.default.addObserver(self, selector: #selector(deviceWasDisconnected), name: .AVCaptureDeviceWasDisconnected, object: nil)\n        NotificationCenter.default.addObserver(self, selector: #selector(deviceWasConnected), name: .AVCaptureDeviceWasConnected, object: nil)\n        checkCameraAvailability()\n    }\n    \n    deinit {\n        NotificationCenter.default.removeObserver(self)\n        \n        if let session = captureSession {\n            if session.isRunning {\n                session.stopRunning()\n            }\n        }\n        captureSession = nil\n            \n        previewLayer = nil\n    }\n\n    // MARK: - Camera Management\n    \n    /// Checks current authorization status and requests access if needed\n    func checkAndRequestVideoAuthorization() {\n        let status = AVCaptureDevice.authorizationStatus(for: .video)\n        DispatchQueue.main.async {\n            self.authorizationStatus = status\n        }\n        \n        switch status {\n        case .authorized:\n            checkCameraAvailability() // Check availability if authorized\n        case .notDetermined:\n            requestVideoAccess()\n        case .denied, .restricted:\n            NSLog(\"Camera access denied or restricted\")\n        @unknown default:\n            NSLog(\"Unknown authorization status\")\n        }\n    }\n    \n    /// Requests access to the camera\n    private func requestVideoAccess() {\n        AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in\n            DispatchQueue.main.async {\n                self?.authorizationStatus = granted ? .authorized : .denied\n                if granted {\n                    self?.checkCameraAvailability() // Check availability if access granted\n                }\n            }\n        }\n    }\n    \n    /// Checks if any camera devices are available and sets up capture session if needed\n    func checkCameraAvailability() {\n        let availableDevices = AVCaptureDevice.DiscoverySession(\n            deviceTypes: [.external, .builtInWideAngleCamera],\n            mediaType: .video,\n            position: .unspecified\n        ).devices\n        \n        let hasAvailableDevices = !availableDevices.isEmpty\n        \n        DispatchQueue.main.async {\n            self.cameraAvailable = hasAvailableDevices\n        }\n    }\n    \n    /// Sets up the capture session with a completion handler\n    private func setupCaptureSession(completion: @escaping (Bool) -> Void) {\n        sessionQueue.async { [weak self] in\n            guard let self = self else { \n                completion(false)\n                return \n            }\n            \n            // Clean up any existing session before creating a new one\n            self.cleanupExistingSession()\n            \n            let session = AVCaptureSession()\n            \n            do {\n                // Get available devices and prefer external camera if available\n                let discoverySession = AVCaptureDevice.DiscoverySession(\n                    deviceTypes: [.external, .builtInWideAngleCamera],\n                    mediaType: .video,\n                    position: .unspecified\n                )\n                \n                guard let videoDevice = discoverySession.devices.first else {\n                    NSLog(\"No video devices available\")\n                    DispatchQueue.main.async {\n                        self.isSessionRunning = false\n                        self.cameraAvailable = false\n                    }\n                    completion(false)\n                    return\n                }\n                \n                NSLog(\"Using camera: \\(videoDevice.localizedName)\")\n                \n                // Lock device for configuration\n                try videoDevice.lockForConfiguration()\n                defer { videoDevice.unlockForConfiguration() }\n                \n                let videoInput = try AVCaptureDeviceInput(device: videoDevice)\n                guard session.canAddInput(videoInput) else {\n                    throw NSError(domain: \"BoringNotch.WebcamManager\", code: -1, userInfo: [NSLocalizedDescriptionKey: \"Cannot add video input\"])\n                }\n                \n                session.beginConfiguration()\n                session.sessionPreset = .high\n                session.addInput(videoInput)\n                \n                let videoOutput = AVCaptureVideoDataOutput()\n                videoOutput.setSampleBufferDelegate(nil, queue: nil)\n                if session.canAddOutput(videoOutput) {\n                    session.addOutput(videoOutput)\n                }\n                session.commitConfiguration()\n                \n                self.captureSession = session\n                \n                // Create and set up preview layer on main thread\n                DispatchQueue.main.async {\n                    self.cameraAvailable = true\n                    let previewLayer = AVCaptureVideoPreviewLayer(session: session)\n                    previewLayer.videoGravity = .resizeAspectFill\n                    self.previewLayer = previewLayer\n                    \n                    // Setup is complete, let the caller know\n                    completion(true)\n                }\n                \n                NSLog(\"Capture session setup completed successfully\")\n            } catch {\n                NSLog(\"Failed to setup capture session: \\(error.localizedDescription)\")\n                DispatchQueue.main.async {\n                    self.isSessionRunning = false\n                    self.cameraAvailable = false\n                    self.previewLayer = nil\n                }\n                completion(false)\n            }\n        }\n    }\n    \n    /// Cleans up an existing capture session, removing all inputs and outputs\n    private func cleanupExistingSession() {\n        if let existingSession = self.captureSession {\n            // First stop the session if running\n            if existingSession.isRunning {\n                existingSession.stopRunning()\n            }\n            \n            // Then perform configuration cleanup\n            existingSession.beginConfiguration()\n            \n            // Remove all inputs and outputs\n            for input in existingSession.inputs {\n                existingSession.removeInput(input)\n            }\n            for output in existingSession.outputs {\n                existingSession.removeOutput(output)\n            }\n            \n            existingSession.commitConfiguration()\n            self.captureSession = nil\n            \n            // Clear preview layer on main thread\n            DispatchQueue.main.async {\n                self.previewLayer = nil\n            }\n        }\n    }\n\n    @objc private func deviceWasDisconnected(notification: Notification) {\n        NSLog(\"Camera device was disconnected\")\n        sessionQueue.async { [weak self] in\n            guard let self = self else { return }\n            self.stopSession()\n            DispatchQueue.main.async {\n                self.cameraAvailable = false\n            }\n        }\n    }\n\n    @objc private func deviceWasConnected(notification: Notification) {\n        NSLog(\"Camera device was connected\")\n        sessionQueue.async { [weak self] in\n            guard let self = self else { return }\n            self.checkCameraAvailability()\n        }\n    }\n\n    private func updateSessionState() {\n        let isRunning = self.captureSession?.isRunning ?? false\n        DispatchQueue.main.async {\n            self.isSessionRunning = isRunning\n        }\n    }\n    \n    func startSession() {\n        sessionQueue.async { [weak self] in\n            guard let self = self else { return }\n            \n            // If no session exists, create new session\n            if self.captureSession == nil {\n                self.setupCaptureSession { success in\n                    if success {\n                        // Only start the session if setup was successful\n                        self.startRunningCaptureSession()\n                    }\n                }\n            } else {\n                // Session already exists, just start it\n                self.startRunningCaptureSession()\n            }\n        }\n    }\n    \n    private func startRunningCaptureSession() {\n        sessionQueue.async { [weak self] in\n            guard let self = self, let session = self.captureSession, !session.isRunning else {\n                return\n            }\n            \n            session.startRunning()\n            \n            // Update state on main thread\n            self.updateSessionState()\n            \n            NSLog(\"Capture session started successfully\")\n        }\n    }\n    \n    func stopSession() {\n        sessionQueue.async { [weak self] in\n            guard let self = self else { return }\n            \n            // Update state to indicate we're stopping\n            DispatchQueue.main.async {\n                self.isSessionRunning = false\n            }\n            \n            self.cleanupExistingSession()\n            \n            NSLog(\"Capture session stopped and cleaned up\")\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/menu/StatusBarMenu.swift",
    "content": "import Cocoa\n\nclass BoringStatusMenu: NSMenu {\n    \n    var statusItem: NSStatusItem!\n    \n    override init() {\n        super.init()\n        \n        // Initialize the status item\n        statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)\n        \n        if let button = statusItem.button {\n            button.image = NSImage(systemSymbolName: \"music.note\", accessibilityDescription: \"BoringNotch\")\n            button.action = #selector(showMenu)\n        }\n        \n        // Set up the menu\n        let menu = NSMenu()\n        menu.addItem(NSMenuItem(title: \"Quit\", action: #selector(quitAction), keyEquivalent: \"q\"))\n        statusItem.menu = menu\n    }\n\n}\n"
  },
  {
    "path": "boringNotch/metal/visualizer.metal",
    "content": "//\n//  visualizer.metal\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 28/08/24.\n//\n\n#include <metal_stdlib>\nusing namespace metal;\n\nvertex float4 vertexShader(uint vertexID [[vertex_id]],\n                           constant float2 *vertices [[buffer(0)]]) {\n    return float4(vertices[vertexID], 0, 1);\n}\n\nfragment float4 fragmentShader(constant float4 &color [[buffer(0)]]) {\n    return color;\n}\n"
  },
  {
    "path": "boringNotch/models/BatteryStatusViewModel.swift",
    "content": "import Cocoa\nimport Defaults\nimport Foundation\nimport IOKit.ps\nimport SwiftUI\n\n/// A view model that manages and monitors the battery status of the device\nclass BatteryStatusViewModel: ObservableObject {\n\n    private var wasCharging: Bool = false\n    private var powerSourceChangedCallback: IOPowerSourceCallbackType?\n    private var runLoopSource: Unmanaged<CFRunLoopSource>?\n\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n\n    @Published private(set) var levelBattery: Float = 0.0\n    @Published private(set) var maxCapacity: Float = 0.0\n    @Published private(set) var isPluggedIn: Bool = false\n    @Published private(set) var isCharging: Bool = false\n    @Published private(set) var isInLowPowerMode: Bool = false\n    @Published private(set) var isInitial: Bool = false\n    @Published private(set) var timeToFullCharge: Int = 0\n    @Published private(set) var statusText: String = \"\"\n\n    private let managerBattery = BatteryActivityManager.shared\n    private var managerBatteryId: Int?\n\n    static let shared = BatteryStatusViewModel()\n\n    /// Initializes the view model with a given BoringViewModel instance\n    /// - Parameter vm: The BoringViewModel instance\n    private init() {\n        setupPowerStatus()\n        setupMonitor()\n    }\n\n    /// Sets up the initial power status by fetching battery information\n    private func setupPowerStatus() {\n        let batteryInfo = managerBattery.initializeBatteryInfo()\n        updateBatteryInfo(batteryInfo)\n    }\n\n    /// Sets up the monitor to observe battery events\n    private func setupMonitor() {\n        managerBatteryId = managerBattery.addObserver { [weak self] event in\n            guard let self = self else { return }\n            self.handleBatteryEvent(event)\n        }\n    }\n\n    /// Handles battery events and updates the corresponding properties\n    /// - Parameter event: The battery event to handle\n    private func handleBatteryEvent(_ event: BatteryActivityManager.BatteryEvent) {\n        switch event {\n        case .powerSourceChanged(let isPluggedIn):\n            print(\"🔌 Power source: \\(isPluggedIn ? \"Connected\" : \"Disconnected\")\")\n            withAnimation {\n                self.isPluggedIn = isPluggedIn\n                self.statusText = isPluggedIn ? \"Plugged In\" : \"Unplugged\"\n                self.notifyImportanChangeStatus()\n            }\n\n        case .batteryLevelChanged(let level):\n            print(\"🔋 Battery level: \\(Int(level))%\")\n            withAnimation {\n                self.levelBattery = level\n            }\n\n        case .lowPowerModeChanged(let isEnabled):\n            print(\"⚡ Low power mode: \\(isEnabled ? \"Enabled\" : \"Disabled\")\")\n            self.notifyImportanChangeStatus()\n            withAnimation {\n                self.isInLowPowerMode = isEnabled\n                self.statusText = \"Low Power: \\(self.isInLowPowerMode ? \"On\" : \"Off\")\"\n            }\n\n        case .isChargingChanged(let isCharging):\n            print(\"🔌 Charging: \\(isCharging ? \"Yes\" : \"No\")\")\n            print(\"maxCapacity: \\(self.maxCapacity)\")\n            print(\"levelBattery: \\(self.levelBattery)\")\n            self.notifyImportanChangeStatus()\n            withAnimation {\n                self.isCharging = isCharging\n                self.statusText =\n                    isCharging\n                    ? \"Charging battery\"\n                    : (self.levelBattery < self.maxCapacity ? \"Not charging\" : \"Full charge\")\n            }\n\n        case .timeToFullChargeChanged(let time):\n            print(\"🕒 Time to full charge: \\(time) minutes\")\n            withAnimation {\n                self.timeToFullCharge = time\n            }\n\n        case .maxCapacityChanged(let capacity):\n            print(\"🔋 Max capacity: \\(capacity)\")\n            withAnimation {\n                self.maxCapacity = capacity\n            }\n\n        case .error(let description):\n            print(\"⚠️ Error: \\(description)\")\n        }\n    }\n\n    /// Updates the battery information with the given BatteryInfo instance\n    /// - Parameter batteryInfo: The BatteryInfo instance containing the battery data\n    private func updateBatteryInfo(_ batteryInfo: BatteryInfo) {\n        withAnimation {\n            self.levelBattery = batteryInfo.currentCapacity\n            self.isPluggedIn = batteryInfo.isPluggedIn\n            self.isCharging = batteryInfo.isCharging\n            self.isInLowPowerMode = batteryInfo.isInLowPowerMode\n            self.timeToFullCharge = batteryInfo.timeToFullCharge\n            self.maxCapacity = batteryInfo.maxCapacity\n            self.statusText = batteryInfo.isPluggedIn ? \"Plugged In\" : \"Unplugged\"\n        }\n    }\n\n    /// Notifies important changes in the battery status with an optional delay\n    /// - Parameter delay: The delay before notifying the change, default is 0.0\n    private func notifyImportanChangeStatus(delay: Double = 0.0) {\n        Task {\n            try? await Task.sleep(for: .seconds(delay))\n            self.coordinator.toggleExpandingView(status: true, type: .battery)\n        }\n    }\n\n    deinit {\n        print(\"🔌 Cleaning up battery monitoring...\")\n        if let managerBatteryId: Int = managerBatteryId {\n            managerBattery.removeObserver(byId: managerBatteryId)\n        }\n    }\n\n}\n"
  },
  {
    "path": "boringNotch/models/BoringViewModel.swift",
    "content": "//\n//  BoringViewModel.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 04/08/24.\n//\n\nimport Combine\nimport Defaults\nimport SwiftUI\n\nclass BoringViewModel: NSObject, ObservableObject {\n    @ObservedObject var coordinator = BoringViewCoordinator.shared\n    @ObservedObject var detector = FullscreenMediaDetector.shared\n\n    let animationLibrary: BoringAnimations = .init()\n    let animation: Animation?\n\n    @Published var contentType: ContentType = .normal\n    @Published private(set) var notchState: NotchState = .closed\n\n    @Published var dragDetectorTargeting: Bool = false\n    @Published var generalDropTargeting: Bool = false\n    @Published var dropZoneTargeting: Bool = false\n    @Published var dropEvent: Bool = false\n    @Published var anyDropZoneTargeting: Bool = false\n    var cancellables: Set<AnyCancellable> = []\n    \n    @Published var hideOnClosed: Bool = true\n\n    @Published var edgeAutoOpenActive: Bool = false\n    @Published var isHoveringCalendar: Bool = false\n    @Published var isBatteryPopoverActive: Bool = false\n\n    @Published var screenUUID: String?\n\n    @Published var notchSize: CGSize = getClosedNotchSize()\n    @Published var closedNotchSize: CGSize = getClosedNotchSize()\n    \n    let webcamManager = WebcamManager.shared\n    @Published var isCameraExpanded: Bool = false\n    @Published var isRequestingAuthorization: Bool = false\n    \n    deinit {\n        destroy()\n    }\n\n    func destroy() {\n        cancellables.forEach { $0.cancel() }\n        cancellables.removeAll()\n    }\n\n    init(screenUUID: String? = nil) {\n        animation = animationLibrary.animation\n\n        super.init()\n        \n        self.screenUUID = screenUUID\n        notchSize = getClosedNotchSize(screenUUID: screenUUID)\n        closedNotchSize = notchSize\n\n        Publishers.CombineLatest3($dropZoneTargeting, $dragDetectorTargeting, $generalDropTargeting)\n            .map { shelf, drag, general in\n                shelf || drag || general\n            }\n            .assign(to: \\.anyDropZoneTargeting, on: self)\n            .store(in: &cancellables)\n        \n        setupDetectorObserver()\n    }\n    \n    private func setupDetectorObserver() {\n        // Publisher for the user’s fullscreen detection setting\n        let enabledPublisher = Defaults\n            .publisher(.hideNotchOption)\n            .map(\\.newValue)\n            .map { $0 != .never }\n            .removeDuplicates()\n\n        // Publisher for the current screen UUID (non-nil, distinct)\n        let screenPublisher = $screenUUID\n            .compactMap { $0 }\n            .removeDuplicates()\n\n        // Publisher for fullscreen status dictionary\n        let fullscreenStatusPublisher = detector.$fullscreenStatus\n            .removeDuplicates()\n\n        // Combine all three: screen UUID, fullscreen status, and enabled setting\n        Publishers.CombineLatest3(screenPublisher, fullscreenStatusPublisher, enabledPublisher)\n            .map { screenUUID, fullscreenStatus, enabled in\n                let isFullscreen = fullscreenStatus[screenUUID] ?? false\n                return enabled && isFullscreen\n            }\n            .removeDuplicates()\n            .receive(on: RunLoop.main)\n            .sink { [weak self] shouldHide in\n                withAnimation(.smooth) {\n                    self?.hideOnClosed = shouldHide\n                }\n            }\n            .store(in: &cancellables)\n    }\n\n    // Computed property for effective notch height\n    var effectiveClosedNotchHeight: CGFloat {\n        let currentScreen = screenUUID.flatMap { NSScreen.screen(withUUID: $0) }\n        let noNotchAndFullscreen = hideOnClosed && (currentScreen?.safeAreaInsets.top ?? 0 <= 0 || currentScreen == nil)\n        return noNotchAndFullscreen ? 0 : closedNotchSize.height\n    }\n\n    var chinHeight: CGFloat {\n        if !Defaults[.hideTitleBar] {\n            return 0\n        }\n\n        guard let currentScreen = screenUUID.flatMap({ NSScreen.screen(withUUID: $0) }) else {\n            return 0\n        }\n\n        if notchState == .open { return 0 }\n\n        let menuBarHeight = currentScreen.frame.maxY - currentScreen.visibleFrame.maxY\n        let currentHeight = effectiveClosedNotchHeight\n\n        if currentHeight == 0 { return 0 }\n\n        return max(0, menuBarHeight - currentHeight)\n    }\n\n    func toggleCameraPreview() {\n        if isRequestingAuthorization {\n            return\n        }\n\n        switch webcamManager.authorizationStatus {\n        case .authorized:\n            if webcamManager.isSessionRunning {\n                webcamManager.stopSession()\n                isCameraExpanded = false\n            } else if webcamManager.cameraAvailable {\n                webcamManager.startSession()\n                isCameraExpanded = true\n            }\n\n        case .denied, .restricted:\n            DispatchQueue.main.async {\n                NSApp.setActivationPolicy(.regular)\n                NSApp.activate(ignoringOtherApps: true)\n\n                let alert = NSAlert()\n                alert.messageText = \"Camera Access Required\"\n                alert.informativeText = \"Please allow camera access in System Settings.\"\n                alert.addButton(withTitle: \"Open Settings\")\n                alert.addButton(withTitle: \"Cancel\")\n\n                if alert.runModal() == .alertFirstButtonReturn {\n                    if let url = URL(string: \"x-apple.systempreferences:com.apple.preference.security?Privacy_Camera\") {\n                        NSWorkspace.shared.open(url)\n                    }\n                }\n\n                NSApp.setActivationPolicy(.accessory)\n                NSApp.deactivate()\n            }\n\n        case .notDetermined:\n            isRequestingAuthorization = true\n            webcamManager.checkAndRequestVideoAuthorization()\n            DispatchQueue.main.asyncAfter(deadline: .now() + 2) {\n                self.isRequestingAuthorization = false\n            }\n\n        default:\n            break\n        }\n    }\n    \n    func isMouseHovering(position: NSPoint = NSEvent.mouseLocation) -> Bool {\n        let screenFrame = getScreenFrame(screenUUID)\n        if let frame = screenFrame {\n            \n            let baseY = frame.maxY - notchSize.height\n            let baseX = frame.midX - notchSize.width / 2\n            \n            return position.y >= baseY && position.x >= baseX && position.x <= baseX + notchSize.width\n        }\n        \n        return false\n    }\n\n    func open() {\n        self.notchSize = openNotchSize\n        self.notchState = .open\n        \n        // Force music information update when notch is opened\n        MusicManager.shared.forceUpdate()\n    }\n\n    func close() {\n        // Do not close while a share picker or sharing service is active\n        if SharingStateManager.shared.preventNotchClose {\n            return\n        }\n        self.notchSize = getClosedNotchSize(screenUUID: self.screenUUID)\n        self.closedNotchSize = self.notchSize\n        self.notchState = .closed\n        self.isBatteryPopoverActive = false\n        self.coordinator.sneakPeek.show = false\n        self.edgeAutoOpenActive = false\n\n        // Set the current view to shelf if it contains files and the user enables openShelfByDefault\n        // Otherwise, if the user has not enabled openLastShelfByDefault, set the view to home\n    if !ShelfStateViewModel.shared.isEmpty && Defaults[.openShelfByDefault] {\n            coordinator.currentView = .shelf\n        } else if !coordinator.openLastTabByDefault {\n            coordinator.currentView = .home\n        }\n    }\n\n    func closeHello() {\n        Task { @MainActor in\n            withAnimation(animationLibrary.animation) {\n                coordinator.helloAnimationRunning = false\n                close()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "boringNotch/models/CalendarModel.swift",
    "content": "//\n//  CalendarModel.swift\n//  Calendr\n//\n//  Created by Paker on 31/12/20.\n//  Original source: https://github.com/pakerwreah/Calendr\n//\n\nimport Cocoa\n\nstruct CalendarModel: Equatable {\n    let id: String\n    let account: String\n    let title: String\n    let color: NSColor\n    let isSubscribed: Bool\n    let isReminder: Bool // true if this is a reminder calendar\n}\n"
  },
  {
    "path": "boringNotch/models/Constants.swift",
    "content": "//\n//  Constants.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 2024. 10. 17..\n//\n\nimport SwiftUI\nimport Defaults\n\nprivate let availableDirectories = FileManager\n    .default\n    .urls(for: .documentDirectory, in: .userDomainMask)\nlet documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!\nlet bundleIdentifier = Bundle.main.bundleIdentifier!\nlet appVersion = \"\\(Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"] as? String ?? \"\") (\\(Bundle.main.infoDictionary?[\"CFBundleVersion\"] as? String ?? \"\"))\"\n\nlet temporaryDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask).first!\nlet spacing: CGFloat = 16\n\nstruct CustomVisualizer: Codable, Hashable, Equatable, Defaults.Serializable {\n    let UUID: UUID\n    var name: String\n    var url: URL\n    var speed: CGFloat = 1.0\n}\n\nenum CalendarSelectionState: Codable, Defaults.Serializable {\n    case all\n    case selected(Set<String>)\n}\n\nenum HideNotchOption: String, Defaults.Serializable {\n    case always\n    case nowPlayingOnly\n    case never\n}\n\n// Define notification names at file scope\nextension Notification.Name {\n    static let mediaControllerChanged = Notification.Name(\"mediaControllerChanged\")\n}\n\n// Media controller types for selection in settings\nenum MediaControllerType: String, CaseIterable, Identifiable, Defaults.Serializable {\n    case nowPlaying = \"Now Playing\"\n    case appleMusic = \"Apple Music\"\n    case spotify = \"Spotify\"\n    case youtubeMusic = \"YouTube Music\"\n    \n    var id: String { self.rawValue }\n}\n\n// Sneak peek styles for selection in settings\nenum SneakPeekStyle: String, CaseIterable, Identifiable, Defaults.Serializable {\n    case standard = \"Default\"\n    case inline = \"Inline\"\n    \n    var id: String { self.rawValue }\n}\n\n// Action to perform when Option (⌥) is held while pressing media keys\nenum OptionKeyAction: String, CaseIterable, Identifiable, Defaults.Serializable {\n    case openSettings = \"Open System Settings\"\n    case showHUD = \"Show HUD\"\n    case none = \"No Action\"\n\n    var id: String { self.rawValue }\n}\n\nextension Defaults.Keys {\n    // MARK: General\n    static let menubarIcon = Key<Bool>(\"menubarIcon\", default: true)\n    static let showOnAllDisplays = Key<Bool>(\"showOnAllDisplays\", default: false)\n    static let automaticallySwitchDisplay = Key<Bool>(\"automaticallySwitchDisplay\", default: true)\n    static let releaseName = Key<String>(\"releaseName\", default: \"Flying Rabbit 🐇🪽\")\n    \n    // MARK: Behavior\n    static let minimumHoverDuration = Key<TimeInterval>(\"minimumHoverDuration\", default: 0.3)\n    static let enableHaptics = Key<Bool>(\"enableHaptics\", default: true)\n    static let openNotchOnHover = Key<Bool>(\"openNotchOnHover\", default: true)\n    static let extendHoverArea = Key<Bool>(\"extendHoverArea\", default: false)\n    static let notchHeightMode = Key<WindowHeightMode>(\n        \"notchHeightMode\",\n        default: WindowHeightMode.matchRealNotchSize\n    )\n    static let nonNotchHeightMode = Key<WindowHeightMode>(\n        \"nonNotchHeightMode\",\n        default: WindowHeightMode.matchMenuBar\n    )\n    static let nonNotchHeight = Key<CGFloat>(\"nonNotchHeight\", default: 32)\n    static let notchHeight = Key<CGFloat>(\"notchHeight\", default: 32)\n    //static let openLastTabByDefault = Key<Bool>(\"openLastTabByDefault\", default: false)\n    static let showOnLockScreen = Key<Bool>(\"showOnLockScreen\", default: false)\n    static let hideFromScreenRecording = Key<Bool>(\"hideFromScreenRecording\", default: false)\n    \n    // MARK: Appearance\n    static let showEmojis = Key<Bool>(\"showEmojis\", default: false)\n    //static let alwaysShowTabs = Key<Bool>(\"alwaysShowTabs\", default: true)\n    static let showMirror = Key<Bool>(\"showMirror\", default: false)\n    static let mirrorShape = Key<MirrorShapeEnum>(\"mirrorShape\", default: MirrorShapeEnum.rectangle)\n    static let settingsIconInNotch = Key<Bool>(\"settingsIconInNotch\", default: true)\n    static let lightingEffect = Key<Bool>(\"lightingEffect\", default: true)\n    static let enableShadow = Key<Bool>(\"enableShadow\", default: true)\n    static let cornerRadiusScaling = Key<Bool>(\"cornerRadiusScaling\", default: true)\n\n    static let showNotHumanFace = Key<Bool>(\"showNotHumanFace\", default: false)\n    static let tileShowLabels = Key<Bool>(\"tileShowLabels\", default: false)\n    static let showCalendar = Key<Bool>(\"showCalendar\", default: false)\n    static let hideCompletedReminders = Key<Bool>(\"hideCompletedReminders\", default: true)\n    static let sliderColor = Key<SliderColorEnum>(\n        \"sliderUseAlbumArtColor\",\n        default: SliderColorEnum.white\n    )\n    static let playerColorTinting = Key<Bool>(\"playerColorTinting\", default: true)\n    static let useMusicVisualizer = Key<Bool>(\"useMusicVisualizer\", default: true)\n    static let customVisualizers = Key<[CustomVisualizer]>(\"customVisualizers\", default: [])\n    static let selectedVisualizer = Key<CustomVisualizer?>(\"selectedVisualizer\", default: nil)\n    \n    // MARK: Gestures\n    static let enableGestures = Key<Bool>(\"enableGestures\", default: true)\n    static let closeGestureEnabled = Key<Bool>(\"closeGestureEnabled\", default: true)\n    static let gestureSensitivity = Key<CGFloat>(\"gestureSensitivity\", default: 200.0)\n    \n    // MARK: Media playback\n    static let coloredSpectrogram = Key<Bool>(\"coloredSpectrogram\", default: true)\n    static let enableSneakPeek = Key<Bool>(\"enableSneakPeek\", default: false)\n    static let sneakPeekStyles = Key<SneakPeekStyle>(\"sneakPeekStyles\", default: .standard)\n    static let waitInterval = Key<Double>(\"waitInterval\", default: 3)\n    static let showShuffleAndRepeat = Key<Bool>(\"showShuffleAndRepeat\", default: false)\n    static let enableLyrics = Key<Bool>(\"enableLyrics\", default: false)\n    static let musicControlSlots = Key<[MusicControlButton]>(\n        \"musicControlSlots\",\n        default: MusicControlButton.defaultLayout\n    )\n    static let musicControlSlotLimit = Key<Int>(\n        \"musicControlSlotLimit\",\n        default: MusicControlButton.defaultLayout.count\n    )\n    \n    // MARK: Battery\n    static let showPowerStatusNotifications = Key<Bool>(\"showPowerStatusNotifications\", default: true)\n    static let showBatteryIndicator = Key<Bool>(\"showBatteryIndicator\", default: true)\n    static let showBatteryPercentage = Key<Bool>(\"showBatteryPercentage\", default: true)\n    static let showPowerStatusIcons = Key<Bool>(\"showPowerStatusIcons\", default: true)\n    \n    // MARK: Downloads\n    static let enableDownloadListener = Key<Bool>(\"enableDownloadListener\", default: true)\n    static let enableSafariDownloads = Key<Bool>(\"enableSafariDownloads\", default: true)\n    static let selectedDownloadIndicatorStyle = Key<DownloadIndicatorStyle>(\"selectedDownloadIndicatorStyle\", default: DownloadIndicatorStyle.progress)\n    static let selectedDownloadIconStyle = Key<DownloadIconStyle>(\"selectedDownloadIconStyle\", default: DownloadIconStyle.onlyAppIcon)\n    \n    // MARK: HUD\n    static let hudReplacement = Key<Bool>(\"hudReplacement\", default: false)\n    static let inlineHUD = Key<Bool>(\"inlineHUD\", default: false)\n    static let enableGradient = Key<Bool>(\"enableGradient\", default: false)\n    static let systemEventIndicatorShadow = Key<Bool>(\"systemEventIndicatorShadow\", default: false)\n    static let systemEventIndicatorUseAccent = Key<Bool>(\"systemEventIndicatorUseAccent\", default: false)\n    static let showOpenNotchHUD = Key<Bool>(\"showOpenNotchHUD\", default: true)\n    static let showOpenNotchHUDPercentage = Key<Bool>(\"showOpenNotchHUDPercentage\", default: true)\n    static let showClosedNotchHUDPercentage = Key<Bool>(\"showClosedNotchHUDPercentage\", default: false)\n    // Option key modifier behaviour for media keys\n    static let optionKeyAction = Key<OptionKeyAction>(\"optionKeyAction\", default: OptionKeyAction.openSettings)\n    \n    // MARK: Shelf\n    static let boringShelf = Key<Bool>(\"boringShelf\", default: true)\n    static let openShelfByDefault = Key<Bool>(\"openShelfByDefault\", default: true)\n    static let shelfTapToOpen = Key<Bool>(\"shelfTapToOpen\", default: true)\n    static let quickShareProvider = Key<String>(\"quickShareProvider\", default: QuickShareProvider.defaultProvider.id)\n    static let copyOnDrag = Key<Bool>(\"copyOnDrag\", default: false)\n    static let autoRemoveShelfItems = Key<Bool>(\"autoRemoveShelfItems\", default: false)\n    static let expandedDragDetection = Key<Bool>(\"expandedDragDetection\", default: true)\n    \n    // MARK: Calendar\n    static let calendarSelectionState = Key<CalendarSelectionState>(\"calendarSelectionState\", default: .all)\n    static let hideAllDayEvents = Key<Bool>(\"hideAllDayEvents\", default: false)\n    static let showFullEventTitles = Key<Bool>(\"showFullEventTitles\", default: false)\n    static let autoScrollToNextEvent = Key<Bool>(\"autoScrollToNextEvent\", default: true)\n    \n    // MARK: Fullscreen Media Detection\n    static let hideNotchOption = Key<HideNotchOption>(\"hideNotchOption\", default: .nowPlayingOnly)\n    \n    // MARK: Media Controller\n    static let mediaController = Key<MediaControllerType>(\"mediaController\", default: defaultMediaController)\n    \n    // MARK: Advanced Settings\n    static let useCustomAccentColor = Key<Bool>(\"useCustomAccentColor\", default: false)\n    static let customAccentColorData = Key<Data?>(\"customAccentColorData\", default: nil)\n    // Show or hide the title bar\n    static let hideTitleBar = Key<Bool>(\"hideTitleBar\", default: true)\n    \n    // Helper to determine the default media controller based on NowPlaying deprecation status\n    static var defaultMediaController: MediaControllerType {\n        if MusicManager.shared.isNowPlayingDeprecated {\n            return .appleMusic\n        } else {\n            return .nowPlaying\n        }\n    }\n\n    static let didClearLegacyURLCacheV1 = Key<Bool>(\"didClearLegacyURLCache_v1\", default: false)\n}\n"
  },
  {
    "path": "boringNotch/models/EventModel.swift",
    "content": "//\n//  EventModel.swift\n//  Calendr\n//\n//  Created by Paker on 24/12/20.\n//  Original source: https://github.com/pakerwreah/Calendr\n//  Modified by Alexander on 2025-05-18.\n//\n\nimport Foundation\n\nstruct EventModel: Equatable, Identifiable {\n    let id: String\n    let start: Date\n    let end: Date\n    let title: String\n    let location: String?\n    let notes: String?\n    let url: URL?\n    let isAllDay: Bool\n    let type: EventType\n    let calendar: CalendarModel\n    let participants: [Participant]\n    let timeZone: TimeZone?\n    let hasRecurrenceRules: Bool\n    let priority: Priority?\n}\n\nenum AttendanceStatus: Comparable {\n    case accepted\n    case maybe\n    case pending\n    case declined\n    case unknown\n\n    private var comparisonValue: Int {\n        switch self {\n        case .accepted: return 1\n        case .maybe: return 2\n        case .declined: return 3\n        case .pending: return 4\n        case .unknown: return 5\n        }\n    }\n\n    static func < (lhs: Self, rhs: Self) -> Bool {\n        return lhs.comparisonValue < rhs.comparisonValue\n    }\n}\n\nenum EventType: Equatable {\n    case event(AttendanceStatus)\n    case birthday\n    case reminder(completed: Bool)\n}\n\nenum EventStatus: Equatable {\n    case upcoming\n    case inProgress\n    case ended\n}\n\nextension EventType {\n    var isEvent: Bool { if case .event = self { return true } else { return false } }\n    var isBirthday: Bool { self ~= .birthday }\n    var isReminder: Bool { if case .reminder = self { return true } else { return false } }\n}\n\nextension EventModel {\n    \n    var eventStatus: EventStatus {\n        if start > Date() {\n            return .upcoming\n        } else if end > Date() {\n            return .inProgress\n        } else {\n            return .ended\n        }\n    }\n        \n    var attendance: AttendanceStatus { if case .event(let attendance) = type { return attendance } else { return .unknown } }\n\n    var isMeeting: Bool { !participants.isEmpty }\n\n    func calendarAppURL() -> URL? {\n\n        guard let id = id.addingPercentEncoding(withAllowedCharacters: .urlPathAllowed) else {\n            return nil\n        }\n\n        guard !type.isReminder else {\n            return URL(string: \"x-apple-reminderkit://remcdreminder/\\(id)\")\n        }\n\n        let date: String\n        if hasRecurrenceRules {\n            let formatter = DateFormatter()\n            formatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ssZ\"\n            if !isAllDay {\n                formatter.timeZone = .init(secondsFromGMT: 0)\n            }\n            if let formattedDate = formatter.string(for: start) {\n                date = \"/\\(formattedDate)\"\n            } else {\n                return nil\n            }\n        } else {\n            date =  \"\"\n        }\n        return URL(string: \"ical://ekevent\\(date)/\\(id)?method=show&options=more\")\n    }\n}\n\nstruct Participant: Hashable {\n    let name: String\n    let status: AttendanceStatus\n    let isOrganizer: Bool\n    let isCurrentUser: Bool\n}\n\nenum Priority {\n    case high\n    case medium\n    case low\n}\n"
  },
  {
    "path": "boringNotch/models/MusicControlButton.swift",
    "content": "//\n//  MusicControlButton.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-11-16.\n//\n\nimport Defaults\n\nenum MusicControlButton: String, CaseIterable, Identifiable, Codable, Defaults.Serializable {\n    case shuffle\n    case previous\n    case playPause\n    case next\n    case repeatMode\n    case volume\n    case favorite\n    case goBackward\n    case goForward\n    case none\n\n    var id: String { rawValue }\n\n    static let defaultLayout: [MusicControlButton] = [\n        .none,\n        .previous,\n        .playPause,\n        .next,\n        .none\n    ]\n\n    static let minSlotCount: Int = 3\n    static let maxSlotCount: Int = 5\n\n    static let pickerOptions: [MusicControlButton] = [\n        .shuffle,\n        .previous,\n        .playPause,\n        .next,\n        .repeatMode,\n        .favorite,\n        .volume,\n        .goBackward,\n        .goForward\n    ]\n\n    var label: String {\n        switch self {\n        case .shuffle:\n            return \"Shuffle\"\n        case .previous:\n            return \"Previous\"\n        case .playPause:\n            return \"Play/Pause\"\n        case .next:\n            return \"Next\"\n        case .repeatMode:\n            return \"Repeat\"\n        case .volume:\n            return \"Volume\"\n        case .favorite:\n            return \"Favorite\"\n        case .goBackward:\n            return \"Backward 15s\"\n        case .goForward:\n            return \"Forward 15s\"\n        case .none:\n            return \"Empty slot\"\n        }\n    }\n\n    var iconName: String {\n        switch self {\n        case .shuffle:\n            return \"shuffle\"\n        case .previous:\n            return \"backward.fill\"\n        case .playPause:\n            return \"playpause\"\n        case .next:\n            return \"forward.fill\"\n        case .repeatMode:\n            return \"repeat\"\n        case .volume:\n            return \"speaker.wave.2.fill\"\n        case .favorite:\n            return \"heart\"\n        case .goBackward:\n            return \"gobackward.15\"\n        case .goForward:\n            return \"goforward.15\"\n        case .none:\n            return \"\"\n        }\n    }\n\n    var prefersLargeScale: Bool {\n        self == .playPause\n    }\n}\n"
  },
  {
    "path": "boringNotch/models/PlaybackState.swift",
    "content": "//\n//  PlaybackState.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-03-29.\n//\n\nimport Foundation\n\nenum RepeatMode: Int, Codable {\n    case off = 1\n    case one = 2\n    case all = 3\n}\n\nstruct PlaybackState {\n    var bundleIdentifier: String\n    var isPlaying: Bool = false\n    var title: String = \"I'm Handsome\"\n    var artist: String = \"Me\"\n    var album: String = \"Self Love\"\n    var currentTime: Double = 0\n    var duration: Double = 0\n    var playbackRate: Double = 1\n    var isShuffled: Bool = false\n    var repeatMode: RepeatMode = .off\n    var lastUpdated: Date = Date.distantPast\n    var artwork: Data?\n    var volume: Double = 0.5\n    var isFavorite: Bool = false\n}\n\nextension PlaybackState: Equatable {\n    static func == (lhs: PlaybackState, rhs: PlaybackState) -> Bool {\n        return lhs.bundleIdentifier == rhs.bundleIdentifier\n            && lhs.isPlaying == rhs.isPlaying\n            && lhs.title == rhs.title\n            && lhs.artist == rhs.artist\n            && lhs.album == rhs.album\n            && lhs.currentTime == rhs.currentTime\n            && lhs.duration == rhs.duration\n            && lhs.isShuffled == rhs.isShuffled\n            && lhs.repeatMode == rhs.repeatMode\n            && lhs.artwork == rhs.artwork\n            && lhs.isFavorite == rhs.isFavorite\n    }\n}\n"
  },
  {
    "path": "boringNotch/models/SharingStateManager.swift",
    "content": "//\n//  SharingStateManager.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-10-10.\n//\n\nimport AppKit\nimport Combine\nimport Foundation\n\nextension Notification.Name {\n\tstatic let sharingDidFinish = Notification.Name(\"com.boringNotch.sharingDidFinish\")\n}\n\n@MainActor\nfinal class SharingStateManager: ObservableObject {\n\tstatic let shared = SharingStateManager()\n\n\tprivate var activeSessions: Int = 0 {\n\t\tdidSet {\n\t\t\tlet newValue = activeSessions > 0\n\t\t\tif newValue != preventNotchClose {\n\t\t\t\tpreventNotchClose = newValue\n\t\t\t\tif !newValue {\n\t\t\t\t\tNotificationCenter.default.post(name: .sharingDidFinish, object: nil)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Published var preventNotchClose: Bool = false\n\n\tprivate var activeDelegates: [UUID: SharingLifecycleDelegate] = [:]\n\n\tprivate init() {}\n\t\n\tfunc requestCloseIfReady() {\n\t\tif !preventNotchClose {\n\t\t\tNotificationCenter.default.post(name: .sharingDidFinish, object: nil)\n\t\t}\n\t}\n\n\tfunc beginInteraction() {\n\t\tactiveSessions += 1\n\t}\n\n\tfunc endInteraction() {\n\t\tif activeSessions > 0 { activeSessions -= 1 }\n\t}\n\n\tfunc makeDelegate(onEnd: (() -> Void)? = nil) -> SharingLifecycleDelegate {\n\t\tlet id = UUID()\n\t\tlet delegate = SharingLifecycleDelegate(id: id, onEnd: { [weak self] in\n\t\t\tonEnd?()\n\t\t\tself?.unregisterDelegate(id: id)\n\t\t}, onBegin: { [weak self] in\n\t\t\tself?.beginInteraction()\n\t\t}, onFinish: { [weak self] in\n\t\t\tself?.endInteraction()\n\t\t})\n\t\tactiveDelegates[id] = delegate\n\t\treturn delegate\n\t}\n\n\tprivate func unregisterDelegate(id: UUID) {\n\t\tactiveDelegates.removeValue(forKey: id)\n\t}\n}\n\nfinal class SharingLifecycleDelegate: NSObject, NSSharingServiceDelegate, NSSharingServicePickerDelegate {\n\tlet id: UUID\n\tprivate let onEnd: () -> Void\n\tprivate let onBegin: () -> Void\n\tprivate let onFinish: () -> Void\n\n\tprivate var pickerActive = false\n\tprivate var serviceInProgress = false\n\tprivate var finished = false\n\tprivate var timeoutTask: Task<Void, Never>?\n\n\tinit(id: UUID, onEnd: @escaping () -> Void, onBegin: @escaping () -> Void, onFinish: @escaping () -> Void) {\n\t\tself.id = id\n\t\tself.onEnd = onEnd\n\t\tself.onBegin = onBegin\n\t\tself.onFinish = onFinish\n\t}\n\t\n\tdeinit {\n\t\ttimeoutTask?.cancel()\n\t}\n\n\tfunc markPickerBegan() {\n\t\tguard !pickerActive else { return }\n\t\tpickerActive = true\n\t\tonBegin()\n\t}\n\n\tfunc markServiceBegan() {\n\t\tguard !serviceInProgress else { return }\n\t\tserviceInProgress = true\n\t\tonBegin()\n\t\tstartTimeoutFallback()\n\t}\n\t\n\tprivate func startTimeoutFallback() {\n\t\ttimeoutTask?.cancel()\n\t\ttimeoutTask = Task { @MainActor [weak self] in\n\t\t\ttry? await Task.sleep(for: .seconds(2))\n\t\t\tguard let self = self, !Task.isCancelled else { return }\n\t\t\tif !self.finished {\n\t\t\t\tself.finishIfNeeded()\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate func finishIfNeeded() {\n\t\tguard !finished else { return }\n\t\tfinished = true\n\t\ttimeoutTask?.cancel()\n\t\tonFinish()\n\t\tonEnd()\n\t}\n\n\t// MARK: - NSSharingServicePickerDelegate\n\n\tfunc sharingServicePicker(_ sharingServicePicker: NSSharingServicePicker, didChoose service: NSSharingService?) {\n\t\tif service == nil {\n\t\t\tif pickerActive && !serviceInProgress {\n\t\t\t\tfinishIfNeeded()\n\t\t\t}\n\t\t\treturn\n\t\t}\n\n\t\tservice?.delegate = self\n\t\tserviceInProgress = true\n\t\tstartTimeoutFallback()\n\t}\n\n\t// MARK: - NSSharingServiceDelegate\n\n\tfunc sharingService(_ sharingService: NSSharingService, willShareItems items: [Any]) {\n\t\tif !pickerActive && !serviceInProgress {\n\t\t\tonBegin()\n\t\t}\n\t\tserviceInProgress = true\n\t}\n\n\tfunc sharingService(_ sharingService: NSSharingService, didShareItems items: [Any]) {\n\t\tfinishIfNeeded()\n\t}\n\n\tfunc sharingService(_ sharingService: NSSharingService, didFailToShareItems items: [Any], error: Error) {\n\t\tfinishIfNeeded()\n\t}\n}\n\n"
  },
  {
    "path": "boringNotch/observers/DragDetector.swift",
    "content": "//\n//  DragDetector.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-11-20.\n//\n\nimport Cocoa\nimport UniformTypeIdentifiers\n\nfinal class DragDetector {\n\n    // MARK: - Callbacks\n\n    typealias VoidCallback = () -> Void\n    typealias PositionCallback = (_ globalPoint: CGPoint) -> Void\n\n    var onDragEntersNotchRegion: VoidCallback?\n    var onDragExitsNotchRegion: VoidCallback?\n    var onDragMove: PositionCallback?\n\n\n    private var mouseDownMonitor: Any?\n    private var mouseDraggedMonitor: Any?\n    private var mouseUpMonitor: Any?\n\n    private var pasteboardChangeCount: Int = -1\n    private var isDragging: Bool = false\n    private var isContentDragging: Bool = false\n    private var hasEnteredNotchRegion: Bool = false\n\n    private let notchRegion: CGRect\n    private let dragPasteboard = NSPasteboard(name: .drag)\n\n    init(notchRegion: CGRect) {\n        self.notchRegion = notchRegion\n    }\n\n    // MARK: - Private Helpers\n    \n    /// Checks if the drag pasteboard contains valid content types that can be dropped on the shelf\n    private func hasValidDragContent() -> Bool {\n        let validTypes: [NSPasteboard.PasteboardType] = [\n            .fileURL,\n            NSPasteboard.PasteboardType(UTType.url.identifier),\n            .string\n        ]\n        return dragPasteboard.types?.contains(where: validTypes.contains) ?? false\n    }\n\n    func startMonitoring() {\n        stopMonitoring()\n\n        // Track pasteboard to detect content drag\n        mouseDownMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDown]) { [weak self] _ in\n            guard let self = self else { return }\n            self.pasteboardChangeCount = self.dragPasteboard.changeCount\n            self.isDragging = true\n            self.isContentDragging = false\n            self.hasEnteredNotchRegion = false\n        }\n\n        // Track drag movement and notch region intersection\n        mouseDraggedMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseDragged]) { [weak self] event in\n            guard let self = self else { return }\n            guard self.isDragging else { return }\n\n            let newContent = self.dragPasteboard.changeCount != self.pasteboardChangeCount\n            \n            // Detect if actual content is being dragged AND it's valid content\n            if newContent && !self.isContentDragging && self.hasValidDragContent() {\n                self.isContentDragging = true\n            }\n\n            // Only process position when content is being dragged\n            if self.isContentDragging {\n                let mouseLocation = NSEvent.mouseLocation\n                self.onDragMove?(mouseLocation)\n                \n                // Track notch region entry/exit\n                let containsMouse = self.notchRegion.contains(mouseLocation)\n                if containsMouse && !self.hasEnteredNotchRegion {\n                    self.hasEnteredNotchRegion = true\n                    self.onDragEntersNotchRegion?()\n                } else if !containsMouse && self.hasEnteredNotchRegion {\n                    self.hasEnteredNotchRegion = false\n                    self.onDragExitsNotchRegion?()\n                }\n            }\n        }\n\n        mouseUpMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.leftMouseUp]) { [weak self] _ in\n            guard let self = self else { return }\n            guard self.isDragging else { return }\n            \n            self.isDragging = false\n            self.isContentDragging = false\n            self.hasEnteredNotchRegion = false\n            self.pasteboardChangeCount = -1\n        }\n    }\n\n    func stopMonitoring() {\n        [mouseDownMonitor, mouseDraggedMonitor, mouseUpMonitor].forEach { monitor in\n            if let monitor = monitor {\n                NSEvent.removeMonitor(monitor)\n            }\n        }\n        mouseDownMonitor = nil\n        mouseDraggedMonitor = nil\n        mouseUpMonitor = nil\n        isDragging = false\n        isContentDragging = false\n        hasEnteredNotchRegion = false\n    }\n\n    deinit {\n        stopMonitoring()\n    }\n}\n"
  },
  {
    "path": "boringNotch/observers/FullscreenMediaDetection.swift",
    "content": "//\n//  FullscreenMediaDetection.swift\n//  boringNotch\n//\n//  Created by Richard Kunkli on 06/09/2024.\n//\n\nimport Foundation\nimport Combine\nimport Defaults\nimport MacroVisionKit\n\n@MainActor\nfinal class FullscreenMediaDetector: ObservableObject {\n    static let shared = FullscreenMediaDetector()\n    \n    @Published var fullscreenStatus: [String: Bool] = [:]\n    \n    private var monitorTask: Task<Void, Never>?\n    \n    private init() {\n        startMonitoring()\n    }\n    \n    deinit {\n        monitorTask?.cancel()\n    }\n    \n    private func startMonitoring() {\n        monitorTask = Task { @MainActor in\n            let stream = await FullScreenMonitor.shared.spaceChanges()\n            for await spaces in stream {\n                updateStatus(with: spaces)\n            }\n        }\n    }\n    \n    private func updateStatus(with spaces: [MacroVisionKit.FullScreenMonitor.SpaceInfo]) {\n        var newStatus: [String: Bool] = [:]\n        \n        for space in spaces {\n            if let uuid = space.screenUUID {\n                let shouldDetect: Bool\n                if Defaults[.hideNotchOption] == .nowPlayingOnly, let musicSourceBundle = MusicManager.shared.bundleIdentifier  {\n                    shouldDetect = space.runningApps.contains(musicSourceBundle)\n                } else {\n                    shouldDetect = true\n                }\n                newStatus[uuid] = shouldDetect\n            }\n        }\n        \n        self.fullscreenStatus = newStatus\n    }\n}\n\n"
  },
  {
    "path": "boringNotch/observers/MediaKeyInterceptor.swift",
    "content": "//\n//  MediaKeyInterceptor.swift\n//  boringNotch\n//\n//  Created by Alexander on 2025-11-23.\n\nimport Foundation\nimport AppKit\nimport ApplicationServices\nimport Defaults\nimport AVFoundation\n\nprivate let kSystemDefinedEventType = CGEventType(rawValue: 14)!\n\nfinal class MediaKeyInterceptor {\n    static let shared = MediaKeyInterceptor()\n    \n    private enum NXKeyType: Int {\n        case soundUp = 0\n        case soundDown = 1\n        case brightnessUp = 2\n        case brightnessDown = 3\n        case mute = 7\n        case keyboardBrightnessUp = 21\n        case keyboardBrightnessDown = 22\n    }\n    \n    private var eventTap: CFMachPort?\n    private var runLoopSource: CFRunLoopSource?\n    private let step: Float = 1.0 / 16.0\n    private var audioPlayer: AVAudioPlayer?\n    \n    private init() {}\n    \n    // MARK: - Accessibility (via XPC)\n    \n    func requestAccessibilityAuthorization() {\n        XPCHelperClient.shared.requestAccessibilityAuthorization()\n    }\n    \n    func ensureAccessibilityAuthorization(promptIfNeeded: Bool = false) async -> Bool {\n        await XPCHelperClient.shared.ensureAccessibilityAuthorization(promptIfNeeded: promptIfNeeded)\n    }\n    \n    // MARK: - Event Tap\n    \n    func start(promptIfNeeded: Bool = false) async {\n        guard eventTap == nil else { return }\n        \n        // Ensure HUD replacement is enabled\n        guard Defaults[.hudReplacement] else {\n            stop()\n            return\n        }\n        \n        // Check accessibility authorization\n        let authorized = await XPCHelperClient.shared.isAccessibilityAuthorized()\n        if !authorized {\n            if promptIfNeeded {\n                let granted = await ensureAccessibilityAuthorization(promptIfNeeded: true)\n                guard granted else { return }\n            } else {\n                return\n            }\n        }\n        \n        let mask = CGEventMask(1 << kSystemDefinedEventType.rawValue)\n        eventTap = CGEvent.tapCreate(\n            tap: .cghidEventTap,\n            place: .headInsertEventTap,\n            options: .defaultTap,\n            eventsOfInterest: mask,\n            callback: { _, _, cgEvent, userInfo in\n                guard let userInfo else { return Unmanaged.passRetained(cgEvent) }\n                let interceptor = Unmanaged<MediaKeyInterceptor>.fromOpaque(userInfo).takeUnretainedValue()\n                return interceptor.handleEvent(cgEvent)\n            },\n            userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())\n        )\n        \n        if let eventTap {\n            runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0)\n            if let runLoopSource {\n                CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)\n            }\n            CGEvent.tapEnable(tap: eventTap, enable: true)\n        }\n    }\n    \n    func stop() {\n        if let eventTap {\n            CGEvent.tapEnable(tap: eventTap, enable: false)\n        }\n        if let runLoopSource {\n            CFRunLoopRemoveSource(CFRunLoopGetMain(), runLoopSource, .commonModes)\n        }\n        runLoopSource = nil\n        eventTap = nil\n    }\n    \n    // MARK: - Event Handling\n    \n    private func handleEvent(_ cgEvent: CGEvent) -> Unmanaged<CGEvent>? {\n        // Ensure the CGEvent has a valid type before converting to NSEvent\n        guard cgEvent.type != .null else {\n            return Unmanaged.passRetained(cgEvent)\n        }\n        guard let nsEvent = NSEvent(cgEvent: cgEvent),\n              nsEvent.type == .systemDefined,\n              nsEvent.subtype.rawValue == 8 else {\n            return Unmanaged.passRetained(cgEvent)\n        }\n        \n        let data1 = nsEvent.data1\n        let keyCode = (data1 & 0xFFFF_0000) >> 16\n        let stateByte = ((data1 & 0xFF00) >> 8)\n        \n        // 0xA = key down, 0xB = key up. Only handle key down.\n        guard stateByte == 0xA,\n              let keyType = NXKeyType(rawValue: keyCode) else {\n            return Unmanaged.passRetained(cgEvent)\n        }\n        \n        let flags = nsEvent.modifierFlags\n        let option = flags.contains(.option)\n        let shift = flags.contains(.shift)\n        let command = flags.contains(.command)\n        \n        // Handle option key action (without shift)\n        if option && !shift {\n            if handleOptionAction(for: keyType, command: command) {\n                return nil\n            }\n        }\n        \n        // Handle normal key press\n        handleKeyPress(keyType: keyType, option: option, shift: shift, command: command)\n        return nil\n    }\n    \n    private func handleOptionAction(for keyType: NXKeyType, command: Bool) -> Bool {\n        let action = Defaults[.optionKeyAction]\n        \n        switch action {\n        case .openSettings:\n            openSystemSettings(for: keyType, command: command)\n            return true\n        case .showHUD:\n            showHUD(for: keyType, command: command)\n            return true\n        case .none:\n            return true\n        }\n    }\n    \n    private func prepareAudioPlayerIfNeeded() {\n        guard audioPlayer == nil else { return }\n\n        let defaultPath = \"/System/Library/LoginPlugins/BezelServices.loginPlugin/Contents/Resources/volume.aiff\"\n        if FileManager.default.fileExists(atPath: defaultPath) {\n            do {\n                audioPlayer = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: defaultPath))\n                print(\"🔊 [MediaKeyInterceptor] Loaded default Bezel audio from: \\(defaultPath)\")\n            } catch {\n                print(\"⚠️ [MediaKeyInterceptor] Failed to init AVAudioPlayer with default path \\(defaultPath): \\(error.localizedDescription)\")\n            }\n        } else {\n            print(\"⚠️ [MediaKeyInterceptor] Default bezel audio not found at: \\(defaultPath)\")\n        }\n\n        if let player = audioPlayer {\n            player.volume = 1.0\n            player.numberOfLoops = 0\n            player.prepareToPlay()\n        }\n    }\n\n    private func playFeedbackSound() {\n        guard let feedback = UserDefaults.standard.persistentDomain(forName: \"NSGlobalDomain\")?[\"com.apple.sound.beep.feedback\"] as? Int,\n              feedback == 1 else { return }\n\n        prepareAudioPlayerIfNeeded()\n        guard let player = audioPlayer else {\n            print(\"⚠️ [MediaKeyInterceptor] No audio player available to play feedback sound\")\n            return\n        }\n        if let url = player.url {\n            print(\"🔊 [MediaKeyInterceptor] Playing feedback sound from: \\(url.path)\")\n        } else {\n            print(\"🔊 [MediaKeyInterceptor] Playing feedback sound (no url available for AVAudioPlayer)\")\n        }\n        if player.isPlaying {\n            player.stop()\n            player.currentTime = 0\n        }\n        player.play()\n    }\n\n    private func handleKeyPress(keyType: NXKeyType, option: Bool, shift: Bool, command: Bool) {\n        let stepDivisor: Float = (option && shift) ? 4.0 : 1.0\n        \n        switch keyType {\n        case .soundUp:\n            Task { @MainActor in\n                self.playFeedbackSound()\n                VolumeManager.shared.increase(stepDivisor: stepDivisor)\n            }\n        case .soundDown:\n            Task { @MainActor in\n                self.playFeedbackSound()\n                VolumeManager.shared.decrease(stepDivisor: stepDivisor)\n            }\n        case .mute:\n            Task { @MainActor in\n                VolumeManager.shared.toggleMuteAction()\n            }\n        case .brightnessUp, .keyboardBrightnessUp:\n            let delta = step / stepDivisor\n            adjustBrightness(delta: delta, keyboard: keyType == .keyboardBrightnessUp || command)\n        case .brightnessDown, .keyboardBrightnessDown:\n            let delta = -(step / stepDivisor)\n            adjustBrightness(delta: delta, keyboard: keyType == .keyboardBrightnessDown || command)\n        }\n    }\n    \n    private func adjustBrightness(delta: Float, keyboard: Bool) {\n        Task { @MainActor in\n            if keyboard {\n                KeyboardBacklightManager.shared.setRelative(delta: delta)\n            } else {\n                BrightnessManager.shared.setRelative(delta: delta)\n            }\n        }\n    }\n    \n    private func showHUD(for keyType: NXKeyType, command: Bool) {\n        Task { @MainActor in\n            switch keyType {\n            case .soundUp, .soundDown, .mute:\n                let v = VolumeManager.shared.rawVolume\n                BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .volume, value: CGFloat(v))\n            case .brightnessUp, .brightnessDown:\n                if command {\n                    let v = KeyboardBacklightManager.shared.rawBrightness\n                    BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .backlight, value: CGFloat(v))\n                } else {\n                    let v = BrightnessManager.shared.rawBrightness\n                    BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .brightness, value: CGFloat(v))\n                }\n            case .keyboardBrightnessUp, .keyboardBrightnessDown:\n                let v = KeyboardBacklightManager.shared.rawBrightness\n                BoringViewCoordinator.shared.toggleSneakPeek(status: true, type: .backlight, value: CGFloat(v))\n            }\n        }\n    }\n    \n    private func openSystemSettings(for keyType: NXKeyType, command: Bool) {\n        let urlString: String\n        \n        switch keyType {\n        case .soundUp, .soundDown, .mute:\n            urlString = \"x-apple.systempreferences:com.apple.preference.sound\"\n        case .brightnessUp, .brightnessDown:\n            if command {\n                urlString = \"x-apple.systempreferences:com.apple.preference.keyboard\"\n            } else {\n                urlString = \"x-apple.systempreferences:com.apple.preference.displays\"\n            }\n        case .keyboardBrightnessUp, .keyboardBrightnessDown:\n            urlString = \"x-apple.systempreferences:com.apple.preference.keyboard\"\n        }\n        \n        guard let url = URL(string: urlString) else { return }\n        NSWorkspace.shared.open(url)\n    }\n}\n"
  },
  {
    "path": "boringNotch/private/CGSSpace.swift",
    "content": "//\n//  CGSSpace.swift\n//  boringNotch\n//\n// This Source Code Form is subject to the terms of the Mozilla Public\n// License, v. 2.0. If a copy of the MPL was not distributed with this\n// file, You can obtain one at http://mozilla.org/MPL/2.0/.\n//\n// Original source: https://github.com/avaidyam/Parrot/\n// Modified by Alexander on 2024-10-27\n\nimport AppKit\n\n/// Small Spaces API wrapper.\npublic final class CGSSpace {\n    private let identifier: CGSSpaceID\n\n    public var windows: Set<NSWindow> = [] {\n        didSet {\n            let remove = oldValue.subtracting(self.windows)\n            let add = self.windows.subtracting(oldValue)\n\n            CGSRemoveWindowsFromSpaces(_CGSDefaultConnection(),\n                                       remove.map { $0.windowNumber } as NSArray,\n                                       [self.identifier])\n            CGSAddWindowsToSpaces(_CGSDefaultConnection(),\n                                  add.map { $0.windowNumber } as NSArray,\n                                  [self.identifier])\n        }\n    }\n\n    /// Initialized `CGSSpace`s *MUST* be de-initialized upon app exit!\n    public init(level: Int = 0) {\n        let flag = 0x1 // this value MUST be 1, otherwise, Finder decides to draw desktop icons\n        self.identifier = CGSSpaceCreate(_CGSDefaultConnection(), flag, nil)\n        CGSSpaceSetAbsoluteLevel(_CGSDefaultConnection(), self.identifier, level)\n        CGSShowSpaces(_CGSDefaultConnection(), [self.identifier])\n    }\n\n    deinit {\n        CGSHideSpaces(_CGSDefaultConnection(), [self.identifier])\n        CGSSpaceDestroy(_CGSDefaultConnection(), self.identifier)\n    }\n}\n\n// CGSSpace stuff:\nfileprivate typealias CGSConnectionID = UInt\nfileprivate typealias CGSSpaceID = UInt64\n@_silgen_name(\"_CGSDefaultConnection\")\nfileprivate func _CGSDefaultConnection() -> CGSConnectionID\n@_silgen_name(\"CGSSpaceCreate\")\nfileprivate func CGSSpaceCreate(_ cid: CGSConnectionID, _ unknown: Int, _ options: NSDictionary?) -> CGSSpaceID\n@_silgen_name(\"CGSSpaceDestroy\")\nfileprivate func CGSSpaceDestroy(_ cid: CGSConnectionID, _ space: CGSSpaceID)\n@_silgen_name(\"CGSSpaceSetAbsoluteLevel\")\nfileprivate func CGSSpaceSetAbsoluteLevel(_ cid: CGSConnectionID, _ space: CGSSpaceID, _ level: Int)\n@_silgen_name(\"CGSAddWindowsToSpaces\")\nfileprivate func CGSAddWindowsToSpaces(_ cid: CGSConnectionID, _ windows: NSArray, _ spaces: NSArray)\n@_silgen_name(\"CGSRemoveWindowsFromSpaces\")\nfileprivate func CGSRemoveWindowsFromSpaces(_ cid: CGSConnectionID, _ windows: NSArray, _ spaces: NSArray)\n@_silgen_name(\"CGSHideSpaces\")\nfileprivate func CGSHideSpaces(_ cid: CGSConnectionID, _ spaces: NSArray)\n@_silgen_name(\"CGSShowSpaces\")\nfileprivate func CGSShowSpaces(_ cid: CGSConnectionID, _ spaces: NSArray)\n"
  },
  {
    "path": "boringNotch/sizing/matters.swift",
    "content": "//\n//  sizeMatters.swift\n//  boringNotch\n//\n//  Created by Harsh Vardhan  Goswami  on 05/08/24.\n//\n\nimport Defaults\nimport Foundation\nimport SwiftUI\n\nlet downloadSneakSize: CGSize = .init(width: 65, height: 1)\nlet batterySneakSize: CGSize = .init(width: 160, height: 1)\n\nlet shadowPadding: CGFloat = 20\nlet openNotchSize: CGSize = .init(width: 640, height: 190)\nlet windowSize: CGSize = .init(width: openNotchSize.width, height: openNotchSize.height + shadowPadding)\nlet cornerRadiusInsets: (opened: (top: CGFloat, bottom: CGFloat), closed: (top: CGFloat, bottom: CGFloat)) = (opened: (top: 19, bottom: 24), closed: (top: 6, bottom: 14))\n\nenum MusicPlayerImageSizes {\n    static let cornerRadiusInset: (opened: CGFloat, closed: CGFloat) = (opened: 13.0, closed: 4.0)\n    static let size = (opened: CGSize(width: 90, height: 90), closed: CGSize(width: 20, height: 20))\n}\n\n@MainActor func getScreenFrame(_ screenUUID: String? = nil) -> CGRect? {\n    var selectedScreen = NSScreen.main\n\n    if let uuid = screenUUID {\n        selectedScreen = NSScreen.screen(withUUID: uuid)\n    }\n    \n    if let screen = selectedScreen {\n        return screen.frame\n    }\n    \n    return nil\n}\n\n@MainActor func getClosedNotchSize(screenUUID: String? = nil) -> CGSize {\n    // Default notch size, to avoid using optionals\n    var notchHeight: CGFloat = Defaults[.nonNotchHeight]\n    var notchWidth: CGFloat = 185\n\n    var selectedScreen = NSScreen.main\n\n    if let uuid = screenUUID {\n        selectedScreen = NSScreen.screen(withUUID: uuid)\n    }\n\n    // Check if the screen is available\n    if let screen = selectedScreen {\n        // Calculate and set the exact width of the notch\n        if let topLeftNotchpadding: CGFloat = screen.auxiliaryTopLeftArea?.width,\n           let topRightNotchpadding: CGFloat = screen.auxiliaryTopRightArea?.width\n        {\n            notchWidth = screen.frame.width - topLeftNotchpadding - topRightNotchpadding + 4\n        }\n\n        // Check if the Mac has a notch\n        if screen.safeAreaInsets.top > 0 {\n            // This is a display WITH a notch - use notch height settings\n            notchHeight = Defaults[.notchHeight]\n            if Defaults[.notchHeightMode] == .matchRealNotchSize {\n                notchHeight = screen.safeAreaInsets.top\n            } else if Defaults[.notchHeightMode] == .matchMenuBar {\n                notchHeight = screen.frame.maxY - screen.visibleFrame.maxY\n            }\n        } else {\n            // This is a display WITHOUT a notch - use non-notch height settings\n            notchHeight = Defaults[.nonNotchHeight]\n            if Defaults[.nonNotchHeightMode] == .matchMenuBar {\n                notchHeight = screen.frame.maxY - screen.visibleFrame.maxY\n            }\n        }\n    }\n\n    return .init(width: notchWidth, height: notchHeight)\n}\n"
  },
  {
    "path": "boringNotch/utils/Logger.swift",
    "content": "import Foundation\nimport SwiftUI\n\nenum LogCategory: String {\n    case lifecycle = \"🔄\"\n    case memory = \"💾\"\n    case performance = \"⚡️\"\n    case ui = \"🎨\"\n    case network = \"🌐\"\n    case error = \"❌\"\n    case warning = \"⚠️\"\n    case success = \"✅\"\n    case debug = \"🔍\"\n}\n\nstruct Logger {\n    static func log(\n        _ message: String,\n        category: LogCategory,\n        file: String = #file,\n        function: String = #function,\n        line: Int = #line\n    ) {\n        let fileName = (file as NSString).lastPathComponent\n        let timestamp = ISO8601DateFormatter().string(from: Date())\n        print(\"\\(category.rawValue) [\\(timestamp)] [\\(fileName):\\(line)] \\(function) - \\(message)\")\n    }\n    \n    static func trackMemory(\n        file: String = #file,\n        function: String = #function,\n        line: Int = #line\n    ) {\n        var info = mach_task_basic_info()\n        var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size)/4\n        \n        let kerr: kern_return_t = withUnsafeMutablePointer(to: &info) {\n            $0.withMemoryRebound(to: integer_t.self, capacity: 1) {\n                task_info(mach_task_self_,\n                         task_flavor_t(MACH_TASK_BASIC_INFO),\n                         $0,\n                         &count)\n            }\n        }\n        \n        if kerr == KERN_SUCCESS {\n            let usedMB = Double(info.resident_size) / 1024.0 / 1024.0\n            log(String(format: \"Memory used: %.2f MB\", usedMB),\n                category: .memory,\n                file: file,\n                function: function,\n                line: line)\n        }\n    }\n}\n\nextension View {\n    func trackLifecycle(_ identifier: String) -> some View {\n        self.modifier(ViewLifecycleTracker(identifier: identifier))\n    }\n}\n\nstruct ViewLifecycleTracker: ViewModifier {\n    let identifier: String\n    \n    func body(content: Content) -> some View {\n        content\n            .onAppear {\n                Logger.log(\"\\(identifier) appeared\", category: .lifecycle)\n                Logger.trackMemory()\n            }\n            .onDisappear {\n                Logger.log(\"\\(identifier) disappeared\", category: .lifecycle)\n                Logger.trackMemory()\n            }\n    }\n} "
  },
  {
    "path": "boringNotch.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 70;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t1100290C2E847E2800035A57 /* NSItemProvider+LoadHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1100290B2E847E2800035A57 /* NSItemProvider+LoadHelpers.swift */; };\n\t\t110029272E84FD4C00035A57 /* TemporaryFileStorageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110029262E84FD4C00035A57 /* TemporaryFileStorageService.swift */; };\n\t\t1100292A2E8691B400035A57 /* FileShareView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 110029292E8691B400035A57 /* FileShareView.swift */; };\n\t\t1100292E2E86940F00035A57 /* QuickShareService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1100292D2E86940F00035A57 /* QuickShareService.swift */; };\n\t\t1113ABC52E80E27000EC13B2 /* ShelfItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABC32E80E27000EC13B2 /* ShelfItemView.swift */; };\n\t\t1113ABC62E80E27000EC13B2 /* ShelfPersistenceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABBE2E80E27000EC13B2 /* ShelfPersistenceService.swift */; };\n\t\t1113ABC82E80E27000EC13B2 /* ShelfItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABB72E80E27000EC13B2 /* ShelfItem.swift */; };\n\t\t1113ABCA2E80E27000EC13B2 /* ShelfSelectionModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABC02E80E27000EC13B2 /* ShelfSelectionModel.swift */; };\n\t\t1113ABCB2E80E27000EC13B2 /* QuickLookService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABBA2E80E27000EC13B2 /* QuickLookService.swift */; };\n\t\t1113ABCC2E80E27000EC13B2 /* ShelfDropService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABBD2E80E27000EC13B2 /* ShelfDropService.swift */; };\n\t\t1113ABCE2E80E27000EC13B2 /* ShelfActionService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABBC2E80E27000EC13B2 /* ShelfActionService.swift */; };\n\t\t1113ABD02E80E6BB00EC13B2 /* ThumbnailService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1113ABCF2E80E6BB00EC13B2 /* ThumbnailService.swift */; };\n\t\t111BE95D2ECD71E10079DD4E /* AsyncXPCConnection in Frameworks */ = {isa = PBXBuildFile; productRef = 111BE95C2ECD71E10079DD4E /* AsyncXPCConnection */; };\n\t\t111BE9952ECF2DF40079DD4E /* DragDetector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111BE9942ECF2DEF0079DD4E /* DragDetector.swift */; };\n\t\t111BEA512ECFBF7F0079DD4E /* MacroVisionKit in Frameworks */ = {isa = PBXBuildFile; productRef = 111BEA502ECFBF7F0079DD4E /* MacroVisionKit */; };\n\t\t111BEA5F2ED07A340079DD4E /* MacroVisionKit in Frameworks */ = {isa = PBXBuildFile; productRef = 111BEA5E2ED07A340079DD4E /* MacroVisionKit */; };\n\t\t111BEA612ED09B1B0079DD4E /* NSScreen+UUID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 111BEA602ED09B1B0079DD4E /* NSScreen+UUID.swift */; };\n\t\t111BEA6F2ED166E20079DD4E /* MacroVisionKit in Frameworks */ = {isa = PBXBuildFile; productRef = 111BEA6E2ED166E20079DD4E /* MacroVisionKit */; };\n\t\t112B0EB82E30DD0F00562D6C /* MediaRemoteAdapterTestClient in Resources */ = {isa = PBXBuildFile; fileRef = 112B0EB52E30DD0F00562D6C /* MediaRemoteAdapterTestClient */; };\n\t\t112B0EB92E30DD0F00562D6C /* mediaremote-adapter.pl in Resources */ = {isa = PBXBuildFile; fileRef = 112B0EB32E30DD0F00562D6C /* mediaremote-adapter.pl */; };\n\t\t112B0EBB2E30DD5000562D6C /* MediaRemoteAdapter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 112B0EBA2E30DD5000562D6C /* MediaRemoteAdapter.framework */; };\n\t\t112FB7352CCF16F70015238C /* NotchSpaceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 112FB7342CCF16F70015238C /* NotchSpaceManager.swift */; };\n\t\t1132E5122E777B6E0068732D /* YouTubeMusicModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1132E5102E777B6E0068732D /* YouTubeMusicModels.swift */; };\n\t\t1132E5142E777B920068732D /* YouTubeMusicNetworking.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1132E5132E777B920068732D /* YouTubeMusicNetworking.swift */; };\n\t\t1132E5162E777C140068732D /* YouTubeMusicAuthentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1132E5152E777C140068732D /* YouTubeMusicAuthentication.swift */; };\n\t\t1153BD8F2D986B1F00979FB0 /* MediaControllerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD8D2D986B1F00979FB0 /* MediaControllerProtocol.swift */; };\n\t\t1153BD912D986DB300979FB0 /* PlaybackState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD902D986DB300979FB0 /* PlaybackState.swift */; };\n\t\t1153BD932D986E4300979FB0 /* AppleMusicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD922D986E4300979FB0 /* AppleMusicController.swift */; };\n\t\t1153BD982D9881F900979FB0 /* AppleScriptHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD972D9881F900979FB0 /* AppleScriptHelper.swift */; };\n\t\t1153BD9A2D98824300979FB0 /* SpotifyController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD992D98824300979FB0 /* SpotifyController.swift */; };\n\t\t1153BD9C2D98853B00979FB0 /* NowPlayingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BD9B2D98853B00979FB0 /* NowPlayingController.swift */; };\n\t\t1153BDA72D99B22200979FB0 /* YouTubeMusicController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1153BDA62D99B22200979FB0 /* YouTubeMusicController.swift */; };\n\t\t115C12EC2ED3D003009754CA /* OpenNotchHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = 115C12EB2ED3D003009754CA /* OpenNotchHUD.swift */; };\n\t\t1160F8D82DD98230006FBB94 /* NotchShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1160F8D72DD98230006FBB94 /* NotchShape.swift */; };\n\t\t1163988D2DF5CAB40052E6AF /* EventModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1163988C2DF5CAB40052E6AF /* EventModel.swift */; };\n\t\t1163988F2DF5CC870052E6AF /* CalendarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1163988E2DF5CC870052E6AF /* CalendarModel.swift */; };\n\t\t116398962DF5D6C00052E6AF /* CalendarServiceProviding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116398952DF5D6C00052E6AF /* CalendarServiceProviding.swift */; };\n\t\t117AB5172E30E09C00558921 /* MediaRemoteAdapter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 112B0EBA2E30DD5000562D6C /* MediaRemoteAdapter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t118D1FD12E98FF5F00A2FF63 /* SharingStateManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118D1FD02E98FF5F00A2FF63 /* SharingStateManager.swift */; };\n\t\t118EBE252E92DCCB00D54B5A /* AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118EBE242E92DCCB00D54B5A /* AssociatedObject.swift */; };\n\t\t118EBE272E92DE8400D54B5A /* NSMenu+AssociatedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118EBE262E92DE7400D54B5A /* NSMenu+AssociatedObject.swift */; };\n\t\t118EBE292E946B3F00D54B5A /* ShareServiceFinder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118EBE282E946B3F00D54B5A /* ShareServiceFinder.swift */; };\n\t\t118EBE2D2E97165600D54B5A /* Bookmark.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118EBE2C2E97165600D54B5A /* Bookmark.swift */; };\n\t\t118EBE312E9717DB00D54B5A /* URL+SecurityScoped.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118EBE302E9717DB00D54B5A /* URL+SecurityScoped.swift */; };\n\t\t118EBE3B2E9720C500D54B5A /* ShelfStateViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 118EBE3A2E9720C500D54B5A /* ShelfStateViewModel.swift */; };\n\t\t1194E87C2EA19E09009C82D6 /* ImageProcessingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1194E87B2EA19E09009C82D6 /* ImageProcessingService.swift */; };\n\t\t1194E8852EA57D23009C82D6 /* SkyLightWindow in Frameworks */ = {isa = PBXBuildFile; productRef = 1194E8842EA57D23009C82D6 /* SkyLightWindow */; };\n\t\t1194E8872EA6DDA7009C82D6 /* BoringNotchSkyLightWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1194E8862EA6DDA7009C82D6 /* BoringNotchSkyLightWindow.swift */; };\n\t\t1194E9402EACC652009C82D6 /* Color+AccentColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1194E93F2EACC652009C82D6 /* Color+AccentColor.swift */; };\n\t\t11A45C792E34E63100CEB175 /* MediaChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11A45C782E34E63100CEB175 /* MediaChecker.swift */; };\n\t\t11C5E3132DFE85970065821E /* SettingsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C5E3112DFE85970065821E /* SettingsWindowController.swift */; };\n\t\t11C5E3162DFE88510065821E /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11C5E3152DFE88510065821E /* SettingsView.swift */; };\n\t\t11CC44A22CEE614100C7244B /* BoringViewCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CC44A12CEE614100C7244B /* BoringViewCoordinator.swift */; };\n\t\t11CFC65B2E097E9D00748C80 /* WelcomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFC65A2E097E9D00748C80 /* WelcomeView.swift */; };\n\t\t11CFC65F2E097F2F00748C80 /* OnboardingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFC65E2E097F2100748C80 /* OnboardingView.swift */; };\n\t\t11CFC6612E097F6800748C80 /* PermissionsRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFC6602E097F6800748C80 /* PermissionsRequestView.swift */; };\n\t\t11CFC6632E09918400748C80 /* MusicControllerSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFC6622E09917B00748C80 /* MusicControllerSelectionView.swift */; };\n\t\t11CFC6652E09C7B300748C80 /* OnboardingFinishView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11CFC6642E09C7B300748C80 /* OnboardingFinishView.swift */; };\n\t\t11D58EA22E760AE100FA8377 /* ImageService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11D58EA12E760AE100FA8377 /* ImageService.swift */; };\n\t\t11EFCD702E8E92D600D0B974 /* ShelfItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11EFCD6F2E8E92D600D0B974 /* ShelfItemViewModel.swift */; };\n\t\t11F747CE2EC75CEA00F841DB /* DragPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F747CD2EC75CEA00F841DB /* DragPreviewView.swift */; };\n\t\t11F7485B2EC9AABA00F841DB /* BoringNotchXPCHelper.xpc in Embed XPC Services */ = {isa = PBXBuildFile; fileRef = 11F7484F2EC9AABA00F841DB /* BoringNotchXPCHelper.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n\t\t11F748682EC9AC9600F841DB /* BoringNotchXPCHelperProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F748652EC9AC9600F841DB /* BoringNotchXPCHelperProtocol.swift */; };\n\t\t11F748692EC9AC9600F841DB /* XPCHelperClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F748662EC9AC9600F841DB /* XPCHelperClient.swift */; };\n\t\t11F748732EC9DA9300F841DB /* Lottie in Frameworks */ = {isa = PBXBuildFile; productRef = 11F748722EC9DA9300F841DB /* Lottie */; };\n\t\t11F748822ECB07A400F841DB /* MusicControlButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F748812ECB07A400F841DB /* MusicControlButton.swift */; };\n\t\t11F748842ECB27DC00F841DB /* MusicSlotConfigurationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 11F748832ECB27DC00F841DB /* MusicSlotConfigurationView.swift */; };\n\t\t14288DDC2C6E015000B9F80C /* AudioPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14288DD62C6E015000B9F80C /* AudioPlayer.swift */; };\n\t\t14288DE82C6E01C800B9F80C /* ProgressIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14288DE72C6E01C800B9F80C /* ProgressIndicator.swift */; };\n\t\t14288E0C2C6F8EC000B9F80C /* AppIcons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14288E0B2C6F8EC000B9F80C /* AppIcons.swift */; };\n\t\t1443E7F32C609DCE0027C1FC /* matters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1443E7F22C609DCE0027C1FC /* matters.swift */; };\n\t\t147163982C5D35B70068B555 /* MusicVisualizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147163972C5D35B70068B555 /* MusicVisualizer.swift */; };\n\t\t1471639A2C5D35FF0068B555 /* MusicManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 147163992C5D35FF0068B555 /* MusicManager.swift */; };\n\t\t1471A8592C6281BD0058408D /* BoringNotchWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1471A8582C6281BD0058408D /* BoringNotchWindow.swift */; };\n\t\t149E0B972C737D00006418B1 /* WebcamManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 149E0B962C737D00006418B1 /* WebcamManager.swift */; };\n\t\t149E0B9A2C737D40006418B1 /* WebcamView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 149E0B992C737D40006418B1 /* WebcamView.swift */; };\n\t\t14A7E5882C64A89C008C1BE9 /* HelloAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14A7E5872C64A89C008C1BE9 /* HelloAnimation.swift */; };\n\t\t14A7E5972C65FD31008C1BE9 /* boring.m4a in Resources */ = {isa = PBXBuildFile; fileRef = 14A7E5962C65FD31008C1BE9 /* boring.m4a */; };\n\t\t14C08BB62C8DE42D000F8AA0 /* CalendarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C08BB52C8DE42D000F8AA0 /* CalendarManager.swift */; };\n\t\t14C08BB92C8DE4B1000F8AA0 /* BoringCalendar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C08BB82C8DE4B1000F8AA0 /* BoringCalendar.swift */; };\n\t\t14C08BC12C8E03AD000F8AA0 /* NSImage+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C08BC02C8E03AD000F8AA0 /* NSImage+Extensions.swift */; };\n\t\t14CEF4162C5CAED300855D72 /* boringNotchApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CEF4152C5CAED300855D72 /* boringNotchApp.swift */; };\n\t\t14CEF4182C5CAED300855D72 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CEF4172C5CAED300855D72 /* ContentView.swift */; };\n\t\t14CEF41A2C5CAED400855D72 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14CEF4192C5CAED400855D72 /* Assets.xcassets */; };\n\t\t14CEF41D2C5CAED400855D72 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 14CEF41C2C5CAED400855D72 /* Preview Assets.xcassets */; };\n\t\t14D0321A2C68F32E0096E6A1 /* LaunchAtLogin in Frameworks */ = {isa = PBXBuildFile; productRef = 14D032192C68F32E0096E6A1 /* LaunchAtLogin */; };\n\t\t14D0321D2C68F3350096E6A1 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 14D0321C2C68F3350096E6A1 /* Sparkle */; };\n\t\t14D570B92C5E98A20011E668 /* drop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570B82C5E98A20011E668 /* drop.swift */; };\n\t\t14D570BC2C5E98EB0011E668 /* generic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570BB2C5E98EB0011E668 /* generic.swift */; };\n\t\t14D570C02C5EA5870011E668 /* AnimatedFace.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570BF2C5EA5870011E668 /* AnimatedFace.swift */; };\n\t\t14D570C22C5EAFBF0011E668 /* EmptyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570C12C5EAFBF0011E668 /* EmptyState.swift */; };\n\t\t14D570C62C5F38210011E668 /* BoringHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570C52C5F38210011E668 /* BoringHeader.swift */; };\n\t\t14D570C92C5F38890011E668 /* BoringViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570C82C5F38890011E668 /* BoringViewModel.swift */; };\n\t\t14D570CB2C5F4B2C0011E668 /* BatteryStatusViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570CA2C5F4B2C0011E668 /* BatteryStatusViewModel.swift */; };\n\t\t14D570CD2C5F4BB70011E668 /* BoringBattery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570CC2C5F4BB70011E668 /* BoringBattery.swift */; };\n\t\t14D570D22C5F6C6A0011E668 /* BoringExtrasMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14D570D12C5F6C6A0011E668 /* BoringExtrasMenu.swift */; };\n\t\t14E9FEAA2C70BF610062E83F /* DownloadView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14E9FEA92C70BF610062E83F /* DownloadView.swift */; };\n\t\t14E9FEAE2C7325770062E83F /* Button+Bouncing.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14E9FEAD2C7325770062E83F /* Button+Bouncing.swift */; };\n\t\t14FC6E502C7DED5600C7BEA5 /* DataTypes+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14FC6E4F2C7DED5600C7BEA5 /* DataTypes+Extensions.swift */; };\n\t\t507266DB2C908E2E00A2D00D /* HoverButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 507266DA2C908E2E00A2D00D /* HoverButton.swift */; };\n\t\t5917FD112E57891600E87F1C /* MediaKeyInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5917FD102E57891600E87F1C /* MediaKeyInterceptor.swift */; };\n\t\t5955950D2E900ED800C66711 /* ApplicationRelauncher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5955950C2E900ED800C66711 /* ApplicationRelauncher.swift */; };\n\t\t59D8C23C2E589FAA00147B33 /* VolumeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59D8C23B2E589FAA00147B33 /* VolumeManager.swift */; };\n\t\t9A0887322C7A693000C160EA /* TabButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0887312C7A693000C160EA /* TabButton.swift */; };\n\t\t9A0887352C7AFF8E00C160EA /* TabSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A0887342C7AFF8E00C160EA /* TabSelectionView.swift */; };\n\t\t9A987A0D2C73CA66005CA465 /* ShelfView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A987A032C73CA66005CA465 /* ShelfView.swift */; };\n\t\t9A987A102C73CA8D005CA465 /* Collections in Frameworks */ = {isa = PBXBuildFile; productRef = 9A987A0F2C73CA8D005CA465 /* Collections */; };\n\t\t9AB0C6BD2C73C9CB00F7CD30 /* NotchHomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AB0C6BB2C73C9CB00F7CD30 /* NotchHomeView.swift */; };\n\t\tB10348D92C74E56000475897 /* ConditionalModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10348D82C74E56000475897 /* ConditionalModifier.swift */; };\n\t\tB10F84A32C6C9596009F3026 /* TestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B10F84A22C6C9596009F3026 /* TestView.swift */; };\n\t\tB141C2412CA5F53F00AC8CC8 /* SparkleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B141C2402CA5F53E00AC8CC8 /* SparkleView.swift */; };\n\t\tB1628B922CC260C0003D8DF3 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = B1628B912CC260C0003D8DF3 /* SwiftUIIntrospect */; };\n\t\tB17266DF2C64DFA00031BA0D /* BundleInfos.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17266DE2C64DFA00031BA0D /* BundleInfos.swift */; };\n\t\tB17266E12C6532560031BA0D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = B17266E02C6532560031BA0D /* Localizable.xcstrings */; };\n\t\tB17266E32C65F7FB0031BA0D /* WhatsNewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B17266E22C65F7FB0031BA0D /* WhatsNewView.swift */; };\n\t\tB172AAC02C95DA0B001623F1 /* InlineHUD.swift in Sources */ = {isa = PBXBuildFile; fileRef = B172AABF2C95DA0B001623F1 /* InlineHUD.swift */; };\n\t\tB18654392C6F4990000B926A /* KeyboardShortcuts in Frameworks */ = {isa = PBXBuildFile; productRef = B18654382C6F4990000B926A /* KeyboardShortcuts */; };\n\t\tB186543C2C6F49AE000B926A /* ShortcutConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B186543B2C6F49AE000B926A /* ShortcutConstants.swift */; };\n\t\tB19016222CC15B3D00E3F12E /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = B19016212CC15B3D00E3F12E /* Defaults */; };\n\t\tB19016242CC15B5000E3F12E /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19016232CC15B4D00E3F12E /* Constants.swift */; };\n\t\tB19424092CD0FF01003E5DC2 /* LottieAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B19424082CD0FEFE003E5DC2 /* LottieAnimationView.swift */; };\n\t\tB1A78C822C8BA08100BD51B0 /* FullscreenMediaDetection.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1A78C812C8BA08100BD51B0 /* FullscreenMediaDetection.swift */; };\n\t\tB1B112912C6A572100093D8F /* EditPanelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B112902C6A572100093D8F /* EditPanelView.swift */; };\n\t\tB1B112932C6A577E00093D8F /* MouseTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1B112922C6A577E00093D8F /* MouseTracker.swift */; };\n\t\tB1C448962C9712C4001F0858 /* ActionBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C448952C9712C4001F0858 /* ActionBar.swift */; };\n\t\tB1C448982C972CC4001F0858 /* ListItemPopover.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C448972C972CC4001F0858 /* ListItemPopover.swift */; };\n\t\tB1C4489B2C97376A001F0858 /* TipStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C4489A2C97376A001F0858 /* TipStore.swift */; };\n\t\tB1C974342C642B6D0000E707 /* MarqueeTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1C974332C642B6D0000E707 /* MarqueeTextView.swift */; };\n\t\tB1CE8CFE2C6F659400DD9871 /* KeyboardShortcutsHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE8CFD2C6F659400DD9871 /* KeyboardShortcutsHelper.swift */; };\n\t\tB1D365CE2C6A979C0047BDBC /* LiveActivityModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D365CD2C6A979C0047BDBC /* LiveActivityModifier.swift */; };\n\t\tB1D365D02C6A9A6C0047BDBC /* SystemEventIndicatorModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D365CF2C6A9A6C0047BDBC /* SystemEventIndicatorModifier.swift */; };\n\t\tB1D6FD432C6603730015F173 /* SoftwareUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D6FD422C6603730015F173 /* SoftwareUpdater.swift */; };\n\t\tB1F0A0022E60000100000001 /* BrightnessManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1F0A0012E60000100000001 /* BrightnessManager.swift */; };\n\t\tB1F747F92EC7E94000F841DB /* LottieView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1F747F82EC7E94000F841DB /* LottieView.swift */; };\n\t\tB1FEB4992C7686630066EBBC /* PanGesture.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1FEB4982C7686630066EBBC /* PanGesture.swift */; };\n\t\tF38DE6482D8243E7008B5C6D /* BatteryActivityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F38DE6472D8243E2008B5C6D /* BatteryActivityManager.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t11F748592EC9AABA00F841DB /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 14CEF40A2C5CAED200855D72 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 11F7484E2EC9AABA00F841DB;\n\t\t\tremoteInfo = BoringNotchXPCHelper;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t11F748412EC8051E00F841DB /* Embed XPC Services */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"$(CONTENTS_FOLDER_PATH)/XPCServices\";\n\t\t\tdstSubfolderSpec = 16;\n\t\t\tfiles = (\n\t\t\t\t11F7485B2EC9AABA00F841DB /* BoringNotchXPCHelper.xpc in Embed XPC Services */,\n\t\t\t);\n\t\t\tname = \"Embed XPC Services\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tB1ECFA062C6FE58A002ACD87 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 12;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\t117AB5172E30E09C00558921 /* MediaRemoteAdapter.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t1100290B2E847E2800035A57 /* NSItemProvider+LoadHelpers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"NSItemProvider+LoadHelpers.swift\"; sourceTree = \"<group>\"; };\n\t\t110029262E84FD4C00035A57 /* TemporaryFileStorageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TemporaryFileStorageService.swift; sourceTree = \"<group>\"; };\n\t\t110029292E8691B400035A57 /* FileShareView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileShareView.swift; sourceTree = \"<group>\"; };\n\t\t1100292D2E86940F00035A57 /* QuickShareService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickShareService.swift; sourceTree = \"<group>\"; };\n\t\t1113ABB72E80E27000EC13B2 /* ShelfItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfItem.swift; sourceTree = \"<group>\"; };\n\t\t1113ABBA2E80E27000EC13B2 /* QuickLookService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookService.swift; sourceTree = \"<group>\"; };\n\t\t1113ABBC2E80E27000EC13B2 /* ShelfActionService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfActionService.swift; sourceTree = \"<group>\"; };\n\t\t1113ABBD2E80E27000EC13B2 /* ShelfDropService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfDropService.swift; sourceTree = \"<group>\"; };\n\t\t1113ABBE2E80E27000EC13B2 /* ShelfPersistenceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfPersistenceService.swift; sourceTree = \"<group>\"; };\n\t\t1113ABC02E80E27000EC13B2 /* ShelfSelectionModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfSelectionModel.swift; sourceTree = \"<group>\"; };\n\t\t1113ABC32E80E27000EC13B2 /* ShelfItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfItemView.swift; sourceTree = \"<group>\"; };\n\t\t1113ABCF2E80E6BB00EC13B2 /* ThumbnailService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThumbnailService.swift; sourceTree = \"<group>\"; };\n\t\t111BE9942ECF2DEF0079DD4E /* DragDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DragDetector.swift; sourceTree = \"<group>\"; };\n\t\t111BEA602ED09B1B0079DD4E /* NSScreen+UUID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"NSScreen+UUID.swift\"; sourceTree = \"<group>\"; };\n\t\t112B0EB32E30DD0F00562D6C /* mediaremote-adapter.pl */ = {isa = PBXFileReference; lastKnownFileType = text.script.perl; path = \"mediaremote-adapter.pl\"; sourceTree = \"<group>\"; };\n\t\t112B0EB52E30DD0F00562D6C /* MediaRemoteAdapterTestClient */ = {isa = PBXFileReference; lastKnownFileType = \"compiled.mach-o.executable\"; path = MediaRemoteAdapterTestClient; sourceTree = \"<group>\"; };\n\t\t112B0EBA2E30DD5000562D6C /* MediaRemoteAdapter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MediaRemoteAdapter.framework; path = \"mediaremote-adapter/MediaRemoteAdapter.framework\"; sourceTree = \"<group>\"; };\n\t\t112FB7342CCF16F70015238C /* NotchSpaceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchSpaceManager.swift; sourceTree = \"<group>\"; };\n\t\t1132E5102E777B6E0068732D /* YouTubeMusicModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeMusicModels.swift; sourceTree = \"<group>\"; };\n\t\t1132E5132E777B920068732D /* YouTubeMusicNetworking.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeMusicNetworking.swift; sourceTree = \"<group>\"; };\n\t\t1132E5152E777C140068732D /* YouTubeMusicAuthentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeMusicAuthentication.swift; sourceTree = \"<group>\"; };\n\t\t1153BD8D2D986B1F00979FB0 /* MediaControllerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaControllerProtocol.swift; sourceTree = \"<group>\"; };\n\t\t1153BD902D986DB300979FB0 /* PlaybackState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaybackState.swift; sourceTree = \"<group>\"; };\n\t\t1153BD922D986E4300979FB0 /* AppleMusicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleMusicController.swift; sourceTree = \"<group>\"; };\n\t\t1153BD972D9881F900979FB0 /* AppleScriptHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleScriptHelper.swift; sourceTree = \"<group>\"; };\n\t\t1153BD992D98824300979FB0 /* SpotifyController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SpotifyController.swift; sourceTree = \"<group>\"; };\n\t\t1153BD9B2D98853B00979FB0 /* NowPlayingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NowPlayingController.swift; sourceTree = \"<group>\"; };\n\t\t1153BDA62D99B22200979FB0 /* YouTubeMusicController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = YouTubeMusicController.swift; sourceTree = \"<group>\"; };\n\t\t115C12EB2ED3D003009754CA /* OpenNotchHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenNotchHUD.swift; sourceTree = \"<group>\"; };\n\t\t1160F8D72DD98230006FBB94 /* NotchShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotchShape.swift; sourceTree = \"<group>\"; };\n\t\t1163988C2DF5CAB40052E6AF /* EventModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventModel.swift; sourceTree = \"<group>\"; };\n\t\t1163988E2DF5CC870052E6AF /* CalendarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarModel.swift; sourceTree = \"<group>\"; };\n\t\t116398952DF5D6C00052E6AF /* CalendarServiceProviding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarServiceProviding.swift; sourceTree = \"<group>\"; };\n\t\t118D1FD02E98FF5F00A2FF63 /* SharingStateManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharingStateManager.swift; sourceTree = \"<group>\"; };\n\t\t118EBE242E92DCCB00D54B5A /* AssociatedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociatedObject.swift; sourceTree = \"<group>\"; };\n\t\t118EBE262E92DE7400D54B5A /* NSMenu+AssociatedObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"NSMenu+AssociatedObject.swift\"; sourceTree = \"<group>\"; };\n\t\t118EBE282E946B3F00D54B5A /* ShareServiceFinder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareServiceFinder.swift; sourceTree = \"<group>\"; };\n\t\t118EBE2C2E97165600D54B5A /* Bookmark.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Bookmark.swift; sourceTree = \"<group>\"; };\n\t\t118EBE302E9717DB00D54B5A /* URL+SecurityScoped.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"URL+SecurityScoped.swift\"; sourceTree = \"<group>\"; };\n\t\t118EBE3A2E9720C500D54B5A /* ShelfStateViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfStateViewModel.swift; sourceTree = \"<group>\"; };\n\t\t1194E87B2EA19E09009C82D6 /* ImageProcessingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProcessingService.swift; sourceTree = \"<group>\"; };\n\t\t1194E8862EA6DDA7009C82D6 /* BoringNotchSkyLightWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringNotchSkyLightWindow.swift; sourceTree = \"<group>\"; };\n\t\t1194E93F2EACC652009C82D6 /* Color+AccentColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"Color+AccentColor.swift\"; sourceTree = \"<group>\"; };\n\t\t11A45C782E34E63100CEB175 /* MediaChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaChecker.swift; sourceTree = \"<group>\"; };\n\t\t11C5E3112DFE85970065821E /* SettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsWindowController.swift; sourceTree = \"<group>\"; };\n\t\t11C5E3152DFE88510065821E /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = \"<group>\"; };\n\t\t11CC44A12CEE614100C7244B /* BoringViewCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringViewCoordinator.swift; sourceTree = \"<group>\"; };\n\t\t11CFC65A2E097E9D00748C80 /* WelcomeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WelcomeView.swift; sourceTree = \"<group>\"; };\n\t\t11CFC65E2E097F2100748C80 /* OnboardingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingView.swift; sourceTree = \"<group>\"; };\n\t\t11CFC6602E097F6800748C80 /* PermissionsRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsRequestView.swift; sourceTree = \"<group>\"; };\n\t\t11CFC6622E09917B00748C80 /* MusicControllerSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicControllerSelectionView.swift; sourceTree = \"<group>\"; };\n\t\t11CFC6642E09C7B300748C80 /* OnboardingFinishView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingFinishView.swift; sourceTree = \"<group>\"; };\n\t\t11D58EA12E760AE100FA8377 /* ImageService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageService.swift; sourceTree = \"<group>\"; };\n\t\t11EFCD6F2E8E92D600D0B974 /* ShelfItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShelfItemViewModel.swift; sourceTree = \"<group>\"; };\n\t\t11F747CD2EC75CEA00F841DB /* DragPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DragPreviewView.swift; sourceTree = \"<group>\"; };\n\t\t11F7484F2EC9AABA00F841DB /* BoringNotchXPCHelper.xpc */ = {isa = PBXFileReference; explicitFileType = \"wrapper.xpc-service\"; includeInIndex = 0; path = BoringNotchXPCHelper.xpc; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t11F748652EC9AC9600F841DB /* BoringNotchXPCHelperProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringNotchXPCHelperProtocol.swift; sourceTree = \"<group>\"; };\n\t\t11F748662EC9AC9600F841DB /* XPCHelperClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = XPCHelperClient.swift; sourceTree = \"<group>\"; };\n\t\t11F748812ECB07A400F841DB /* MusicControlButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicControlButton.swift; sourceTree = \"<group>\"; };\n\t\t11F748832ECB27DC00F841DB /* MusicSlotConfigurationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicSlotConfigurationView.swift; sourceTree = \"<group>\"; };\n\t\t14288DD62C6E015000B9F80C /* AudioPlayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AudioPlayer.swift; sourceTree = \"<group>\"; };\n\t\t14288DE72C6E01C800B9F80C /* ProgressIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProgressIndicator.swift; sourceTree = \"<group>\"; };\n\t\t14288E0B2C6F8EC000B9F80C /* AppIcons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIcons.swift; sourceTree = \"<group>\"; };\n\t\t1443E7F22C609DCE0027C1FC /* matters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = matters.swift; sourceTree = \"<group>\"; };\n\t\t1443E7F42C609E650027C1FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t147163972C5D35B70068B555 /* MusicVisualizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicVisualizer.swift; sourceTree = \"<group>\"; };\n\t\t147163992C5D35FF0068B555 /* MusicManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MusicManager.swift; sourceTree = \"<group>\"; };\n\t\t1471A8582C6281BD0058408D /* BoringNotchWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringNotchWindow.swift; sourceTree = \"<group>\"; };\n\t\t149E0B962C737D00006418B1 /* WebcamManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebcamManager.swift; sourceTree = \"<group>\"; };\n\t\t149E0B992C737D40006418B1 /* WebcamView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebcamView.swift; sourceTree = \"<group>\"; };\n\t\t14A7E5872C64A89C008C1BE9 /* HelloAnimation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelloAnimation.swift; sourceTree = \"<group>\"; };\n\t\t14A7E5962C65FD31008C1BE9 /* boring.m4a */ = {isa = PBXFileReference; lastKnownFileType = file; path = boring.m4a; sourceTree = \"<group>\"; };\n\t\t14C08BB52C8DE42D000F8AA0 /* CalendarManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CalendarManager.swift; sourceTree = \"<group>\"; };\n\t\t14C08BB82C8DE4B1000F8AA0 /* BoringCalendar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringCalendar.swift; sourceTree = \"<group>\"; };\n\t\t14C08BC02C8E03AD000F8AA0 /* NSImage+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = \"NSImage+Extensions.swift\"; sourceTree = \"<group>\"; };\n\t\t14CEF4122C5CAED300855D72 /* boringNotch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = boringNotch.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t14CEF4152C5CAED300855D72 /* boringNotchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = boringNotchApp.swift; sourceTree = \"<group>\"; };\n\t\t14CEF4172C5CAED300855D72 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = \"<group>\"; };\n\t\t14CEF4192C5CAED400855D72 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t14CEF41C2C5CAED400855D72 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = \"Preview Assets.xcassets\"; sourceTree = \"<group>\"; };\n\t\t14CEF41E2C5CAED400855D72 /* boringNotch.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = boringNotch.entitlements; sourceTree = \"<group>\"; };\n\t\t14D031ED2C689DB70096E6A1 /* IOKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOKit.framework; path = System/Library/Frameworks/IOKit.framework; sourceTree = SDKROOT; };\n\t\t14D031EF2C689DC00096E6A1 /* ApplicationServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ApplicationServices.framework; path = System/Library/Frameworks/ApplicationServices.framework; sourceTree = SDKROOT; };\n\t\t14D570B82C5E98A20011E668 /* drop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = drop.swift; sourceTree = \"<group>\"; };\n\t\t14D570BB2C5E98EB0011E668 /* generic.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = generic.swift; sourceTree = \"<group>\"; };\n\t\t14D570BF2C5EA5870011E668 /* AnimatedFace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnimatedFace.swift; sourceTree = \"<group>\"; };\n\t\t14D570C12C5EAFBF0011E668 /* EmptyState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyState.swift; sourceTree = \"<group>\"; };\n\t\t14D570C52C5F38210011E668 /* BoringHeader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringHeader.swift; sourceTree = \"<group>\"; };\n\t\t14D570C82C5F38890011E668 /* BoringViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringViewModel.swift; sourceTree = \"<group>\"; };\n\t\t14D570CA2C5F4B2C0011E668 /* BatteryStatusViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryStatusViewModel.swift; sourceTree = \"<group>\"; };\n\t\t14D570CC2C5F4BB70011E668 /* BoringBattery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringBattery.swift; sourceTree = \"<group>\"; };\n\t\t14D570D12C5F6C6A0011E668 /* BoringExtrasMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BoringExtrasMenu.swift; sourceTree = \"<group>\"; };\n\t\t14E9FEA92C70BF610062E83F /* DownloadView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadView.swift; sourceTree = \"<group>\"; };\n\t\t14E9FEAD2C7325770062E83F /* Button+Bouncing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"Button+Bouncing.swift\"; sourceTree = \"<group>\"; };\n\t\t14FC6E4F2C7DED5600C7BEA5 /* DataTypes+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"DataTypes+Extensions.swift\"; sourceTree = \"<group>\"; };\n\t\t507266DA2C908E2E00A2D00D /* HoverButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HoverButton.swift; sourceTree = \"<group>\"; };\n\t\t5917FD102E57891600E87F1C /* MediaKeyInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaKeyInterceptor.swift; sourceTree = \"<group>\"; };\n\t\t5955950C2E900ED800C66711 /* ApplicationRelauncher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationRelauncher.swift; sourceTree = \"<group>\"; };\n\t\t59D8C23B2E589FAA00147B33 /* VolumeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeManager.swift; sourceTree = \"<group>\"; };\n\t\t9A0887312C7A693000C160EA /* TabButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabButton.swift; sourceTree = \"<group>\"; };\n\t\t9A0887342C7AFF8E00C160EA /* TabSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabSelectionView.swift; sourceTree = \"<group>\"; };\n\t\t9A987A032C73CA66005CA465 /* ShelfView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShelfView.swift; sourceTree = \"<group>\"; };\n\t\t9AB0C6BB2C73C9CB00F7CD30 /* NotchHomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotchHomeView.swift; sourceTree = \"<group>\"; };\n\t\tB10348D82C74E56000475897 /* ConditionalModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionalModifier.swift; sourceTree = \"<group>\"; };\n\t\tB10F84A22C6C9596009F3026 /* TestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestView.swift; sourceTree = \"<group>\"; };\n\t\tB141C2402CA5F53E00AC8CC8 /* SparkleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SparkleView.swift; sourceTree = \"<group>\"; };\n\t\tB17266DE2C64DFA00031BA0D /* BundleInfos.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleInfos.swift; sourceTree = \"<group>\"; };\n\t\tB17266E02C6532560031BA0D /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = \"<group>\"; };\n\t\tB17266E22C65F7FB0031BA0D /* WhatsNewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WhatsNewView.swift; sourceTree = \"<group>\"; };\n\t\tB172AABF2C95DA0B001623F1 /* InlineHUD.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InlineHUD.swift; sourceTree = \"<group>\"; };\n\t\tB186543B2C6F49AE000B926A /* ShortcutConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutConstants.swift; sourceTree = \"<group>\"; };\n\t\tB19016232CC15B4D00E3F12E /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = \"<group>\"; };\n\t\tB19424082CD0FEFE003E5DC2 /* LottieAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieAnimationView.swift; sourceTree = \"<group>\"; };\n\t\tB1A78C812C8BA08100BD51B0 /* FullscreenMediaDetection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenMediaDetection.swift; sourceTree = \"<group>\"; };\n\t\tB1B112902C6A572100093D8F /* EditPanelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditPanelView.swift; sourceTree = \"<group>\"; };\n\t\tB1B112922C6A577E00093D8F /* MouseTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MouseTracker.swift; sourceTree = \"<group>\"; };\n\t\tB1C448952C9712C4001F0858 /* ActionBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBar.swift; sourceTree = \"<group>\"; };\n\t\tB1C448972C972CC4001F0858 /* ListItemPopover.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListItemPopover.swift; sourceTree = \"<group>\"; };\n\t\tB1C4489A2C97376A001F0858 /* TipStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TipStore.swift; sourceTree = \"<group>\"; };\n\t\tB1C974332C642B6D0000E707 /* MarqueeTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarqueeTextView.swift; sourceTree = \"<group>\"; };\n\t\tB1CE8CFD2C6F659400DD9871 /* KeyboardShortcutsHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardShortcutsHelper.swift; sourceTree = \"<group>\"; };\n\t\tB1D365CD2C6A979C0047BDBC /* LiveActivityModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityModifier.swift; sourceTree = \"<group>\"; };\n\t\tB1D365CF2C6A9A6C0047BDBC /* SystemEventIndicatorModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SystemEventIndicatorModifier.swift; sourceTree = \"<group>\"; };\n\t\tB1D6FD422C6603730015F173 /* SoftwareUpdater.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SoftwareUpdater.swift; sourceTree = \"<group>\"; };\n\t\tB1F0A0012E60000100000001 /* BrightnessManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BrightnessManager.swift; sourceTree = \"<group>\"; };\n\t\tB1F747F82EC7E94000F841DB /* LottieView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LottieView.swift; sourceTree = \"<group>\"; };\n\t\tB1FEB4982C7686630066EBBC /* PanGesture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PanGesture.swift; sourceTree = \"<group>\"; };\n\t\tF38DE6472D8243E2008B5C6D /* BatteryActivityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BatteryActivityManager.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\t\t11F7485C2EC9AABA00F841DB /* PBXFileSystemSynchronizedBuildFileExceptionSet */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tInfo.plist,\n\t\t\t);\n\t\t\ttarget = 11F7484E2EC9AABA00F841DB /* BoringNotchXPCHelper */;\n\t\t};\n/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t112FB72F2CCF12CC0015238C /* private */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = private; sourceTree = \"<group>\"; };\n\t\t11F748502EC9AABA00F841DB /* BoringNotchXPCHelper */ = {isa = PBXFileSystemSynchronizedRootGroup; exceptions = (11F7485C2EC9AABA00F841DB /* PBXFileSystemSynchronizedBuildFileExceptionSet */, ); explicitFileTypes = {}; explicitFolders = (); path = BoringNotchXPCHelper; sourceTree = \"<group>\"; };\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t11F7484C2EC9AABA00F841DB /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t14CEF40F2C5CAED300855D72 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t111BEA5F2ED07A340079DD4E /* MacroVisionKit in Frameworks */,\n\t\t\t\t9A987A102C73CA8D005CA465 /* Collections in Frameworks */,\n\t\t\t\t1194E8852EA57D23009C82D6 /* SkyLightWindow in Frameworks */,\n\t\t\t\t112B0EBB2E30DD5000562D6C /* MediaRemoteAdapter.framework in Frameworks */,\n\t\t\t\t14D0321D2C68F3350096E6A1 /* Sparkle in Frameworks */,\n\t\t\t\t111BEA6F2ED166E20079DD4E /* MacroVisionKit in Frameworks */,\n\t\t\t\t11F748732EC9DA9300F841DB /* Lottie in Frameworks */,\n\t\t\t\t14D0321A2C68F32E0096E6A1 /* LaunchAtLogin in Frameworks */,\n\t\t\t\t111BEA512ECFBF7F0079DD4E /* MacroVisionKit in Frameworks */,\n\t\t\t\tB18654392C6F4990000B926A /* KeyboardShortcuts in Frameworks */,\n\t\t\t\tB19016222CC15B3D00E3F12E /* Defaults in Frameworks */,\n\t\t\t\t111BE95D2ECD71E10079DD4E /* AsyncXPCConnection in Frameworks */,\n\t\t\t\tB1628B922CC260C0003D8DF3 /* SwiftUIIntrospect in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t1113ABB82E80E27000EC13B2 /* Models */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t118EBE2C2E97165600D54B5A /* Bookmark.swift */,\n\t\t\t\t1113ABB72E80E27000EC13B2 /* ShelfItem.swift */,\n\t\t\t);\n\t\t\tpath = Models;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1113ABBF2E80E27000EC13B2 /* Services */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1194E87B2EA19E09009C82D6 /* ImageProcessingService.swift */,\n\t\t\t\t118EBE282E946B3F00D54B5A /* ShareServiceFinder.swift */,\n\t\t\t\t1100292D2E86940F00035A57 /* QuickShareService.swift */,\n\t\t\t\t110029262E84FD4C00035A57 /* TemporaryFileStorageService.swift */,\n\t\t\t\t1113ABCF2E80E6BB00EC13B2 /* ThumbnailService.swift */,\n\t\t\t\t1113ABBA2E80E27000EC13B2 /* QuickLookService.swift */,\n\t\t\t\t1113ABBC2E80E27000EC13B2 /* ShelfActionService.swift */,\n\t\t\t\t1113ABBD2E80E27000EC13B2 /* ShelfDropService.swift */,\n\t\t\t\t1113ABBE2E80E27000EC13B2 /* ShelfPersistenceService.swift */,\n\t\t\t);\n\t\t\tpath = Services;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1113ABC22E80E27000EC13B2 /* ViewModels */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t118EBE3A2E9720C500D54B5A /* ShelfStateViewModel.swift */,\n\t\t\t\t11EFCD6F2E8E92D600D0B974 /* ShelfItemViewModel.swift */,\n\t\t\t\t1113ABC02E80E27000EC13B2 /* ShelfSelectionModel.swift */,\n\t\t\t);\n\t\t\tpath = ViewModels;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1113ABC42E80E27000EC13B2 /* Views */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11F747CD2EC75CEA00F841DB /* DragPreviewView.swift */,\n\t\t\t\t110029292E8691B400035A57 /* FileShareView.swift */,\n\t\t\t\t1113ABC32E80E27000EC13B2 /* ShelfItemView.swift */,\n\t\t\t\t9A987A032C73CA66005CA465 /* ShelfView.swift */,\n\t\t\t);\n\t\t\tpath = Views;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t112B0EB62E30DD0F00562D6C /* mediaremote-adapter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t112B0EB32E30DD0F00562D6C /* mediaremote-adapter.pl */,\n\t\t\t\t112B0EB52E30DD0F00562D6C /* MediaRemoteAdapterTestClient */,\n\t\t\t);\n\t\t\tpath = \"mediaremote-adapter\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1132E5232E78D6DA0068732D /* YouTube Music Controller */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1132E5152E777C140068732D /* YouTubeMusicAuthentication.swift */,\n\t\t\t\t1132E5132E777B920068732D /* YouTubeMusicNetworking.swift */,\n\t\t\t\t1132E5102E777B6E0068732D /* YouTubeMusicModels.swift */,\n\t\t\t\t1153BDA62D99B22200979FB0 /* YouTubeMusicController.swift */,\n\t\t\t);\n\t\t\tpath = \"YouTube Music Controller\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1153BD8E2D986B1F00979FB0 /* MediaControllers */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1132E5232E78D6DA0068732D /* YouTube Music Controller */,\n\t\t\t\t1153BD8D2D986B1F00979FB0 /* MediaControllerProtocol.swift */,\n\t\t\t\t1153BD922D986E4300979FB0 /* AppleMusicController.swift */,\n\t\t\t\t1153BD992D98824300979FB0 /* SpotifyController.swift */,\n\t\t\t\t1153BD9B2D98853B00979FB0 /* NowPlayingController.swift */,\n\t\t\t);\n\t\t\tpath = MediaControllers;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t116398942DF5D6B40052E6AF /* Providers */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t116398952DF5D6C00052E6AF /* CalendarServiceProviding.swift */,\n\t\t\t);\n\t\t\tpath = Providers;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t11F748672EC9AC9600F841DB /* XPCHelperClient */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11F748652EC9AC9600F841DB /* BoringNotchXPCHelperProtocol.swift */,\n\t\t\t\t11F748662EC9AC9600F841DB /* XPCHelperClient.swift */,\n\t\t\t);\n\t\t\tpath = XPCHelperClient;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14288DD92C6E015000B9F80C /* helpers */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t118EBE242E92DCCB00D54B5A /* AssociatedObject.swift */,\n\t\t\t\t11A45C782E34E63100CEB175 /* MediaChecker.swift */,\n\t\t\t\t1153BD972D9881F900979FB0 /* AppleScriptHelper.swift */,\n\t\t\t\t14288DD62C6E015000B9F80C /* AudioPlayer.swift */,\n\t\t\t\t5955950C2E900ED800C66711 /* ApplicationRelauncher.swift */,\n\t\t\t\t14288E0B2C6F8EC000B9F80C /* AppIcons.swift */,\n\t\t\t);\n\t\t\tpath = helpers;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14288DE22C6E016F00B9F80C /* observers */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t111BE9942ECF2DEF0079DD4E /* DragDetector.swift */,\n\t\t\t\tB1A78C812C8BA08100BD51B0 /* FullscreenMediaDetection.swift */,\n\t\t\t\t5917FD102E57891600E87F1C /* MediaKeyInterceptor.swift */,\n\t\t\t);\n\t\t\tpath = observers;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1443E7F12C609DB90027C1FC /* sizing */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1443E7F22C609DCE0027C1FC /* matters.swift */,\n\t\t\t);\n\t\t\tpath = sizing;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1471639B2C5D362F0068B555 /* components */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB141C23B2CA5F50900AC8CC8 /* Onboarding */,\n\t\t\t\tB1C448992C97375A001F0858 /* Tips */,\n\t\t\t\t14C08BB72C8DE49E000F8AA0 /* Calendar */,\n\t\t\t\t9A987A042C73CA66005CA465 /* Shelf */,\n\t\t\t\t149E0B982C737D26006418B1 /* Webcam */,\n\t\t\t\tB18654312C6F45AE000B926A /* Live activities */,\n\t\t\t\tB18654302C6F4590000B926A /* Settings */,\n\t\t\t\tB186542F2C6F455E000B926A /* Notch */,\n\t\t\t\t9A0887332C7AFF7E00C160EA /* Tabs */,\n\t\t\t\tB186542E2C6F453B000B926A /* Music */,\n\t\t\t\t14D570BF2C5EA5870011E668 /* AnimatedFace.swift */,\n\t\t\t\t14288DE72C6E01C800B9F80C /* ProgressIndicator.swift */,\n\t\t\t\t14D570C12C5EAFBF0011E668 /* EmptyState.swift */,\n\t\t\t\tB17266E22C65F7FB0031BA0D /* WhatsNewView.swift */,\n\t\t\t\tB10F84A22C6C9596009F3026 /* TestView.swift */,\n\t\t\t\t507266DA2C908E2E00A2D00D /* HoverButton.swift */,\n\t\t\t\tB1F747F82EC7E94000F841DB /* LottieView.swift */,\n\t\t\t);\n\t\t\tpath = components;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t147163B52C5D804B0068B555 /* managers */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11D58EA12E760AE100FA8377 /* ImageService.swift */,\n\t\t\t\tF38DE6472D8243E2008B5C6D /* BatteryActivityManager.swift */,\n\t\t\t\t112FB7342CCF16F70015238C /* NotchSpaceManager.swift */,\n\t\t\t\t147163992C5D35FF0068B555 /* MusicManager.swift */,\n\t\t\t\t149E0B962C737D00006418B1 /* WebcamManager.swift */,\n\t\t\t\t14C08BB52C8DE42D000F8AA0 /* CalendarManager.swift */,\n\t\t\t\tB1F0A0012E60000100000001 /* BrightnessManager.swift */,\n\t\t\t\t59D8C23B2E589FAA00147B33 /* VolumeManager.swift */,\n\t\t\t);\n\t\t\tpath = managers;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t149E0B982C737D26006418B1 /* Webcam */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t149E0B992C737D40006418B1 /* WebcamView.swift */,\n\t\t\t);\n\t\t\tpath = Webcam;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14C08BB72C8DE49E000F8AA0 /* Calendar */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t14C08BB82C8DE4B1000F8AA0 /* BoringCalendar.swift */,\n\t\t\t);\n\t\t\tpath = Calendar;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14CEF4092C5CAED200855D72 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t112B0EB62E30DD0F00562D6C /* mediaremote-adapter */,\n\t\t\t\t14CEF4142C5CAED300855D72 /* boringNotch */,\n\t\t\t\t11F748502EC9AABA00F841DB /* BoringNotchXPCHelper */,\n\t\t\t\t14CEF4132C5CAED300855D72 /* Products */,\n\t\t\t\t14D031EC2C689DB70096E6A1 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14CEF4132C5CAED300855D72 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t14CEF4122C5CAED300855D72 /* boringNotch.app */,\n\t\t\t\t11F7484F2EC9AABA00F841DB /* BoringNotchXPCHelper.xpc */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14CEF4142C5CAED300855D72 /* boringNotch */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11F748672EC9AC9600F841DB /* XPCHelperClient */,\n\t\t\t\t116398942DF5D6B40052E6AF /* Providers */,\n\t\t\t\t14288DE22C6E016F00B9F80C /* observers */,\n\t\t\t\t14288DD92C6E015000B9F80C /* helpers */,\n\t\t\t\tB186543A2C6F49A4000B926A /* Shortcuts */,\n\t\t\t\t1443E7F12C609DB90027C1FC /* sizing */,\n\t\t\t\t14D570C72C5F38760011E668 /* models */,\n\t\t\t\t14D570BA2C5E98E30011E668 /* enums */,\n\t\t\t\t14D570B72C5E98960011E668 /* animations */,\n\t\t\t\t147163B52C5D804B0068B555 /* managers */,\n\t\t\t\t1153BD8E2D986B1F00979FB0 /* MediaControllers */,\n\t\t\t\tB15063502C63D3F600EBB0E3 /* extensions */,\n\t\t\t\t1471639B2C5D362F0068B555 /* components */,\n\t\t\t\t14CEF4152C5CAED300855D72 /* boringNotchApp.swift */,\n\t\t\t\t14CEF4172C5CAED300855D72 /* ContentView.swift */,\n\t\t\t\tB17266E02C6532560031BA0D /* Localizable.xcstrings */,\n\t\t\t\t14CEF4192C5CAED400855D72 /* Assets.xcassets */,\n\t\t\t\t14A7E5962C65FD31008C1BE9 /* boring.m4a */,\n\t\t\t\t1443E7F42C609E650027C1FC /* Info.plist */,\n\t\t\t\t14CEF41E2C5CAED400855D72 /* boringNotch.entitlements */,\n\t\t\t\t14CEF41B2C5CAED400855D72 /* Preview Content */,\n\t\t\t\t112FB72F2CCF12CC0015238C /* private */,\n\t\t\t\t11CC44A12CEE614100C7244B /* BoringViewCoordinator.swift */,\n\t\t\t);\n\t\t\tpath = boringNotch;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14CEF41B2C5CAED400855D72 /* Preview Content */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t14CEF41C2C5CAED400855D72 /* Preview Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Preview Content\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14D031EC2C689DB70096E6A1 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t14D031EF2C689DC00096E6A1 /* ApplicationServices.framework */,\n\t\t\t\t14D031ED2C689DB70096E6A1 /* IOKit.framework */,\n\t\t\t\t112B0EBA2E30DD5000562D6C /* MediaRemoteAdapter.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14D570B72C5E98960011E668 /* animations */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t14D570B82C5E98A20011E668 /* drop.swift */,\n\t\t\t\t14A7E5872C64A89C008C1BE9 /* HelloAnimation.swift */,\n\t\t\t);\n\t\t\tpath = animations;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14D570BA2C5E98E30011E668 /* enums */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t14D570BB2C5E98EB0011E668 /* generic.swift */,\n\t\t\t);\n\t\t\tpath = enums;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t14D570C72C5F38760011E668 /* models */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11F748812ECB07A400F841DB /* MusicControlButton.swift */,\n\t\t\t\t118D1FD02E98FF5F00A2FF63 /* SharingStateManager.swift */,\n\t\t\t\t1163988E2DF5CC870052E6AF /* CalendarModel.swift */,\n\t\t\t\t1163988C2DF5CAB40052E6AF /* EventModel.swift */,\n\t\t\t\tB19016232CC15B4D00E3F12E /* Constants.swift */,\n\t\t\t\t14D570C82C5F38890011E668 /* BoringViewModel.swift */,\n\t\t\t\t14D570CA2C5F4B2C0011E668 /* BatteryStatusViewModel.swift */,\n\t\t\t\t1153BD902D986DB300979FB0 /* PlaybackState.swift */,\n\t\t\t);\n\t\t\tpath = models;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9A0887332C7AFF7E00C160EA /* Tabs */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9A0887312C7A693000C160EA /* TabButton.swift */,\n\t\t\t\t9A0887342C7AFF8E00C160EA /* TabSelectionView.swift */,\n\t\t\t);\n\t\t\tpath = Tabs;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9A987A042C73CA66005CA465 /* Shelf */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1113ABB82E80E27000EC13B2 /* Models */,\n\t\t\t\t1113ABBF2E80E27000EC13B2 /* Services */,\n\t\t\t\t1113ABC22E80E27000EC13B2 /* ViewModels */,\n\t\t\t\t1113ABC42E80E27000EC13B2 /* Views */,\n\t\t\t);\n\t\t\tpath = Shelf;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB141C23B2CA5F50900AC8CC8 /* Onboarding */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11CFC6642E09C7B300748C80 /* OnboardingFinishView.swift */,\n\t\t\t\t11CFC6622E09917B00748C80 /* MusicControllerSelectionView.swift */,\n\t\t\t\t11CFC6602E097F6800748C80 /* PermissionsRequestView.swift */,\n\t\t\t\t11CFC65E2E097F2100748C80 /* OnboardingView.swift */,\n\t\t\t\t11CFC65A2E097E9D00748C80 /* WelcomeView.swift */,\n\t\t\t\tB141C2402CA5F53E00AC8CC8 /* SparkleView.swift */,\n\t\t\t);\n\t\t\tpath = Onboarding;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB15063502C63D3F600EBB0E3 /* extensions */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t111BEA602ED09B1B0079DD4E /* NSScreen+UUID.swift */,\n\t\t\t\t1194E93F2EACC652009C82D6 /* Color+AccentColor.swift */,\n\t\t\t\t118EBE302E9717DB00D54B5A /* URL+SecurityScoped.swift */,\n\t\t\t\t118EBE262E92DE7400D54B5A /* NSMenu+AssociatedObject.swift */,\n\t\t\t\t1100290B2E847E2800035A57 /* NSItemProvider+LoadHelpers.swift */,\n\t\t\t\t14C08BC02C8E03AD000F8AA0 /* NSImage+Extensions.swift */,\n\t\t\t\tB17266DE2C64DFA00031BA0D /* BundleInfos.swift */,\n\t\t\t\tB1B112922C6A577E00093D8F /* MouseTracker.swift */,\n\t\t\t\tB1CE8CFD2C6F659400DD9871 /* KeyboardShortcutsHelper.swift */,\n\t\t\t\t14E9FEAD2C7325770062E83F /* Button+Bouncing.swift */,\n\t\t\t\tB10348D82C74E56000475897 /* ConditionalModifier.swift */,\n\t\t\t\tB1FEB4982C7686630066EBBC /* PanGesture.swift */,\n\t\t\t\t14FC6E4F2C7DED5600C7BEA5 /* DataTypes+Extensions.swift */,\n\t\t\t\tB1C448952C9712C4001F0858 /* ActionBar.swift */,\n\t\t\t);\n\t\t\tpath = extensions;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB186542E2C6F453B000B926A /* Music */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB19424082CD0FEFE003E5DC2 /* LottieAnimationView.swift */,\n\t\t\t\t147163972C5D35B70068B555 /* MusicVisualizer.swift */,\n\t\t\t);\n\t\t\tpath = Music;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB186542F2C6F455E000B926A /* Notch */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1194E8862EA6DDA7009C82D6 /* BoringNotchSkyLightWindow.swift */,\n\t\t\t\t1160F8D72DD98230006FBB94 /* NotchShape.swift */,\n\t\t\t\t9AB0C6BB2C73C9CB00F7CD30 /* NotchHomeView.swift */,\n\t\t\t\t14D570C52C5F38210011E668 /* BoringHeader.swift */,\n\t\t\t\t14D570D12C5F6C6A0011E668 /* BoringExtrasMenu.swift */,\n\t\t\t\t1471A8582C6281BD0058408D /* BoringNotchWindow.swift */,\n\t\t\t);\n\t\t\tpath = Notch;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB18654302C6F4590000B926A /* Settings */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t11F748832ECB27DC00F841DB /* MusicSlotConfigurationView.swift */,\n\t\t\t\t11C5E3152DFE88510065821E /* SettingsView.swift */,\n\t\t\t\t11C5E3112DFE85970065821E /* SettingsWindowController.swift */,\n\t\t\t\tB1D6FD422C6603730015F173 /* SoftwareUpdater.swift */,\n\t\t\t\tB1B112902C6A572100093D8F /* EditPanelView.swift */,\n\t\t\t\tB1C448972C972CC4001F0858 /* ListItemPopover.swift */,\n\t\t\t);\n\t\t\tpath = Settings;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB18654312C6F45AE000B926A /* Live activities */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t115C12EB2ED3D003009754CA /* OpenNotchHUD.swift */,\n\t\t\t\t14D570CC2C5F4BB70011E668 /* BoringBattery.swift */,\n\t\t\t\tB1C974332C642B6D0000E707 /* MarqueeTextView.swift */,\n\t\t\t\tB1D365CD2C6A979C0047BDBC /* LiveActivityModifier.swift */,\n\t\t\t\tB1D365CF2C6A9A6C0047BDBC /* SystemEventIndicatorModifier.swift */,\n\t\t\t\t14E9FEA92C70BF610062E83F /* DownloadView.swift */,\n\t\t\t\tB172AABF2C95DA0B001623F1 /* InlineHUD.swift */,\n\t\t\t);\n\t\t\tpath = \"Live activities\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB186543A2C6F49A4000B926A /* Shortcuts */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB186543B2C6F49AE000B926A /* ShortcutConstants.swift */,\n\t\t\t);\n\t\t\tpath = Shortcuts;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tB1C448992C97375A001F0858 /* Tips */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tB1C4489A2C97376A001F0858 /* TipStore.swift */,\n\t\t\t);\n\t\t\tpath = Tips;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t11F7484E2EC9AABA00F841DB /* BoringNotchXPCHelper */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 11F7485D2EC9AABA00F841DB /* Build configuration list for PBXNativeTarget \"BoringNotchXPCHelper\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t11F7484B2EC9AABA00F841DB /* Sources */,\n\t\t\t\t11F7484C2EC9AABA00F841DB /* Frameworks */,\n\t\t\t\t11F7484D2EC9AABA00F841DB /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t11F748502EC9AABA00F841DB /* BoringNotchXPCHelper */,\n\t\t\t);\n\t\t\tname = BoringNotchXPCHelper;\n\t\t\tpackageProductDependencies = (\n\t\t\t);\n\t\t\tproductName = BoringNotchXPCHelper;\n\t\t\tproductReference = 11F7484F2EC9AABA00F841DB /* BoringNotchXPCHelper.xpc */;\n\t\t\tproductType = \"com.apple.product-type.xpc-service\";\n\t\t};\n\t\t14CEF4112C5CAED300855D72 /* boringNotch */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 14CEF4212C5CAED400855D72 /* Build configuration list for PBXNativeTarget \"boringNotch\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t14CEF40E2C5CAED300855D72 /* Sources */,\n\t\t\t\t14CEF40F2C5CAED300855D72 /* Frameworks */,\n\t\t\t\t14CEF4102C5CAED300855D72 /* Resources */,\n\t\t\t\tB1ECFA062C6FE58A002ACD87 /* Embed Frameworks */,\n\t\t\t\t11F748412EC8051E00F841DB /* Embed XPC Services */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t11F7485A2EC9AABA00F841DB /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t112FB72F2CCF12CC0015238C /* private */,\n\t\t\t);\n\t\t\tname = boringNotch;\n\t\t\tpackageProductDependencies = (\n\t\t\t\t14D032192C68F32E0096E6A1 /* LaunchAtLogin */,\n\t\t\t\t14D0321C2C68F3350096E6A1 /* Sparkle */,\n\t\t\t\tB18654382C6F4990000B926A /* KeyboardShortcuts */,\n\t\t\t\t9A987A0F2C73CA8D005CA465 /* Collections */,\n\t\t\t\tB19016212CC15B3D00E3F12E /* Defaults */,\n\t\t\t\tB1628B912CC260C0003D8DF3 /* SwiftUIIntrospect */,\n\t\t\t\t1194E8842EA57D23009C82D6 /* SkyLightWindow */,\n\t\t\t\t11F748722EC9DA9300F841DB /* Lottie */,\n\t\t\t\t111BE95C2ECD71E10079DD4E /* AsyncXPCConnection */,\n\t\t\t\t111BEA502ECFBF7F0079DD4E /* MacroVisionKit */,\n\t\t\t\t111BEA5E2ED07A340079DD4E /* MacroVisionKit */,\n\t\t\t\t111BEA6E2ED166E20079DD4E /* MacroVisionKit */,\n\t\t\t);\n\t\t\tproductName = dynamicNotch;\n\t\t\tproductReference = 14CEF4122C5CAED300855D72 /* boringNotch.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t14CEF40A2C5CAED200855D72 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1640;\n\t\t\t\tLastUpgradeCheck = 1640;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t11F7484E2EC9AABA00F841DB = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 16.4;\n\t\t\t\t\t};\n\t\t\t\t\t14CEF4112C5CAED300855D72 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 15.4;\n\t\t\t\t\t\tLastSwiftMigration = 1540;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 14CEF40D2C5CAED200855D72 /* Build configuration list for PBXProject \"boringNotch\" */;\n\t\t\tcompatibilityVersion = \"Xcode 14.0\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 14CEF4092C5CAED200855D72;\n\t\t\tpackageReferences = (\n\t\t\t\t14D032182C68F32E0096E6A1 /* XCRemoteSwiftPackageReference \"LaunchAtLogin-Modern\" */,\n\t\t\t\t14D0321B2C68F3350096E6A1 /* XCRemoteSwiftPackageReference \"Sparkle\" */,\n\t\t\t\tB18654372C6F4990000B926A /* XCRemoteSwiftPackageReference \"KeyboardShortcuts\" */,\n\t\t\t\t9A987A0E2C73CA8D005CA465 /* XCRemoteSwiftPackageReference \"swift-collections\" */,\n\t\t\t\t9A987A112C73CAA1005CA465 /* XCRemoteSwiftPackageReference \"Pow\" */,\n\t\t\t\tB19016202CC15B3D00E3F12E /* XCRemoteSwiftPackageReference \"Defaults\" */,\n\t\t\t\tB1628B902CC260C0003D8DF3 /* XCRemoteSwiftPackageReference \"swiftui-introspect\" */,\n\t\t\t\t1194E8832EA57D23009C82D6 /* XCRemoteSwiftPackageReference \"SkyLightWindow\" */,\n\t\t\t\t11F748712EC9DA9300F841DB /* XCRemoteSwiftPackageReference \"lottie-spm\" */,\n\t\t\t\t111BE95B2ECD71E10079DD4E /* XCRemoteSwiftPackageReference \"AsyncXPCConnection\" */,\n\t\t\t\t111BEA6D2ED166E20079DD4E /* XCRemoteSwiftPackageReference \"MacroVisionKit\" */,\n\t\t\t);\n\t\t\tproductRefGroup = 14CEF4132C5CAED300855D72 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t14CEF4112C5CAED300855D72 /* boringNotch */,\n\t\t\t\t11F7484E2EC9AABA00F841DB /* BoringNotchXPCHelper */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t11F7484D2EC9AABA00F841DB /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t14CEF4102C5CAED300855D72 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t14CEF41D2C5CAED400855D72 /* Preview Assets.xcassets in Resources */,\n\t\t\t\t14CEF41A2C5CAED400855D72 /* Assets.xcassets in Resources */,\n\t\t\t\t112B0EB82E30DD0F00562D6C /* MediaRemoteAdapterTestClient in Resources */,\n\t\t\t\t112B0EB92E30DD0F00562D6C /* mediaremote-adapter.pl in Resources */,\n\t\t\t\tB17266E12C6532560031BA0D /* Localizable.xcstrings in Resources */,\n\t\t\t\t14A7E5972C65FD31008C1BE9 /* boring.m4a in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t11F7484B2EC9AABA00F841DB /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t14CEF40E2C5CAED300855D72 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t115C12EC2ED3D003009754CA /* OpenNotchHUD.swift in Sources */,\n\t\t\t\t14C08BB92C8DE4B1000F8AA0 /* BoringCalendar.swift in Sources */,\n\t\t\t\t5955950D2E900ED800C66711 /* ApplicationRelauncher.swift in Sources */,\n\t\t\t\t1100292A2E8691B400035A57 /* FileShareView.swift in Sources */,\n\t\t\t\t147163982C5D35B70068B555 /* MusicVisualizer.swift in Sources */,\n\t\t\t\t11CC44A22CEE614100C7244B /* BoringViewCoordinator.swift in Sources */,\n\t\t\t\tB186543C2C6F49AE000B926A /* ShortcutConstants.swift in Sources */,\n\t\t\t\t11A45C792E34E63100CEB175 /* MediaChecker.swift in Sources */,\n\t\t\t\tB1D365CE2C6A979C0047BDBC /* LiveActivityModifier.swift in Sources */,\n\t\t\t\t1113ABD02E80E6BB00EC13B2 /* ThumbnailService.swift in Sources */,\n\t\t\t\t11CFC65B2E097E9D00748C80 /* WelcomeView.swift in Sources */,\n\t\t\t\t14D570C02C5EA5870011E668 /* AnimatedFace.swift in Sources */,\n\t\t\t\tB1D6FD432C6603730015F173 /* SoftwareUpdater.swift in Sources */,\n\t\t\t\t111BE9952ECF2DF40079DD4E /* DragDetector.swift in Sources */,\n\t\t\t\tB1D365D02C6A9A6C0047BDBC /* SystemEventIndicatorModifier.swift in Sources */,\n\t\t\t\t1194E87C2EA19E09009C82D6 /* ImageProcessingService.swift in Sources */,\n\t\t\t\t1194E8872EA6DDA7009C82D6 /* BoringNotchSkyLightWindow.swift in Sources */,\n\t\t\t\t14D570CB2C5F4B2C0011E668 /* BatteryStatusViewModel.swift in Sources */,\n\t\t\t\t9A0887322C7A693000C160EA /* TabButton.swift in Sources */,\n\t\t\t\t1153BD9C2D98853B00979FB0 /* NowPlayingController.swift in Sources */,\n\t\t\t\t1194E9402EACC652009C82D6 /* Color+AccentColor.swift in Sources */,\n\t\t\t\tB141C2412CA5F53F00AC8CC8 /* SparkleView.swift in Sources */,\n\t\t\t\t116398962DF5D6C00052E6AF /* CalendarServiceProviding.swift in Sources */,\n\t\t\t\t59D8C23C2E589FAA00147B33 /* VolumeManager.swift in Sources */,\n\t\t\t\t1163988D2DF5CAB40052E6AF /* EventModel.swift in Sources */,\n\t\t\t\t14D570B92C5E98A20011E668 /* drop.swift in Sources */,\n\t\t\t\t11EFCD702E8E92D600D0B974 /* ShelfItemViewModel.swift in Sources */,\n\t\t\t\t1153BDA72D99B22200979FB0 /* YouTubeMusicController.swift in Sources */,\n\t\t\t\t1163988F2DF5CC870052E6AF /* CalendarModel.swift in Sources */,\n\t\t\t\t1153BD9A2D98824300979FB0 /* SpotifyController.swift in Sources */,\n\t\t\t\t118EBE292E946B3F00D54B5A /* ShareServiceFinder.swift in Sources */,\n\t\t\t\tB19424092CD0FF01003E5DC2 /* LottieAnimationView.swift in Sources */,\n\t\t\t\tB1F747F92EC7E94000F841DB /* LottieView.swift in Sources */,\n\t\t\t\tB1C974342C642B6D0000E707 /* MarqueeTextView.swift in Sources */,\n\t\t\t\t14288DE82C6E01C800B9F80C /* ProgressIndicator.swift in Sources */,\n\t\t\t\t1113ABC52E80E27000EC13B2 /* ShelfItemView.swift in Sources */,\n\t\t\t\t1113ABC62E80E27000EC13B2 /* ShelfPersistenceService.swift in Sources */,\n\t\t\t\t1113ABC82E80E27000EC13B2 /* ShelfItem.swift in Sources */,\n\t\t\t\t1113ABCA2E80E27000EC13B2 /* ShelfSelectionModel.swift in Sources */,\n\t\t\t\t1113ABCB2E80E27000EC13B2 /* QuickLookService.swift in Sources */,\n\t\t\t\t1113ABCC2E80E27000EC13B2 /* ShelfDropService.swift in Sources */,\n\t\t\t\t1100292E2E86940F00035A57 /* QuickShareService.swift in Sources */,\n\t\t\t\t1113ABCE2E80E27000EC13B2 /* ShelfActionService.swift in Sources */,\n\t\t\t\t1160F8D82DD98230006FBB94 /* NotchShape.swift in Sources */,\n\t\t\t\t5917FD112E57891600E87F1C /* MediaKeyInterceptor.swift in Sources */,\n\t\t\t\t14C08BC12C8E03AD000F8AA0 /* NSImage+Extensions.swift in Sources */,\n\t\t\t\t149E0B9A2C737D40006418B1 /* WebcamView.swift in Sources */,\n\t\t\t\t1471639A2C5D35FF0068B555 /* MusicManager.swift in Sources */,\n\t\t\t\tB1B112932C6A577E00093D8F /* MouseTracker.swift in Sources */,\n\t\t\t\tB1C448962C9712C4001F0858 /* ActionBar.swift in Sources */,\n\t\t\t\t118EBE312E9717DB00D54B5A /* URL+SecurityScoped.swift in Sources */,\n\t\t\t\t1132E5162E777C140068732D /* YouTubeMusicAuthentication.swift in Sources */,\n\t\t\t\t14A7E5882C64A89C008C1BE9 /* HelloAnimation.swift in Sources */,\n\t\t\t\t111BEA612ED09B1B0079DD4E /* NSScreen+UUID.swift in Sources */,\n\t\t\t\t1153BD8F2D986B1F00979FB0 /* MediaControllerProtocol.swift in Sources */,\n\t\t\t\t9AB0C6BD2C73C9CB00F7CD30 /* NotchHomeView.swift in Sources */,\n\t\t\t\tB172AAC02C95DA0B001623F1 /* InlineHUD.swift in Sources */,\n\t\t\t\t14E9FEAA2C70BF610062E83F /* DownloadView.swift in Sources */,\n\t\t\t\tB1B112912C6A572100093D8F /* EditPanelView.swift in Sources */,\n\t\t\t\tB1C4489B2C97376A001F0858 /* TipStore.swift in Sources */,\n\t\t\t\t1153BD912D986DB300979FB0 /* PlaybackState.swift in Sources */,\n\t\t\t\tB1A78C822C8BA08100BD51B0 /* FullscreenMediaDetection.swift in Sources */,\n\t\t\t\t14E9FEAE2C7325770062E83F /* Button+Bouncing.swift in Sources */,\n\t\t\t\t14D570C92C5F38890011E668 /* BoringViewModel.swift in Sources */,\n\t\t\t\t14D570BC2C5E98EB0011E668 /* generic.swift in Sources */,\n\t\t\t\t118EBE272E92DE8400D54B5A /* NSMenu+AssociatedObject.swift in Sources */,\n\t\t\t\tB1CE8CFE2C6F659400DD9871 /* KeyboardShortcutsHelper.swift in Sources */,\n\t\t\t\t14D570C62C5F38210011E668 /* BoringHeader.swift in Sources */,\n\t\t\t\tB17266E32C65F7FB0031BA0D /* WhatsNewView.swift in Sources */,\n\t\t\t\t14C08BB62C8DE42D000F8AA0 /* CalendarManager.swift in Sources */,\n\t\t\t\t14D570CD2C5F4BB70011E668 /* BoringBattery.swift in Sources */,\n\t\t\t\tB10F84A32C6C9596009F3026 /* TestView.swift in Sources */,\n\t\t\t\t1443E7F32C609DCE0027C1FC /* matters.swift in Sources */,\n\t\t\t\t11C5E3162DFE88510065821E /* SettingsView.swift in Sources */,\n\t\t\t\t1153BD932D986E4300979FB0 /* AppleMusicController.swift in Sources */,\n\t\t\t\t11C5E3132DFE85970065821E /* SettingsWindowController.swift in Sources */,\n\t\t\t\t110029272E84FD4C00035A57 /* TemporaryFileStorageService.swift in Sources */,\n\t\t\t\t11CFC6652E09C7B300748C80 /* OnboardingFinishView.swift in Sources */,\n\t\t\t\t507266DB2C908E2E00A2D00D /* HoverButton.swift in Sources */,\n\t\t\t\t1471A8592C6281BD0058408D /* BoringNotchWindow.swift in Sources */,\n\t\t\t\t14CEF4182C5CAED300855D72 /* ContentView.swift in Sources */,\n\t\t\t\t9A987A0D2C73CA66005CA465 /* ShelfView.swift in Sources */,\n\t\t\t\t1132E5142E777B920068732D /* YouTubeMusicNetworking.swift in Sources */,\n\t\t\t\t1132E5122E777B6E0068732D /* YouTubeMusicModels.swift in Sources */,\n\t\t\t\t11CFC65F2E097F2F00748C80 /* OnboardingView.swift in Sources */,\n\t\t\t\t14CEF4162C5CAED300855D72 /* boringNotchApp.swift in Sources */,\n\t\t\t\tB17266DF2C64DFA00031BA0D /* BundleInfos.swift in Sources */,\n\t\t\t\t118EBE3B2E9720C500D54B5A /* ShelfStateViewModel.swift in Sources */,\n\t\t\t\t11F747CE2EC75CEA00F841DB /* DragPreviewView.swift in Sources */,\n\t\t\t\tF38DE6482D8243E7008B5C6D /* BatteryActivityManager.swift in Sources */,\n\t\t\t\tB10348D92C74E56000475897 /* ConditionalModifier.swift in Sources */,\n\t\t\t\t118D1FD12E98FF5F00A2FF63 /* SharingStateManager.swift in Sources */,\n\t\t\t\t14D570C22C5EAFBF0011E668 /* EmptyState.swift in Sources */,\n\t\t\t\t118EBE252E92DCCB00D54B5A /* AssociatedObject.swift in Sources */,\n\t\t\t\t11F748682EC9AC9600F841DB /* BoringNotchXPCHelperProtocol.swift in Sources */,\n\t\t\t\t11F748692EC9AC9600F841DB /* XPCHelperClient.swift in Sources */,\n\t\t\t\t118EBE2D2E97165600D54B5A /* Bookmark.swift in Sources */,\n\t\t\t\tB1FEB4992C7686630066EBBC /* PanGesture.swift in Sources */,\n\t\t\t\t11F748842ECB27DC00F841DB /* MusicSlotConfigurationView.swift in Sources */,\n\t\t\t\t9A0887352C7AFF8E00C160EA /* TabSelectionView.swift in Sources */,\n\t\t\t\t112FB7352CCF16F70015238C /* NotchSpaceManager.swift in Sources */,\n\t\t\t\t14FC6E502C7DED5600C7BEA5 /* DataTypes+Extensions.swift in Sources */,\n\t\t\t\t1153BD982D9881F900979FB0 /* AppleScriptHelper.swift in Sources */,\n\t\t\t\t11CFC6612E097F6800748C80 /* PermissionsRequestView.swift in Sources */,\n\t\t\t\t14D570D22C5F6C6A0011E668 /* BoringExtrasMenu.swift in Sources */,\n\t\t\t\t11D58EA22E760AE100FA8377 /* ImageService.swift in Sources */,\n\t\t\t\t11F748822ECB07A400F841DB /* MusicControlButton.swift in Sources */,\n\t\t\t\t14288DDC2C6E015000B9F80C /* AudioPlayer.swift in Sources */,\n\t\t\t\t149E0B972C737D00006418B1 /* WebcamManager.swift in Sources */,\n\t\t\t\t14288E0C2C6F8EC000B9F80C /* AppIcons.swift in Sources */,\n\t\t\t\tB1C448982C972CC4001F0858 /* ListItemPopover.swift in Sources */,\n\t\t\t\tB19016242CC15B5000E3F12E /* Constants.swift in Sources */,\n\t\t\t\t1100290C2E847E2800035A57 /* NSItemProvider+LoadHelpers.swift in Sources */,\n\t\t\t\t11CFC6632E09918400748C80 /* MusicControllerSelectionView.swift in Sources */,\n\t\t\t\tB1F0A0022E60000100000001 /* BrightnessManager.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t11F7485A2EC9AABA00F841DB /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 11F7484E2EC9AABA00F841DB /* BoringNotchXPCHelper */;\n\t\t\ttargetProxy = 11F748592EC9AABA00F841DB /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\t11F7485E2EC9AABA00F841DB /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = BoringNotchXPCHelper/BoringNotchXPCHelper.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 271;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = BoringNotchXPCHelper/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = BoringNotchXPCHelper;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 2.7.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = theboringteam.boringnotch.BoringNotchXPCHelper;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t11F7485F2EC9AABA00F841DB /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = BoringNotchXPCHelper/BoringNotchXPCHelper.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 271;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = BoringNotchXPCHelper/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = BoringNotchXPCHelper;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 2.7.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = theboringteam.boringnotch.BoringNotchXPCHelper;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t14CEF41F2C5CAED400855D72 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = targeted;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t14CEF4202C5CAED400855D72 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSDKROOT = macosx;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = targeted;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t14CEF4222C5CAED400855D72 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = boringNotch/boringNotch.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 271;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"boringNotch/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/mediaremote-adapter\",\n\t\t\t\t);\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = boringNotch/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = TheBoringNotch;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.utilities\";\n\t\t\t\tINFOPLIST_KEY_LSBackgroundOnly = NO;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSAppleEventsUsageDescription = \"This app uses AppleEvents to control music\";\n\t\t\t\tINFOPLIST_KEY_NSCalendarsUsageDescription = \"This app uses the calendar to display your calendar events\";\n\t\t\t\tINFOPLIST_KEY_NSCameraUsageDescription = \"This app uses the camera to display a live camera view\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tINFOPLIST_KEY_NSRemindersUsageDescription = \"This app uses Reminders to display your scheduled reminder in the calendar\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 2.7.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = theboringteam.boringnotch;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = targeted;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(SDKROOT)/System/Library/PrivateFrameworks\",\n\t\t\t\t);\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t14CEF4232C5CAED400855D72 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_ALLOW_ENTITLEMENTS_MODIFICATION = YES;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = boringNotch/boringNotch.entitlements;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCOMBINE_HIDPI_IMAGES = YES;\n\t\t\t\tCURRENT_PROJECT_VERSION = 271;\n\t\t\t\tDEAD_CODE_STRIPPING = YES;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"boringNotch/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tFRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(PROJECT_DIR)/mediaremote-adapter\",\n\t\t\t\t);\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = boringNotch/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = TheBoringNotch;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"public.app-category.utilities\";\n\t\t\t\tINFOPLIST_KEY_LSBackgroundOnly = NO;\n\t\t\t\tINFOPLIST_KEY_LSUIElement = YES;\n\t\t\t\tINFOPLIST_KEY_NSAppleEventsUsageDescription = \"This app uses AppleEvents to control music\";\n\t\t\t\tINFOPLIST_KEY_NSCalendarsUsageDescription = \"This app uses the calendar to display your calendar events\";\n\t\t\t\tINFOPLIST_KEY_NSCameraUsageDescription = \"This app uses the camera to display a live camera view\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"\";\n\t\t\t\tINFOPLIST_KEY_NSRemindersUsageDescription = \"This app uses Reminders to display your scheduled reminder in the calendar\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 14.0;\n\t\t\t\tMARKETING_VERSION = 2.7.3;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = theboringteam.boringnotch;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = targeted;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tSYSTEM_FRAMEWORK_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"$(SDKROOT)/System/Library/PrivateFrameworks\",\n\t\t\t\t);\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t11F7485D2EC9AABA00F841DB /* Build configuration list for PBXNativeTarget \"BoringNotchXPCHelper\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t11F7485E2EC9AABA00F841DB /* Debug */,\n\t\t\t\t11F7485F2EC9AABA00F841DB /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t14CEF40D2C5CAED200855D72 /* Build configuration list for PBXProject \"boringNotch\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t14CEF41F2C5CAED400855D72 /* Debug */,\n\t\t\t\t14CEF4202C5CAED400855D72 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t14CEF4212C5CAED400855D72 /* Build configuration list for PBXNativeTarget \"boringNotch\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t14CEF4222C5CAED400855D72 /* Debug */,\n\t\t\t\t14CEF4232C5CAED400855D72 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\t111BE95B2ECD71E10079DD4E /* XCRemoteSwiftPackageReference \"AsyncXPCConnection\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/ChimeHQ/AsyncXPCConnection\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.3.0;\n\t\t\t};\n\t\t};\n\t\t111BEA6D2ED166E20079DD4E /* XCRemoteSwiftPackageReference \"MacroVisionKit\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/TheBoredTeam/MacroVisionKit\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 0.2.0;\n\t\t\t};\n\t\t};\n\t\t1194E8832EA57D23009C82D6 /* XCRemoteSwiftPackageReference \"SkyLightWindow\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/Lakr233/SkyLightWindow\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.0.0;\n\t\t\t};\n\t\t};\n\t\t11F748712EC9DA9300F841DB /* XCRemoteSwiftPackageReference \"lottie-spm\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/airbnb/lottie-spm.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 4.5.2;\n\t\t\t};\n\t\t};\n\t\t14D032182C68F32E0096E6A1 /* XCRemoteSwiftPackageReference \"LaunchAtLogin-Modern\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sindresorhus/LaunchAtLogin-Modern\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.1.0;\n\t\t\t};\n\t\t};\n\t\t14D0321B2C68F3350096E6A1 /* XCRemoteSwiftPackageReference \"Sparkle\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sparkle-project/Sparkle\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 2.8.0;\n\t\t\t};\n\t\t};\n\t\t9A987A0E2C73CA8D005CA465 /* XCRemoteSwiftPackageReference \"swift-collections\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/apple/swift-collections.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.1.2;\n\t\t\t};\n\t\t};\n\t\t9A987A112C73CAA1005CA465 /* XCRemoteSwiftPackageReference \"Pow\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/EmergeTools/Pow\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.0.4;\n\t\t\t};\n\t\t};\n\t\tB1628B902CC260C0003D8DF3 /* XCRemoteSwiftPackageReference \"swiftui-introspect\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/siteline/swiftui-introspect\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 1.3.0;\n\t\t\t};\n\t\t};\n\t\tB18654372C6F4990000B926A /* XCRemoteSwiftPackageReference \"KeyboardShortcuts\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sindresorhus/KeyboardShortcuts\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 2.2.4;\n\t\t\t};\n\t\t};\n\t\tB19016202CC15B3D00E3F12E /* XCRemoteSwiftPackageReference \"Defaults\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/sindresorhus/Defaults\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 9.0.2;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t111BE95C2ECD71E10079DD4E /* AsyncXPCConnection */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 111BE95B2ECD71E10079DD4E /* XCRemoteSwiftPackageReference \"AsyncXPCConnection\" */;\n\t\t\tproductName = AsyncXPCConnection;\n\t\t};\n\t\t111BEA502ECFBF7F0079DD4E /* MacroVisionKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = MacroVisionKit;\n\t\t};\n\t\t111BEA5E2ED07A340079DD4E /* MacroVisionKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tproductName = MacroVisionKit;\n\t\t};\n\t\t111BEA6E2ED166E20079DD4E /* MacroVisionKit */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 111BEA6D2ED166E20079DD4E /* XCRemoteSwiftPackageReference \"MacroVisionKit\" */;\n\t\t\tproductName = MacroVisionKit;\n\t\t};\n\t\t1194E8842EA57D23009C82D6 /* SkyLightWindow */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 1194E8832EA57D23009C82D6 /* XCRemoteSwiftPackageReference \"SkyLightWindow\" */;\n\t\t\tproductName = SkyLightWindow;\n\t\t};\n\t\t11F748722EC9DA9300F841DB /* Lottie */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 11F748712EC9DA9300F841DB /* XCRemoteSwiftPackageReference \"lottie-spm\" */;\n\t\t\tproductName = Lottie;\n\t\t};\n\t\t14D032192C68F32E0096E6A1 /* LaunchAtLogin */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 14D032182C68F32E0096E6A1 /* XCRemoteSwiftPackageReference \"LaunchAtLogin-Modern\" */;\n\t\t\tproductName = LaunchAtLogin;\n\t\t};\n\t\t14D0321C2C68F3350096E6A1 /* Sparkle */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 14D0321B2C68F3350096E6A1 /* XCRemoteSwiftPackageReference \"Sparkle\" */;\n\t\t\tproductName = Sparkle;\n\t\t};\n\t\t9A987A0F2C73CA8D005CA465 /* Collections */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 9A987A0E2C73CA8D005CA465 /* XCRemoteSwiftPackageReference \"swift-collections\" */;\n\t\t\tproductName = Collections;\n\t\t};\n\t\tB1628B912CC260C0003D8DF3 /* SwiftUIIntrospect */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = B1628B902CC260C0003D8DF3 /* XCRemoteSwiftPackageReference \"swiftui-introspect\" */;\n\t\t\tproductName = SwiftUIIntrospect;\n\t\t};\n\t\tB18654382C6F4990000B926A /* KeyboardShortcuts */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = B18654372C6F4990000B926A /* XCRemoteSwiftPackageReference \"KeyboardShortcuts\" */;\n\t\t\tproductName = KeyboardShortcuts;\n\t\t};\n\t\tB19016212CC15B3D00E3F12E /* Defaults */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = B19016202CC15B3D00E3F12E /* XCRemoteSwiftPackageReference \"Defaults\" */;\n\t\t\tproductName = Defaults;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = 14CEF40A2C5CAED200855D72 /* Project object */;\n}\n"
  },
  {
    "path": "boringNotch.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "boringNotch.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "boringNotch.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "boringNotch.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"ab961f5de25797b1a82bd0f8c39b561332a3dfe10de0a118e1252262fbd45864\",\n  \"pins\" : [\n    {\n      \"identity\" : \"asyncxpcconnection\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/ChimeHQ/AsyncXPCConnection\",\n      \"state\" : {\n        \"revision\" : \"da31dbcaa1b57949e46dcc19360b17d1a8de06bd\",\n        \"version\" : \"1.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"defaults\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sindresorhus/Defaults\",\n      \"state\" : {\n        \"revision\" : \"8192b0986611e105355a9433e99bf5c79469fbbd\",\n        \"version\" : \"9.0.6\"\n      }\n    },\n    {\n      \"identity\" : \"keyboardshortcuts\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sindresorhus/KeyboardShortcuts\",\n      \"state\" : {\n        \"revision\" : \"1aef85578fdd4f9eaeeb8d53b7b4fc31bf08fe27\",\n        \"version\" : \"2.4.0\"\n      }\n    },\n    {\n      \"identity\" : \"launchatlogin-modern\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sindresorhus/LaunchAtLogin-Modern\",\n      \"state\" : {\n        \"revision\" : \"a04ec1c363be3627734f6dad757d82f5d4fa8fcc\",\n        \"version\" : \"1.1.0\"\n      }\n    },\n    {\n      \"identity\" : \"lottie-spm\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/airbnb/lottie-spm.git\",\n      \"state\" : {\n        \"revision\" : \"04f2fd18cc9404a0a0917265a449002674f24ec9\",\n        \"version\" : \"4.5.2\"\n      }\n    },\n    {\n      \"identity\" : \"macrovisionkit\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/TheBoredTeam/MacroVisionKit\",\n      \"state\" : {\n        \"revision\" : \"da481a6be8d8b1bf7fcb218507a72428bbcae7b0\",\n        \"version\" : \"0.2.0\"\n      }\n    },\n    {\n      \"identity\" : \"pow\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/EmergeTools/Pow\",\n      \"state\" : {\n        \"revision\" : \"a504eb6d144bcf49f4f33029a2795345cb39e6b4\",\n        \"version\" : \"1.0.5\"\n      }\n    },\n    {\n      \"identity\" : \"skylightwindow\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/Lakr233/SkyLightWindow\",\n      \"state\" : {\n        \"revision\" : \"b7bd99f62a0673a99bed4bfd31098ca1dcdd10eb\",\n        \"version\" : \"1.0.0\"\n      }\n    },\n    {\n      \"identity\" : \"sparkle\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/sparkle-project/Sparkle\",\n      \"state\" : {\n        \"revision\" : \"9a1d2a19d3595fcf8d9c447173f9a1687b3dcadb\",\n        \"version\" : \"2.8.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-collections\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/apple/swift-collections.git\",\n      \"state\" : {\n        \"revision\" : \"7b847a3b7008b2dc2f47ca3110d8c782fb2e5c7e\",\n        \"version\" : \"1.3.0\"\n      }\n    },\n    {\n      \"identity\" : \"swift-syntax\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/swiftlang/swift-syntax\",\n      \"state\" : {\n        \"revision\" : \"4799286537280063c85a32f09884cfbca301b1a1\",\n        \"version\" : \"602.0.0\"\n      }\n    },\n    {\n      \"identity\" : \"swiftui-introspect\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/siteline/swiftui-introspect\",\n      \"state\" : {\n        \"revision\" : \"807f73ce09a9b9723f12385e592b4e0aaebd3336\",\n        \"version\" : \"1.3.0\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "crowdin.yml",
    "content": "files:\n  - source: /boringNotch/Localizable.xcstrings\n    translation: /boringNotch/Localizable.xcstrings\n    multilingual: 1\n"
  },
  {
    "path": "mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/Resources/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple Computer//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>English</string>\n\t<key>CFBundleExecutable</key>\n\t<string>MediaRemoteAdapter</string>\n\t<key>CFBundleIconFile</key>\n\t<string></string>\n\t<key>CFBundleIdentifier</key>\n\t<string>com.vandenbe.MediaRemoteAdapter</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>MediaRemoteAdapter</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>0.1.0</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>0.1</string>\n\t<key>CSResourcesFileMapped</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "mediaremote-adapter/MediaRemoteAdapter.framework/Versions/A/_CodeSignature/CodeResources",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>files</key>\n\t<dict>\n\t\t<key>Resources/Info.plist</key>\n\t\t<data>\n\t\tM6AF1VWVJ1A/DSliCSjg170FqsY=\n\t\t</data>\n\t</dict>\n\t<key>files2</key>\n\t<dict>\n\t\t<key>Resources/Info.plist</key>\n\t\t<dict>\n\t\t\t<key>hash2</key>\n\t\t\t<data>\n\t\t\tz3yWmTAqjdrPJEZUQ+t6AVPhw0e/I8PAiVr0HIU2ivg=\n\t\t\t</data>\n\t\t</dict>\n\t</dict>\n\t<key>rules</key>\n\t<dict>\n\t\t<key>^Resources/</key>\n\t\t<true/>\n\t\t<key>^Resources/.*\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>optional</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1000</real>\n\t\t</dict>\n\t\t<key>^Resources/.*\\.lproj/locversion.plist$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1100</real>\n\t\t</dict>\n\t\t<key>^Resources/Base\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>1010</real>\n\t\t</dict>\n\t\t<key>^version.plist$</key>\n\t\t<true/>\n\t</dict>\n\t<key>rules2</key>\n\t<dict>\n\t\t<key>.*\\.dSYM($|/)</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>11</real>\n\t\t</dict>\n\t\t<key>^(.*/)?\\.DS_Store$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>2000</real>\n\t\t</dict>\n\t\t<key>^(Frameworks|SharedFrameworks|PlugIns|Plug-ins|XPCServices|Helpers|MacOS|Library/(Automator|Spotlight|LoginItems))/</key>\n\t\t<dict>\n\t\t\t<key>nested</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>10</real>\n\t\t</dict>\n\t\t<key>^.*</key>\n\t\t<true/>\n\t\t<key>^Info\\.plist$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^PkgInfo$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^Resources/</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^Resources/.*\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>optional</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1000</real>\n\t\t</dict>\n\t\t<key>^Resources/.*\\.lproj/locversion.plist$</key>\n\t\t<dict>\n\t\t\t<key>omit</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>1100</real>\n\t\t</dict>\n\t\t<key>^Resources/Base\\.lproj/</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>1010</real>\n\t\t</dict>\n\t\t<key>^[^/]+$</key>\n\t\t<dict>\n\t\t\t<key>nested</key>\n\t\t\t<true/>\n\t\t\t<key>weight</key>\n\t\t\t<real>10</real>\n\t\t</dict>\n\t\t<key>^embedded\\.provisionprofile$</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t\t<key>^version\\.plist$</key>\n\t\t<dict>\n\t\t\t<key>weight</key>\n\t\t\t<real>20</real>\n\t\t</dict>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "mediaremote-adapter/mediaremote-adapter.pl",
    "content": "#!/usr/bin/perl\n# Copyright (c) 2025 Jonas van den Berg\n# This file is licensed under the BSD 3-Clause License.\n\n# For usage information read below or run the script without arguments.\n\nuse strict;\nuse warnings;\nuse DynaLoader;\nuse File::Spec;\nuse File::Basename;\n\nsub print_help() {\n  print <<'HELP';\nUsage:\n  mediaremote-adapter.pl FRAMEWORK_PATH [TEST_CLIENT_PATH]\n                         [FUNCTION [PARAMS|OPTIONS...]]\n\nFRAMEWORK_PATH:\n  Absolute path to the MediaRemoteAdapter.framework directory\n\nTEST_CLIENT_PATH: (optional)\n  Absolute path to the MediaRemoteAdapterTestClient executable. Only for \"test\"\n\nFUNCTION:\n  stream   Streams now playing information (as diff by default)\n  get      Prints now playing information once with all available metadata\n  send     Sends a command to the now playing application\n  seek     Seeks to a specific timeline position\n  shuffle  Sets the shuffle mode\n  repeat   Sets the repeat mode\n  speed    Sets the playback speed\n  test     Tests if the adapter is entitled to use the MediaRemote framework.\n           An exit code other than 0 indicates the adapter is non-functional\n\nPARAMS:\n  send(command)\n    command: The MRCommand ID as a number (e.g. kMRPlay = 0)\n  seek(position)\n    position: The timeline position in microseconds\n  shuffle(mode)\n    mode: The shuffle mode\n  repeat(mode)\n    mode: The repeat mode\n  speed(speed)\n    speed: The playback speed\n\nOPTIONS:\n  get\n    --now: Adds an \"elapsedTimeNow\" key with an estimation of the current\n      elapsed playback time. This estimation may be off by up to a second.\n      To determine a more accurate time without polling \"get\" continuously,\n      calculate it using the \"elapsedTime\" and \"timestamp\" keys. \"elapsedTime\"\n      contains the elapsed time at the time that is stored in \"timestamp\".\n  stream\n    --no-diff: Disable diffing and always dump all metadata\n    --debounce=N: Delay in milliseconds to prevent spam (0 by default)\n  get, stream\n    --micros: Replaces the following time keys with microsecond equivalents\n      \"duration\" -> \"durationMicros\"\n      \"elapsedTime\" -> \"elapsedTimeMicros\"\n      \"elapsedTimeNow\" -> \"elapsedTimeNowMicros\"\n      \"timestamp\" -> \"timestampEpochMicros\" (converted to epoch time)\n    --human-readable, -h: Makes values human-readable. Use only for debugging.\n      The JSON output is pretty-printed and the following keys are adapted:\n      \"artworkData\" -> Binary data is truncated to a shorter representation\n\nExamples (script name and framework path omitted):\n  stream --no-diff --debounce=100\n  send 2    # Toggles play/pause in the media player (kMRATogglePlayPause)\n  repeat 3  # Sets the repeat mode to \"playlist\" (kMRARepeatModePlaylist)\n\nHELP\n  exit 0;\n}\n\nif (!defined $ARGV[1]) {\n  print_help();\n}\n\nsub fail {\n  my ($error) = @_;\n  print STDERR \"$error\\n\";\n  exit 1;\n}\n\nfail \"Framework path not provided\" unless @ARGV >= 1;\n\nmy $framework_path = shift @ARGV;\n\n# Optionally accept MEDIAREMOTEADAPTER_TEST_CLIENT_PATH path as second argument\nmy $maybe_helper_path = $ARGV[0] // '';\nif ($maybe_helper_path =~ m{/}){\n  my $helper_path = shift @ARGV;\n  $ENV{MEDIAREMOTEADAPTER_TEST_CLIENT_PATH} = $helper_path;\n}\n\nif (!defined $ARGV[0]) {\n  print_help();\n}\n\nmy $framework_basename = File::Basename::basename($framework_path);\nfail \"Provided path is not a framework: $framework_path\"\n  unless $framework_basename =~ s/\\.framework$//;\n\nmy $framework = File::Spec->catfile($framework_path, $framework_basename);\nfail \"Framework not found at $framework\" unless -e $framework;\n\nmy $handle = DynaLoader::dl_load_file($framework, 0)\n  or fail \"Failed to load framework: $framework\";\nmy $function_name = shift @ARGV or fail \"Missing function name\";\nfail \"Invalid function name: '$function_name'\"\n  unless $function_name eq \"stream\"\n  || $function_name eq \"get\"\n  || $function_name eq \"send\"\n  || $function_name eq \"seek\"\n  || $function_name eq \"shuffle\"\n  || $function_name eq \"repeat\"\n  || $function_name eq \"speed\"\n  || $function_name eq \"test\";\n\nsub parse_options {\n  my ($start_index) = @_;\n  my %arg_map;\n  my $i = $start_index;\n  while ($i <= $#ARGV) {\n    my $arg = $ARGV[$i];\n    if ($arg =~ /^--([a-z\\\\-]+)(?:=(.*))?$/) {\n      my $key = $1;\n      my $value = defined $2 ? $2 : undef;\n      $arg_map{$key} = $value;\n      splice @ARGV, $i, 1;\n    }\n    elsif ($arg =~ /^-([a-zA-Z]+)$/) {\n      my @flags = split //, $1;\n      $arg_map{$_} = undef for @flags;\n      splice @ARGV, $i, 1;\n    }\n    else {\n      $i++;\n    }\n  }\n  return \\%arg_map;\n}\n\nsub env_func {\n  my $symbol_name = shift;\n  return \"${symbol_name}_env\";\n}\n\nsub set_env_param {\n  my ($func, $index, $name, $value) = @_;\n  $ENV{\"MEDIAREMOTEADAPTER_PARAM_${func}_${index}_${name}\"} = \"$value\";\n}\n\nsub set_env_option_unsafe {\n  my ($name, $value) = @_;\n  $name =~ s/-/_/g;\n  $ENV{\"MEDIAREMOTEADAPTER_OPTION_${name}\"} = defined $value ? \"$value\" : \"\";\n}\n\nsub set_env_option {\n  my ($options, $key) = @_;\n  my $value = $options->{$key};\n  if (defined $value) {\n    fail \"Unexpected value for option '$key'\";\n  }\n  set_env_option_unsafe($key, $value);\n}\n\nsub set_env_option_value {\n  my ($options, $key) = @_;\n  my $value = $options->{$key};\n  if (!defined $value) {\n    fail \"Missing value for option '$key'\";\n  }\n  set_env_option_unsafe($key, $value);\n}\n\nmy $symbol_name = \"adapter_$function_name\";\nif ($function_name eq \"send\") {\n  my $id = shift @ARGV;\n  fail \"Missing ID for '$function_name' command\" unless defined $id;\n  set_env_param($symbol_name, 0, \"command\", \"$id\");\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"stream\") {\n  my $options = parse_options(0);\n  foreach my $key (keys %{$options}) {\n    if ($key eq \"no-diff\") {\n      set_env_option($options, $key);\n    }\n    elsif ($key eq \"debounce\") {\n      set_env_option_value($options, $key);\n    }\n    elsif ($key eq \"micros\") {\n      set_env_option($options, $key);\n    }\n    elsif ($key eq \"human-readable\" || $key eq \"h\") {\n      set_env_option($options, \"human-readable\");\n    }\n    else {\n      fail \"Unrecognized option '$key'\";\n    }\n  }\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"get\") {\n  my $options = parse_options(0);\n  foreach my $key (keys %{$options}) {\n    if ($key eq \"micros\") {\n      set_env_option($options, $key);\n    }\n    elsif ($key eq \"human-readable\" || $key eq \"h\") {\n      set_env_option($options, \"human-readable\");\n    }\n    elsif ($key eq \"now\") {\n      set_env_option($options, $key);\n    }\n    else {\n      fail \"Unrecognized option '$key'\";\n    }\n  }\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"seek\") {\n  my $position = shift @ARGV;\n  fail \"Missing position for '$function_name' command\" unless defined $position;\n  set_env_param($symbol_name, 0, \"position\", \"$position\");\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"shuffle\") {\n  my $mode = shift @ARGV;\n  fail \"Missing mode for '$function_name' command\" unless defined $mode;\n  set_env_param($symbol_name, 0, \"mode\", \"$mode\");\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"repeat\") {\n  my $mode = shift @ARGV;\n  fail \"Missing mode for '$function_name' command\" unless defined $mode;\n  set_env_param($symbol_name, 0, \"mode\", \"$mode\");\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"speed\") {\n  my $speed = shift @ARGV;\n  fail \"Missing speed for '$function_name' command\" unless defined $speed;\n  set_env_param($symbol_name, 0, \"speed\", \"$speed\");\n  $symbol_name = env_func($symbol_name);\n}\nelsif ($function_name eq \"test\") {\n  $symbol_name = \"adapter_test\";\n}\n\nif (defined shift @ARGV) {\n  fail \"Too many arguments\";\n}\n\nmy $symbol = DynaLoader::dl_find_symbol($handle, \"$symbol_name\")\n  or fail \"Symbol '$symbol_name' not found in $framework\";\nDynaLoader::dl_install_xsub(\"main::$function_name\", $symbol);\n\neval {\n  no strict \"refs\";\n  &{\"main::$function_name\"}();\n};\nif ($@) {\n  fail \"Error executing $function_name: $@\";\n}\n"
  },
  {
    "path": "updater/appcast.xml",
    "content": "<?xml version=\"1.0\" standalone=\"yes\"?>\n<rss xmlns:sparkle=\"http://www.andymatuschak.org/xml-namespaces/sparkle\" version=\"2.0\">\n    <channel>\n        <item>\n            <title>2.7.3</title>\n            <pubDate>Mon, 24 Nov 2025 08:07:37 +0000</pubDate>\n            <link>https://github.com/TheBoredTeam/boring.notch/releases</link>\n            <sparkle:version>271</sparkle:version>\n            <sparkle:shortVersionString>2.7.3</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>🚀 v2.7.3— Flying Rabbit 🐇🪽</h2>\r\n<h3> Fixes: </h3>\r\n<ul>\r\n    <li><strong>Fixed regression in album artwork view<strong></li>\r\n    <li><strong>Fixed HUD for older versions of macOS<strong></li>\r\n    <li><strong>Added volume feedback for HUD when enabled in macOS settings<strong></li>\r\n    <li><strong>Added option to display percentages for HUDs<strong></li>\r\n    <li><strong>Created a new custom HUD for when the notch is open<strong></li>\r\n    <li><strong>Fixed NowPlaying controller launching Apple Music by itself<strong></li>\r\n    <li><strong>Improved responsiveness of volume slider<strong></li>\r\n    <li><strong>Improved onboarding experience<strong></li>\r\n</ul>\r\n\r\n<h2>🚀 v2.7 — Flying Rabbit 🐇🪽</h2>\r\n<h3>Shelf 2.0</h3>\r\n<p>Major update with improved stability, enhanced functionality, and a refreshed UI.</p>\r\n<ul>\r\n    <li><strong>New Context Menu</strong> – Right-click in the notch to access various file actions</li>\r\n    <li><strong>Multi-Item Selection</strong> – Hold <kbd>⇧</kbd> for consecutive or <kbd>⌘</kbd> for non-consecutive\r\n        selections</li>\r\n    <li><strong>Double-Click to Open</strong> – Double-click on selected files to open them</li>\r\n    <li><strong>Move by Default</strong> – Dragging files now moves them; hold <kbd>⌥</kbd> to copy</li>\r\n    <li><strong>Simplified Removal</strong> – Files can be removed after dragging (configurable in Settings)</li>\r\n    <li><strong>Expanded Drag Detection</strong> – The shelf opens when files are dragged into the notch area</li>\r\n    <li><strong>Expanded Sharing</strong> – More sharing services available in Settings</li>\r\n</ul>\r\n<h3>Complete HUD Replacement</h3>\r\n<p>Full support for macOS system controls:</p>\r\n<ul>\r\n    <li><strong>Screen Brightness</strong>, <strong>Keyboard Brightness</strong>, and <strong>System Volume</strong>\r\n    </li>\r\n    <li><strong>Keyboard Brightness Controls:</strong> <kbd>⌘ + Brightness Down/Up</kbd></li>\r\n    <li><strong>Option (<kbd>⌥</kbd>)</strong> – Triggers alternate action configured in Settings</li>\r\n    <li><strong>Option + Shift (<kbd>⌥</kbd> <kbd>⇧</kbd>)</strong> – Adjustments in smaller increments</li>\r\n</ul>\r\n<h3>Music Enhancements</h3>\r\n<ul>\r\n    <li><strong>YouTube Music Support Rewritten</strong> – Now powered by WebSocket for better accuracy and performance\r\n    </li>\r\n</ul>\r\n<blockquote>\r\n    <p><strong>Note:</strong> Requires version <strong>3.11+</strong> of <a\r\n            href=\"https://github.com/pear-devs/pear-desktop\">YouTube Music/Pear Desktop</a>. Updating is strongly\r\n        recommended.</p>\r\n</blockquote>\r\n<ul>\r\n    <li><strong>Redesigned Music Controls</strong> – Fully customizable:\r\n        <ul>\r\n            <li>Skip backward/forward by 15 seconds</li>\r\n            <li>Adjust music app volume</li>\r\n            <li>Mark favorite songs  (Apple Music &amp;\r\n        YouTube MusiZ</li>\r\n            <li>Rearrange or remove existing controls</li>\r\n        </ul>\r\n    </li>\r\n    <li><strong>Optimized Spotify Artwork Cache</strong> – Significantly reduced storage usage with automatic cleanup\r\n    </li>\r\n    <li><strong>Lyrics (Beta)</strong> – View synchronized lyrics for currently playing songs</li>\r\n</ul>\r\n<h3>Calendar Improvements</h3>\r\n<ul>\r\n    <li>New setting to hide all-day events</li>\r\n    <li>New setting to auto-scroll to next current or upcoming event</li>\r\n    <li>New setting to prevent truncation of long event names</li>\r\n</ul>\r\n<h3>Improved Window Behavior</h3>\r\n<ul>\r\n    <li><strong>Enhanced Fullscreen Detection</strong> – Significantly more reliable</li>\r\n    <li><strong>Better Edge Handling</strong> – Fixed top edge cursor issues</li>\r\n    <li><strong>Reduced Title Bar Interference</strong> – Less intrusive during fullscreen</li>\r\n    <li><strong>Lock Screen Support</strong> – Notch now appears on lock screen</li>\r\n    <li><strong>Screenshot Privacy</strong> – Hide notch from screenshots and recordings</li>\r\n</ul>\r\n<h3>Advanced Settings</h3>\r\n<ul>\r\n    <li>Cleaner interface with lesser-used settings moved to Advanced</li>\r\n    <li>Accent color override reintroduced</li>\r\n</ul>\r\n<h3>General Enhancements</h3>\r\n<ul>\r\n    <li>Numerous UI fixes and polish</li>\r\n    <li>Localization updates across all supported languages</li>\r\n</ul>\r\n<hr>\r\n<h2>👋 New Contributors</h2>\r\n<p>@azhao4227 · @bueckerlars · @TheMalenia · @Corentin132 · @SupKittyMeow · @M7T5M3P · @charshith · @Decryptu ·\r\n    @EnesCinr · @lambegraham</p>\r\n<hr>\r\n<h3>📄 Full Changelog</h3>\r\n<p><a href=\"https://github.com/TheBoredTeam/boring.notch/compare/v2.7-rc.3...v2.7\">Compare v2.7-rc.3 → v2.7</a></p>]]></description>\n            <enclosure url=\"https://github.com/TheBoredTeam/boring.notch/releases/download/v2.7.3/boringNotch.dmg\" length=\"9727030\" type=\"application/octet-stream\" sparkle:edSignature=\"gl1LSheePaR/h57sppaFMnIPGyX2AC3jITvMZzmYc95wiuuDAKLdSSjKAiUqDSvHqW8hx/fgCbS7878yUH3JCQ==\"/>\n        </item>\n        <item>\n            <title>2.7.2</title>\n            <pubDate>Sat, 22 Nov 2025 22:40:29 +0000</pubDate>\n            <link>https://github.com/TheBoredTeam/boring.notch/releases</link>\n            <sparkle:version>262</sparkle:version>\n            <sparkle:shortVersionString>2.7.2</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>🚀 v2.7.2— Flying Rabbit 🐇🪽</h2>\r\n<h3> Fixes: </h3>\r\n<ul>\r\n    <li><strong>Fixed default sneak peak<strong></li>\r\n</ul>\r\n<h2>🚀 v2.7.1 — Flying Rabbit 🐇🪽</h2>\r\n<h3> Fixes: </h3>\r\n<ul>\r\n    <li><strong>Fixed update signing</strong></li>\r\n    <li><strong>Improved animations</strong></li>\r\n     <li><strong>Fixed shadow clipping</strong></li>\r\n     <h3>📄 Full Changelog</h3>\r\n      <p><a href=\"https://github.com/TheBoredTeam/boring.notch/compare/v2.7...v2.7.1\">Compare v2.7 → v2.7.1</a></p>\r\n</ul>\r\n\r\n<h2>🚀 v2.7 — Flying Rabbit 🐇🪽</h2>\r\n<h3>Shelf 2.0</h3>\r\n<p>Major update with improved stability, enhanced functionality, and a refreshed UI.</p>\r\n<ul>\r\n    <li><strong>New Context Menu</strong> – Right-click in the notch to access various file actions</li>\r\n    <li><strong>Multi-Item Selection</strong> – Hold <kbd>⇧</kbd> for consecutive or <kbd>⌘</kbd> for non-consecutive\r\n        selections</li>\r\n    <li><strong>Double-Click to Open</strong> – Double-click on selected files to open them</li>\r\n    <li><strong>Move by Default</strong> – Dragging files now moves them; hold <kbd>⌥</kbd> to copy</li>\r\n    <li><strong>Simplified Removal</strong> – Files can be removed after dragging (configurable in Settings)</li>\r\n    <li><strong>Expanded Drag Detection</strong> – The shelf opens when files are dragged into the notch area</li>\r\n    <li><strong>Expanded Sharing</strong> – More sharing services available in Settings</li>\r\n</ul>\r\n<h3>Complete HUD Replacement</h3>\r\n<p>Full support for macOS system controls:</p>\r\n<ul>\r\n    <li><strong>Screen Brightness</strong>, <strong>Keyboard Brightness</strong>, and <strong>System Volume</strong>\r\n    </li>\r\n    <li><strong>Keyboard Brightness Controls:</strong> <kbd>⌘ + Brightness Down/Up</kbd></li>\r\n    <li><strong>Option (<kbd>⌥</kbd>)</strong> – Triggers alternate action configured in Settings</li>\r\n    <li><strong>Option + Shift (<kbd>⌥</kbd> <kbd>⇧</kbd>)</strong> – Adjustments in smaller increments</li>\r\n</ul>\r\n<h3>Music Enhancements</h3>\r\n<ul>\r\n    <li><strong>YouTube Music Support Rewritten</strong> – Now powered by WebSocket for better accuracy and performance\r\n    </li>\r\n</ul>\r\n<blockquote>\r\n    <p><strong>Note:</strong> Requires version <strong>3.11+</strong> of <a\r\n            href=\"https://github.com/pear-devs/pear-desktop\">YouTube Music/Pear Desktop</a>. Updating is strongly\r\n        recommended.</p>\r\n</blockquote>\r\n<ul>\r\n    <li><strong>Redesigned Music Controls</strong> – Fully customizable:\r\n        <ul>\r\n            <li>Skip backward/forward by 15 seconds</li>\r\n            <li>Adjust music app volume</li>\r\n            <li>Mark favorite songs  (Apple Music &amp;\r\n        YouTube MusiZ</li>\r\n            <li>Rearrange or remove existing controls</li>\r\n        </ul>\r\n    </li>\r\n    <li><strong>Optimized Spotify Artwork Cache</strong> – Significantly reduced storage usage with automatic cleanup\r\n    </li>\r\n    <li><strong>Lyrics (Beta)</strong> – View synchronized lyrics for currently playing songs</li>\r\n</ul>\r\n<h3>Calendar Improvements</h3>\r\n<ul>\r\n    <li>New setting to hide all-day events</li>\r\n    <li>New setting to auto-scroll to next current or upcoming event</li>\r\n    <li>New setting to prevent truncation of long event names</li>\r\n</ul>\r\n<h3>Improved Window Behavior</h3>\r\n<ul>\r\n    <li><strong>Enhanced Fullscreen Detection</strong> – Significantly more reliable</li>\r\n    <li><strong>Better Edge Handling</strong> – Fixed top edge cursor issues</li>\r\n    <li><strong>Reduced Title Bar Interference</strong> – Less intrusive during fullscreen</li>\r\n    <li><strong>Lock Screen Support</strong> – Notch now appears on lock screen</li>\r\n    <li><strong>Screenshot Privacy</strong> – Hide notch from screenshots and recordings</li>\r\n</ul>\r\n<h3>Advanced Settings</h3>\r\n<ul>\r\n    <li>Cleaner interface with lesser-used settings moved to Advanced</li>\r\n    <li>Accent color override reintroduced</li>\r\n</ul>\r\n<h3>General Enhancements</h3>\r\n<ul>\r\n    <li>Numerous UI fixes and polish</li>\r\n    <li>Localization updates across all supported languages</li>\r\n</ul>\r\n<hr>\r\n<h2>👋 New Contributors</h2>\r\n<p>@azhao4227 · @bueckerlars · @TheMalenia · @Corentin132 · @SupKittyMeow · @M7T5M3P · @charshith · @Decryptu ·\r\n    @EnesCinr · @lambegraham</p>\r\n<hr>\r\n<h3>📄 Full Changelog</h3>\r\n<p><a href=\"https://github.com/TheBoredTeam/boring.notch/compare/v2.7-rc.3...v2.7\">Compare v2.7-rc.3 → v2.7</a></p>]]></description>\n            <enclosure url=\"https://github.com/TheBoredTeam/boring.notch/releases/download/v2.7.2/boringNotch.dmg\" length=\"9684618\" type=\"application/octet-stream\" sparkle:edSignature=\"8fWrEF4oezQcnF/iM9X0B4bGIe4zPnJSzpEuwq3OfyZ5TphlXf85DmRL21PWDIYuUIGuX6DaUyzv7NOB9Fk9Cg==\"/>\n        </item>\n        <item>\n            <title>2.7.1</title>\n            <pubDate>Sat, 22 Nov 2025 22:14:41 +0000</pubDate>\n            <link>https://github.com/TheBoredTeam/boring.notch/releases</link>\n            <sparkle:version>260</sparkle:version>\n            <sparkle:shortVersionString>2.7.1</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.0</sparkle:minimumSystemVersion>\n            <description><![CDATA[<h2>🚀 v2.7.1 — Flying Rabbit 🐇🪽</h2>\r\n<h3> Fixes: </h3>\r\n<ul>\r\n    <li><strong>Fixed update signing</strong></li>\r\n    <li><strong>Improved animations</strong></li>\r\n     <li><strong>Fixed shadow clipping</strong></li>\r\n     <h3>📄 Full Changelog</h3>\r\n      <p><a href=\"https://github.com/TheBoredTeam/boring.notch/compare/v2.7...v2.7.1\">Compare v2.7 → v2.7.1</a></p>\r\n</ul>\r\n\r\n<h2>🚀 v2.7 — Flying Rabbit 🐇🪽</h2>\r\n<h3>Shelf 2.0</h3>\r\n<p>Major update with improved stability, enhanced functionality, and a refreshed UI.</p>\r\n<ul>\r\n    <li><strong>New Context Menu</strong> – Right-click in the notch to access various file actions</li>\r\n    <li><strong>Multi-Item Selection</strong> – Hold <kbd>⇧</kbd> for consecutive or <kbd>⌘</kbd> for non-consecutive\r\n        selections</li>\r\n    <li><strong>Double-Click to Open</strong> – Double-click on selected files to open them</li>\r\n    <li><strong>Move by Default</strong> – Dragging files now moves them; hold <kbd>⌥</kbd> to copy</li>\r\n    <li><strong>Simplified Removal</strong> – Files can be removed after dragging (configurable in Settings)</li>\r\n    <li><strong>Expanded Drag Detection</strong> – The shelf opens when files are dragged into the notch area</li>\r\n    <li><strong>Expanded Sharing</strong> – More sharing services available in Settings</li>\r\n</ul>\r\n<h3>Complete HUD Replacement</h3>\r\n<p>Full support for macOS system controls:</p>\r\n<ul>\r\n    <li><strong>Screen Brightness</strong>, <strong>Keyboard Brightness</strong>, and <strong>System Volume</strong>\r\n    </li>\r\n    <li><strong>Keyboard Brightness Controls:</strong> <kbd>⌘ + Brightness Down/Up</kbd></li>\r\n    <li><strong>Option (<kbd>⌥</kbd>)</strong> – Triggers alternate action configured in Settings</li>\r\n    <li><strong>Option + Shift (<kbd>⌥</kbd> <kbd>⇧</kbd>)</strong> – Adjustments in smaller increments</li>\r\n</ul>\r\n<h3>Music Enhancements</h3>\r\n<ul>\r\n    <li><strong>YouTube Music Support Rewritten</strong> – Now powered by WebSocket for better accuracy and performance\r\n    </li>\r\n</ul>\r\n<blockquote>\r\n    <p><strong>Note:</strong> Requires version <strong>3.11+</strong> of <a\r\n            href=\"https://github.com/pear-devs/pear-desktop\">YouTube Music/Pear Desktop</a>. Updating is strongly\r\n        recommended.</p>\r\n</blockquote>\r\n<ul>\r\n    <li><strong>Redesigned Music Controls</strong> – Fully customizable:\r\n        <ul>\r\n            <li>Skip backward/forward by 15 seconds</li>\r\n            <li>Adjust music app volume</li>\r\n            <li>Mark favorite songs  (Apple Music &amp;\r\n        YouTube MusiZ</li>\r\n            <li>Rearrange or remove existing controls</li>\r\n        </ul>\r\n    </li>\r\n    <li><strong>Optimized Spotify Artwork Cache</strong> – Significantly reduced storage usage with automatic cleanup\r\n    </li>\r\n    <li><strong>Lyrics (Beta)</strong> – View synchronized lyrics for currently playing songs</li>\r\n</ul>\r\n<h3>Calendar Improvements</h3>\r\n<ul>\r\n    <li>New setting to hide all-day events</li>\r\n    <li>New setting to auto-scroll to next current or upcoming event</li>\r\n    <li>New setting to prevent truncation of long event names</li>\r\n</ul>\r\n<h3>Improved Window Behavior</h3>\r\n<ul>\r\n    <li><strong>Enhanced Fullscreen Detection</strong> – Significantly more reliable</li>\r\n    <li><strong>Better Edge Handling</strong> – Fixed top edge cursor issues</li>\r\n    <li><strong>Reduced Title Bar Interference</strong> – Less intrusive during fullscreen</li>\r\n    <li><strong>Lock Screen Support</strong> – Notch now appears on lock screen</li>\r\n    <li><strong>Screenshot Privacy</strong> – Hide notch from screenshots and recordings</li>\r\n</ul>\r\n<h3>Advanced Settings</h3>\r\n<ul>\r\n    <li>Cleaner interface with lesser-used settings moved to Advanced</li>\r\n    <li>Accent color override reintroduced</li>\r\n</ul>\r\n<h3>General Enhancements</h3>\r\n<ul>\r\n    <li>Numerous UI fixes and polish</li>\r\n    <li>Localization updates across all supported languages</li>\r\n</ul>\r\n<hr>\r\n<h2>👋 New Contributors</h2>\r\n<p>@azhao4227 · @bueckerlars · @TheMalenia · @Corentin132 · @SupKittyMeow · @M7T5M3P · @charshith · @Decryptu ·\r\n    @EnesCinr · @lambegraham</p>\r\n<hr>\r\n<h3>📄 Full Changelog</h3>\r\n<p><a href=\"https://github.com/TheBoredTeam/boring.notch/compare/v2.7-rc.3...v2.7\">Compare v2.7-rc.3 → v2.7</a></p>]]></description>\n            <enclosure url=\"https://github.com/TheBoredTeam/boring.notch/releases/download/v2.7.1/boringNotch.dmg\" length=\"9684624\" type=\"application/octet-stream\" sparkle:edSignature=\"d+5NbhE7/CjLdcwOyPmxKSC7r3BAFG1Fe6t9voGwT/w6yUfaTMn6d30pUFhPqWihWFhT4cwtPOPztzEV35XBAQ==\"/>\n        </item>\n        <item>\n            <title>2.7-rc.1 Flying Rabbit 🐇🪽</title>\n            <pubDate>Sun, 27 Jul 2025 09:41:40 +0530</pubDate>\n            <sparkle:version>2.7-rc.1+3</sparkle:version>\n            <sparkle:shortVersionString>2.7-rc.1</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.2</sparkle:minimumSystemVersion>\n            <description><![CDATA[\r\n                    <div>\r\n                        <h1>🐇 boring.notch v2.7 – Flying Rabbit RC 1</h1>\r\n                        <p>Release Candidate - July 27, 2025</p>\r\n                            <h2>✨ What's New & Improved</h2>\r\n                            <ul>\r\n                            <li><b>🛠️ Fixed hanging issues:</b> Resolved stability concerns by addressing test instability. (by @Alexander5015)</li>\r\n                            <li><b>📅 Calendar Settings Resolved:</b> Calendar settings now update correctly with authorization; proper show/hide.</li>\r\n                            <li><b>🎵 YouTube Music Controller:</b>\r\n                                <ul>\r\n                                <li>Improved shuffle and repeat UX, beta features for shuffle/repeat now available.</li>\r\n                                <li>Fixed seek control bugs and added forced polling.</li>\r\n                                </ul>\r\n                                (by @pranav1st & @Alexander5015)\r\n                            </li>\r\n                            <li><b>🔲 MediaRemoteAdapter.Framework updated</b></li>\r\n                            <li><b>🔃 Button order and repeat toggle improved:</b> Better player button logic and new repeat toggle.</li>\r\n                            <li><b>🐞 Now Playing Controller Beta:</b> Beta enhancements and settings (known bugs remain for testing).</li>\r\n                            <li><b>♻️ Shuffle is now always enabled.</b></li>\r\n                            <li><b>💡 Refactored Shuffle & Repeat:</b> toggleShuffle/toggleRepeat refactored for improved experience.</li>\r\n                            </ul>\r\n                        <p>— The Boring Team</p>\r\n                    </div>\r\n                ]]></description>\n            <enclosure url=\"https://github.com/TheBoredTeam/boring.notch/releases/download/v2.7-rc.1/Flying_Rabbit.dmg\" length=\"9033202\" type=\"application/octet-stream\" sparkle:edSignature=\"z1SbQHDCg1D+6m811f4ubdinUiXvoYF4Ra2xlzS3NSAlUqmmIpdzCp1MHMv5J9ddlxTws3rG48OCJfZRAXHHAw==\"/>\n        </item>\n        <item>\n            <title>2.7-rc.0 Flying Rabbit 🐇🪽</title>\n            <pubDate>Sat, 26 Jul 2025 12:45:19 +0530</pubDate>\n            <sparkle:version>2.7-rc.0+1</sparkle:version>\n            <sparkle:shortVersionString>2.7-rc.0</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.2</sparkle:minimumSystemVersion>\n            <description><![CDATA[\r\n                    <h1 id=\"-release-v2-7-flying-rabbit-rc-0-boring-notch\">🎉 Release v2.7 Flying Rabbit RC 0 — Boring Notch</h1>\r\n                    <p>We're thrilled to announce <strong>Boring Notch v2.7: Flying Rabbit RC 0</strong>, packed with powerful new features, polish, and community contributions!</p>\r\n                    <h2 id=\"-highlights\">🚀 Highlights</h2>\r\n                    <ul>\r\n                    <li><strong>🔋 Enhanced Battery Status \\&amp; Charging Experience</strong>\r\n                    Thanks to @AlexLemus-Dev<ul>\r\n                    <li>More informative battery menu: percentage, max capacity, charging, low power, and status icons</li>\r\n                    <li>Configurable battery indicators, notifications, and display options</li>\r\n                    <li>Interactive battery icon with detailed info and instant System Preferences access</li>\r\n                    <li>Visual alignment and consistent styling (macOS-like bolt/plug icons, dark mode, etc.)</li>\r\n                    <li>Optimized and documented code, robust error handling, and improved performance</li>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/437\">Details \\&amp; Demo</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>🖥️ Fullscreen Detection \\&amp; Playback Management Fixes</strong>\r\n                    @Alexander5015 improved reliability of media controls during fullscreen transitions<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/449\">PR #449</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>🦷 Allow 0 Height Notch</strong>\r\n                    Now supports notches with zero height for enhanced layout flexibility<ul>\r\n                    <li>Thanks, @yaxarat! <a href=\"https://github.com/TheBoredTeam/boring.notch/pull/397\">PR #397</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>🎛️ Multiple Media Controllers</strong>\r\n                    Control and display several media controllers at once<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/460\">PR #460</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>👀 Sneak Peek \\&amp; Jiggle Fixes</strong>\r\n                    Re-added sneak peek and improved animation stability<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/409\">PR #409</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>🗓️ New Calendar Service</strong>\r\n                    Seamlessly integrates your calendar into Boring Notch<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/589\">PR #589</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>📄 Updated LICENSE</strong>\r\n                    Keeping compliance and clarity up to date<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/590\">PR #590</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>🆕 Onboarding \\&amp; Better Settings Window</strong>\r\n                    Streamlined onboarding and redesigned settings for easy configuration<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/600\">PR #600</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>📸 Camera Toggle Feature</strong>\r\n                    New camera quick toggle, privacy-first!<ul>\r\n                    <li>Added by @Steve-sy <a href=\"https://github.com/TheBoredTeam/boring.notch/pull/598\">PR #598</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    <li><strong>🎵 MediaRemote Adapter Support</strong>\r\n                    Improved compatibility with MediaRemote devices<ul>\r\n                    <li><a href=\"https://github.com/TheBoredTeam/boring.notch/pull/631\">PR #631</a></li>\r\n                    </ul>\r\n                    </li>\r\n                    </ul>\r\n                    <h2 id=\"-welcoming-first-time-contributors-\">🆕 Welcoming First-Time Contributors!</h2>\r\n                    <table>\r\n                    <thead>\r\n                    <tr>\r\n                    <th style=\"text-align:left\">Contributor</th>\r\n                    <th style=\"text-align:left\">PR Link</th>\r\n                    </tr>\r\n                    </thead>\r\n                    <tbody>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@sancho1952007</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/431\">https://github.com/TheBoredTeam/boring.notch/pull/431</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@Ein-Tim</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/405\">https://github.com/TheBoredTeam/boring.notch/pull/405</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@AlexLemus-Dev</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/437\">https://github.com/TheBoredTeam/boring.notch/pull/437</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@yaxarat</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/397\">https://github.com/TheBoredTeam/boring.notch/pull/397</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@ShirakawaMio</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/399\">https://github.com/TheBoredTeam/boring.notch/pull/399</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@divyanshu0469</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/454\">https://github.com/TheBoredTeam/boring.notch/pull/454</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@oorischubert</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/409\">https://github.com/TheBoredTeam/boring.notch/pull/409</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@Al3Gr</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/493\">https://github.com/TheBoredTeam/boring.notch/pull/493</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@ChemicalChaos-Fabian42</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/509\">https://github.com/TheBoredTeam/boring.notch/pull/509</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@Davetheword</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/501\">https://github.com/TheBoredTeam/boring.notch/pull/501</a>)</td>\r\n                    </tr>\r\n                    <tr>\r\n                    <td style=\"text-align:left\">@Steve-sy</td>\r\n                    <td style=\"text-align:left\">(<a href=\"https://github.com/TheBoredTeam/boring.notch/pull/598\">https://github.com/TheBoredTeam/boring.notch/pull/598</a>)</td>\r\n                    </tr>\r\n                    </tbody>\r\n                    </table>\r\n                    <h2 id=\"-other-improvements\">🛠️ Other Improvements</h2>\r\n                    <ul>\r\n                    <li>Visual and animation refinements for battery/media indicators</li>\r\n                    <li>Enhanced error handling, code reorganization, and extensive documentation</li>\r\n                    <li>Optimizations for consistent performance across all system conditions</li>\r\n                    </ul>\r\n                    <p>Thank you 🌟 to everyone—new and returning—for their effort in making this release feature-rich, steady, and super fun!</p>\r\n                    <p><strong>Try Boring Notch v2.7 — Flying Rabbit RC 0 and let us know what you think!</strong></p>\r\n                    <p><em>— The Boring Notch Team</em></p>\r\n                ]]></description>\n            <enclosure url=\"https://github.com/TheBoredTeam/boring.notch/releases/download/v2.7-rc.0/Flying_Rabbit.dmg\" length=\"9000118\" type=\"application/octet-stream\" sparkle:edSignature=\"G5D2zsLFBR0Ua9b1EkLD8jjdkpMA10P1C9KmEL2Drg1WNwCnNAY+lGmljx1UQ+qDyqpjjMrOoGClevHd/276BQ==\"/>\n        </item>\n        <item>\n            <title>2.6 🎉 Wolf Painting 🐺</title>\n            <pubDate>Sun, 23 Feb 2025 22:02:47 +0530</pubDate>\n            <sparkle:version>2.6+1</sparkle:version>\n            <sparkle:shortVersionString>2.6</sparkle:shortVersionString>\n            <sparkle:minimumSystemVersion>14.2</sparkle:minimumSystemVersion>\n            <description><![CDATA[\r\n                <h1>🎉 v2.6 🚀 Wolf Painting 🐺</h1>\r\n                <p>We're thrilled to announce the release of Wolf Painting version 2.6! 🎊 This major update brings numerous improvements and new features that will make your experience even more enjoyable.</p><br />\r\n                <a href=\"https://github.com/TheBoredTeam/boring.notch/releases/download/wolf.painting/WolfPainting.dmg\"><h1>Download from here</h1></a>\r\n                <h2>🤔 What's Changed? 🤓</h2>\r\n                <ul>\r\n                <li>🧠 Improved memory management and thread safety across the app</li>\r\n                <li>🎥 Enhanced webcam handling with better session lifecycle management</li>\r\n                <li>🎨 Added option to choose between classic and modern notch animations</li>\r\n                <li>⚡️ Improved fullscreen detection with Finder exclusion</li>\r\n                <li>🔄 Better hover state handling and reduced animation flicker</li>\r\n                <li>🔋 Enhanced power status notifications and battery indicators</li>\r\n                <li>⚙️ Improved screen lock and display change handling</li>\r\n                <li>🎵 Better music playback state tracking and elapsed time accuracy</li>\r\n                <li>✨ Added \"Buy us a coffee\" option in Welcome screen</li>\r\n                <li>⚠️ Added warning badges for unsupported extensions</li>\r\n                <li>🐛 Various bug fixes and stability improvements</li>\r\n                <li>For more details, check out <a href=\"https://theboring.name\">our website</a></li>\r\n                </ul>\r\n            ]]></description>\n            <enclosure url=\"https://github.com/TheBoredTeam/boring.notch/releases/download/wolf.painting/WolfPainting.dmg\" length=\"8722621\" type=\"application/octet-stream\" sparkle:edSignature=\"j2Wp9e23UvN7yx2NztYfGbUiKfCU4qc2xpsPwBPx/ERUz01pJTfJlf8oN0Wri8gcho42/xfcfnWNwChfMPzrCQ==\"/>\n        </item>\n    </channel>\n</rss>"
  }
]