diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml
index f2342b06b..92ca3420f 100644
--- a/.github/workflows/ci-cd.yml
+++ b/.github/workflows/ci-cd.yml
@@ -4,6 +4,12 @@ on:
   pull_request: # Runs whenever a pull request is created or updated
   push: # Runs whenever a commit is pushed to the repository
     branches: [master, develop, beta, hotfix/*] # ...on any of these branches
+  workflow_call: # Runs whenever another workflow calls this workflow
+    inputs:
+      ref:
+        description: 'The branch or other ref to run this workflow on'
+        type: string
+        required: true
   workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
 
 concurrency:
@@ -18,29 +24,45 @@ env:
   SKIP_CLEANUP: true
 
 jobs:
-  build-and-test-and-maybe-deploy:
+  set-environment: # this job just a trick to DRY the environment logic
+    name: Set Environment
     runs-on: ubuntu-latest
-    environment: >-
-      ${{
-        (
-          (github.ref == 'refs/heads/master') && 'production'
-        ) ||
-        (
-          (
-            (github.ref == 'refs/heads/develop') ||
-            (github.ref == 'refs/heads/beta') ||
-            startsWith(github.ref, 'refs/heads/hotfix/') ||
-            startsWith(github.ref, 'refs/heads/release/')
-          ) && 'staging'
-        ) ||
-        ''
-      }}
     env:
-      # SCRATCH_ENV comes from the GitHub Environment
-      # See https://github.com/scratchfoundation/scratch-www/settings/variables/actions
-      SCRATCH_SHOULD_DEPLOY: ${{ vars.SCRATCH_ENV != '' }}
+      GH_ENVIRONMENT: >-
+        ${{
+          (
+            (github.ref == 'refs/heads/master') && 'production'
+          ) ||
+          (
+            (
+              (github.ref == 'refs/heads/develop') ||
+              (github.ref == 'refs/heads/beta') ||
+              startsWith(github.ref, 'refs/heads/hotfix/') ||
+              startsWith(github.ref, 'refs/heads/release/')
+            ) && 'staging'
+          ) ||
+          ''
+        }}
+    steps:
+      # apparently you're not allowed to have a job with no steps
+      - name: Report environment
+        run: |
+          echo "Environment: ${{ env.GH_ENVIRONMENT }}"
+    outputs:
+      environment: ${{ env.GH_ENVIRONMENT }}
+      should_deploy: ${{ (env.GH_ENVIRONMENT || '') != '' }}
+  build:
+    name: Build and Unit Tests
+    runs-on: ubuntu-latest
+    needs: set-environment
+    environment: ${{ needs.set-environment.outputs.environment }}
+    env:
+      SCRATCH_ENV: ${{ needs.set-environment.outputs.environment }}
+      SCRATCH_SHOULD_DEPLOY: ${{ needs.set-environment.outputs.should_deploy }}
     steps:
       - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+        with:
+          ref: ${{ inputs.ref }} # this should be empty to use `checkout`'s default UNLESS provided by workflow_call
       - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
         with:
           cache: 'npm'
@@ -48,7 +70,8 @@ jobs:
       - name: info
         run: |
           cat <<EOF
-          Scratch environment: ${{ vars.SCRATCH_ENV }}
+          Scratch environment: ${SCRATCH_ENV}
+          Should deploy: ${SCRATCH_SHOULD_DEPLOY}
           Node version: $(node --version)
           NPM version: $(npm --version)
           github.workflow: ${{ github.workflow }}
@@ -56,11 +79,6 @@ jobs:
           github.head_ref: ${{ github.head_ref }}
           github.ref: ${{ github.ref }}
           EOF
-      - name: setup Python
-        uses: actions/setup-python@v5
-        with:
-          python-version: '3.12'
-      - run: pip install s3cmd==2.4.0
       - run: npm --production=false ci
       - run: mkdir -p ./test/results
       - name: lint
@@ -77,7 +95,6 @@ jobs:
           CLOUDDATA_HOST: ${{ secrets.CLOUDDATA_HOST }}
           PROJECT_HOST: ${{ secrets.PROJECT_HOST }}
           STATIC_HOST: ${{ secrets.STATIC_HOST }}
-          SCRATCH_ENV: ${{ vars.SCRATCH_ENV }}
           ONBOARDING_TEST_ACTIVE: "${{ vars.ONBOARDING_TEST_ACTIVE }}"
           ONBOARDING_TEST_PROJECT_IDS: "${{ vars.ONBOARDING_TEST_PROJECT_IDS }}"
           ONBOARDING_TESTING_STARTING_DATE: "${{ vars.ONBOARDING_TESTING_STARTING_DATE }}"
@@ -92,8 +109,75 @@ jobs:
           JEST_JUNIT_OUTPUT_NAME=localization-jest-results.xml npm run test:unit:jest:localization -- --reporters=jest-junit
           npm run test:unit:tap -- --output-file ./test/results/unit-raw.tap
           npm run test:unit:convertReportToXunit
+      - name: compress artifacts
+        if: ${{ needs.set-environment.outputs.should_deploy == 'true' }}
+        env:
+          ZSTD_NBTHREADS: 0 # tell zstd to automatically choose thread count
+        run: |
+          mkdir -p artifacts/
+          tar -cavf artifacts/build.tar.zst build
+      - name: "upload artifact: build"
+        if: ${{ needs.set-environment.outputs.should_deploy == 'true' }}
+        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
+        with:
+          name: build
+          path: artifacts/build.tar.zst
+          compression-level: 0 # don't re-compress compressed data
+      - name: save node_modules for other jobs
+        if: ${{ needs.set-environment.outputs.should_deploy == 'true' }}
+        uses: actions/cache/save@v4
+        with:
+          path: node_modules
+          key: ${{ runner.os }}-node_modules-${{ hashFiles('.nvmrc', 'package-lock.json') }}
+  deploy:
+    name: Deploy
+    runs-on: ubuntu-latest
+    needs: [set-environment, build]
+    environment: ${{ needs.set-environment.outputs.environment }}
+    env:
+      SCRATCH_ENV: ${{ needs.set-environment.outputs.environment }}
+    if: ${{ needs.set-environment.outputs.should_deploy == 'true' }}
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+        with:
+          ref: ${{ inputs.ref }} # this should be empty to use `checkout`'s default UNLESS provided by workflow_call
+      - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
+        with:
+          node-version-file: '.nvmrc'
+      - name: info
+        run: |
+          cat <<EOF
+          Scratch environment: ${SCRATCH_ENV}
+          Node version: $(node --version)
+          NPM version: $(npm --version)
+          EOF
+      - name: retrieve node_modules
+        uses: actions/cache/restore@v4
+        with:
+          fail-on-cache-miss: true
+          path: node_modules
+          key: ${{ runner.os }}-node_modules-${{ hashFiles('.nvmrc', 'package-lock.json') }}
+      - name: setup Python
+        uses: actions/setup-python@v5
+        with:
+          python-version: '3.13'
+      - run: pip install s3cmd==2.4.0
+      - name: download artifacts
+        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
+        with:
+          # if `name` is not specified, it will download all artifacts
+          path: artifacts/
+          # upload-artifact makes a ZIP file with the provided `name` (default: `artifact`)
+          # by default, download-artifact will extract that ZIP file into a subdirectory with the same name
+          merge-multiple: true # don't make a subdirectory for each artifact name
+      - name: extract artifacts
+        env:
+          ZSTD_NBTHREADS: 0 # tell zstd to automatically choose thread count
+        run: |
+          ls -lRh artifacts/
+          tar -xavf artifacts/build.tar.zst
       - name: deploy
-        if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
+        # This also uses SCRATCH_ENV, defined at the job level
         run: npm run deploy
         env:
           S3_LOCAL_DIR: build
@@ -103,23 +187,51 @@ jobs:
           FASTLY_API_KEY: ${{ secrets.FASTLY_API_KEY }}
           FASTLY_SERVICE_ID: ${{ secrets.FASTLY_SERVICE_ID }}
           SCRATCH_BRANCH: ${{ github.ref_name }}
-          SCRATCH_ENV: ${{ vars.SCRATCH_ENV }}
           SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS: ${{ secrets.SLACK_WEBHOOK_CIRCLECI_NOTIFICATIONS }} # TODO: rename or replace
           SLACK_WEBHOOK_ENGINEERING: ${{ secrets.SLACK_WEBHOOK_ENGINEERING }}
           SLACK_WEBHOOK_MODS: ${{ secrets.SLACK_WEBHOOK_MODS }}
           RADISH_URL: ${{ vars.RADISH_URL }}
-      - name: integration tests
-        if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
+  integration-tests:
+    name: Production Integration Tests
+    runs-on: ubuntu-latest
+    needs: [set-environment, deploy]
+    environment: ${{ needs.set-environment.outputs.environment }}
+    # GHA can't reach staging
+    # TODO: Run integration tests against localhost? Not only would that let us test any and all branches,
+    # but we could also, I dunno, consider fully testing _before_ we deploy? :sweat_smile:
+    if: ${{ needs.set-environment.outputs.environment == 'production' }}
+    steps:
+      - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4
+        with:
+          ref: ${{ inputs.ref }} # this should be empty to use `checkout`'s default UNLESS provided by workflow_call
+      - uses: actions/setup-node@60edb5dd545a775178f52524783378180af0d1f8 # v4
+        with:
+          node-version-file: '.nvmrc'
+      - name: retrieve node_modules
+        uses: actions/cache/restore@v4
+        with:
+          fail-on-cache-miss: true
+          path: node_modules
+          key: ${{ runner.os }}-node_modules-${{ hashFiles('.nvmrc', 'package-lock.json') }}
+      - name: download artifacts
+        uses: actions/download-artifact@fa0a91b85d4f404e444e00e005971372dc801d16 # v4
+        with:
+          # if `name` is not specified, it will download all artifacts
+          path: artifacts/
+          # upload-artifact makes a ZIP file with the provided `name` (default: `artifact`)
+          # by default, download-artifact will extract that ZIP file into a subdirectory with the same name
+          merge-multiple: true # don't make a subdirectory for each artifact name
+      - name: extract artifacts
+        env:
+          ZSTD_NBTHREADS: 0 # tell zstd to automatically choose thread count
         run: |
-          if [ '${{ vars.SCRATCH_ENV }}' == 'production' ]; then
-            # if the health test fails, there's no point in trying to run the integration tests
-            npm run test:health
-            # health test succeeded, so proceed with integration tests
-            JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration -- --reporters=jest-junit
-          else
-            echo "Skipping integration tests for non-production environment"
-            echo "Please run the integration tests manually if necessary"
-          fi
+          tar -xavf artifacts/build.tar.zst
+      - name: integration tests
+        run: |
+          # if the health test fails, there's no point in trying to run the integration tests
+          npm run test:health
+          # health test succeeded, so proceed with integration tests
+          JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration -- --reporters=jest-junit
         env:
           ROOT_URL: ${{ secrets.ROOT_URL }}
 
@@ -144,11 +256,3 @@ jobs:
           OWNED_UNSHARED_SCRATCH2_PROJECT_ID: ${{ secrets.OWNED_UNSHARED_SCRATCH2_PROJECT_ID }}
           TEST_STUDIO_ID: ${{ secrets.TEST_STUDIO_ID }}
           RATE_LIMIT_CHECK: ${{ secrets.RATE_LIMIT_CHECK }}
-      - name: compress artifact
-        if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
-        run: tar -czvf build.tgz build
-      - name: upload artifact
-        if: ${{ env.SCRATCH_SHOULD_DEPLOY == 'true' }}
-        uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4
-        with:
-          path: build.tgz
diff --git a/.github/workflows/prepare-release.yml b/.github/workflows/prepare-release.yml
index f6a8f00f9..f09e9f307 100644
--- a/.github/workflows/prepare-release.yml
+++ b/.github/workflows/prepare-release.yml
@@ -1,6 +1,6 @@
 name: Create release branch and PRs
 
-# Controls when the action will run. 
+# Controls when the action will run.
 on:
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
@@ -12,6 +12,9 @@ jobs:
     # The type of runner that the job will run on
     runs-on: ubuntu-latest
 
+    outputs:
+      branch: ${{ steps.vars.outputs.branch }}
+
     # Steps represent a sequence of tasks that will be executed as part of the job
     steps:
       # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
@@ -47,4 +50,10 @@ jobs:
           pr_title: "[Master] ${{ steps.vars.outputs.branch }}"
           github_token: ${{ secrets.GITHUB_TOKEN }}
           pr_allow_empty: true
-
+  call-ci-cd:
+    name: Call CI/CD workflow
+    needs: build
+    uses: ./.github/workflows/ci-cd.yml
+    secrets: inherit
+    with:
+      ref: ${{ needs.build.outputs.branch }}
diff --git a/package-lock.json b/package-lock.json
index 1b6f82694..094ff0ea9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -23,12 +23,12 @@
         "react-twitter-embed": "3.0.3",
         "react-use": "17.6.0",
         "scratch-parser": "6.0.0",
-        "scratch-storage": "^4.0.69"
+        "scratch-storage": "^4.0.91"
       },
       "devDependencies": {
         "@babel/cli": "7.26.4",
-        "@babel/core": "7.26.9",
-        "@babel/eslint-parser": "7.26.8",
+        "@babel/core": "7.26.10",
+        "@babel/eslint-parser": "7.26.10",
         "@babel/plugin-syntax-dynamic-import": "7.8.3",
         "@babel/plugin-transform-async-to-generator": "7.25.9",
         "@babel/plugin-transform-object-rest-spread": "7.25.9",
@@ -42,7 +42,7 @@
         "@scratch/scratch-gui": "^11.1.0-spork.15",
         "@types/jest": "29.5.14",
         "async": "3.2.6",
-        "autoprefixer": "10.4.20",
+        "autoprefixer": "10.4.21",
         "babel-loader": "8.4.1",
         "babel-plugin-transform-require-context": "0.1.1",
         "bowser": "1.9.4",
@@ -110,10 +110,10 @@
         "redux-mock-store": "1.5.5",
         "redux-thunk": "2.4.2",
         "regenerator-runtime": "0.13.9",
-        "sass": "1.85.1",
+        "sass": "1.86.0",
         "sass-loader": "10.5.2",
-        "scratch-l10n": "5.0.152",
-        "selenium-webdriver": "4.29.0",
+        "scratch-l10n": "5.0.180",
+        "selenium-webdriver": "4.30.0",
         "slick-carousel": "1.8.1",
         "stream-browserify": "3.0.0",
         "style-loader": "4.0.0",
@@ -240,22 +240,22 @@
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.26.9",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
-      "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz",
+      "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
         "@babel/code-frame": "^7.26.2",
-        "@babel/generator": "^7.26.9",
+        "@babel/generator": "^7.26.10",
         "@babel/helper-compilation-targets": "^7.26.5",
         "@babel/helper-module-transforms": "^7.26.0",
-        "@babel/helpers": "^7.26.9",
-        "@babel/parser": "^7.26.9",
+        "@babel/helpers": "^7.26.10",
+        "@babel/parser": "^7.26.10",
         "@babel/template": "^7.26.9",
-        "@babel/traverse": "^7.26.9",
-        "@babel/types": "^7.26.9",
+        "@babel/traverse": "^7.26.10",
+        "@babel/types": "^7.26.10",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
@@ -271,9 +271,9 @@
       }
     },
     "node_modules/@babel/eslint-parser": {
-      "version": "7.26.8",
-      "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.8.tgz",
-      "integrity": "sha512-3tBctaHRW6xSub26z7n8uyOTwwUsCdvIug/oxBH9n6yCO5hMj2vwDJAo7RbBMKrM7P+W2j61zLKviJQFGOYKMg==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.26.10.tgz",
+      "integrity": "sha512-QsfQZr4AiLpKqn7fz+j7SN+f43z2DZCgGyYbNJ2vJOqKfG4E6MZer1+jqGZqKJaxq/gdO2DC/nUu45+pOL5p2Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -290,14 +290,14 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.26.9",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
-      "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.10.tgz",
+      "integrity": "sha512-rRHT8siFIXQrAYOYqZQVsAr8vJ+cBNqcVAY6m5V8/4QqzaPl+zDBe6cLEPRDuNOUf3ww8RfJVlOyQMoSI+5Ang==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/parser": "^7.26.9",
-        "@babel/types": "^7.26.9",
+        "@babel/parser": "^7.26.10",
+        "@babel/types": "^7.26.10",
         "@jridgewell/gen-mapping": "^0.3.5",
         "@jridgewell/trace-mapping": "^0.3.25",
         "jsesc": "^3.0.2"
@@ -558,27 +558,27 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.26.9",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
-      "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.10.tgz",
+      "integrity": "sha512-UPYc3SauzZ3JGgj87GgZ89JVdC5dj0AoetR5Bw6wj4niittNyFh6+eOGonYvJ1ao6B8lEa3Q3klS7ADZ53bc5g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/template": "^7.26.9",
-        "@babel/types": "^7.26.9"
+        "@babel/types": "^7.26.10"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.26.9",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
-      "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
+      "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.26.9"
+        "@babel/types": "^7.26.10"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -2029,17 +2029,17 @@
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.26.9",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
-      "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.10.tgz",
+      "integrity": "sha512-k8NuDrxr0WrPH5Aupqb2LCVURP/S0vBEn5mK6iH+GIYob66U5EtoZvcdudR2jQ4cmTwhEwW1DLB+Yyas9zjF6A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/code-frame": "^7.26.2",
-        "@babel/generator": "^7.26.9",
-        "@babel/parser": "^7.26.9",
+        "@babel/generator": "^7.26.10",
+        "@babel/parser": "^7.26.10",
         "@babel/template": "^7.26.9",
-        "@babel/types": "^7.26.9",
+        "@babel/types": "^7.26.10",
         "debug": "^4.3.1",
         "globals": "^11.1.0"
       },
@@ -2048,9 +2048,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.26.9",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
-      "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
+      "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -3925,6 +3925,39 @@
         "react": "^16.0.0"
       }
     },
+    "node_modules/@scratch/scratch-gui/node_modules/scratch-l10n": {
+      "version": "5.0.152",
+      "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-5.0.152.tgz",
+      "integrity": "sha512-tqK+2KRIFNyJBq53rwzx4J2pNcORJYp+spMfYQf8CNJI9iDlA4a6Q6twofW+1PhP8NFQIwo1r9T1WZdv+yaj3A==",
+      "dev": true,
+      "license": "AGPL-3.0-only",
+      "dependencies": {
+        "@transifex/api": "4.3.0",
+        "download": "8.0.0",
+        "transifex": "1.6.6"
+      },
+      "bin": {
+        "build-i18n-src": "scripts/build-i18n-src.js",
+        "tx-push-src": "scripts/tx-push-src.js"
+      }
+    },
+    "node_modules/@scratch/scratch-gui/node_modules/scratch-storage": {
+      "version": "4.0.70",
+      "resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-4.0.70.tgz",
+      "integrity": "sha512-iuK6bcr01gqxhL2CB8kvob4pTe7mQJbxQu0Y3T3oFDHIioIqmNTkgGdXICRrXW/hc3W7vjsh7XkILLxYCSmUAw==",
+      "dev": true,
+      "license": "AGPL-3.0-only",
+      "dependencies": {
+        "@babel/runtime": "^7.21.0",
+        "arraybuffer-loader": "^1.0.3",
+        "base64-js": "^1.3.0",
+        "buffer": "6.0.3",
+        "cross-fetch": "^4.1.0",
+        "fastestsmallesttextencoderdecoder": "^1.0.7",
+        "js-md5": "^0.7.3",
+        "minilog": "^3.1.0"
+      }
+    },
     "node_modules/@scratch/scratch-render": {
       "version": "11.1.0-spork.15",
       "resolved": "https://registry.npmjs.org/@scratch/scratch-render/-/scratch-render-11.1.0-spork.15.tgz",
@@ -4052,6 +4085,23 @@
         "microee": "0.0.6"
       }
     },
+    "node_modules/@scratch/scratch-vm/node_modules/scratch-storage": {
+      "version": "4.0.70",
+      "resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-4.0.70.tgz",
+      "integrity": "sha512-iuK6bcr01gqxhL2CB8kvob4pTe7mQJbxQu0Y3T3oFDHIioIqmNTkgGdXICRrXW/hc3W7vjsh7XkILLxYCSmUAw==",
+      "dev": true,
+      "license": "AGPL-3.0-only",
+      "dependencies": {
+        "@babel/runtime": "^7.21.0",
+        "arraybuffer-loader": "^1.0.3",
+        "base64-js": "^1.3.0",
+        "buffer": "6.0.3",
+        "cross-fetch": "^4.1.0",
+        "fastestsmallesttextencoderdecoder": "^1.0.7",
+        "js-md5": "^0.7.3",
+        "minilog": "^3.1.0"
+      }
+    },
     "node_modules/@sinclair/typebox": {
       "version": "0.27.8",
       "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -5605,9 +5655,9 @@
       "license": "MIT"
     },
     "node_modules/autoprefixer": {
-      "version": "10.4.20",
-      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz",
-      "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==",
+      "version": "10.4.21",
+      "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz",
+      "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==",
       "dev": true,
       "funding": [
         {
@@ -5625,11 +5675,11 @@
       ],
       "license": "MIT",
       "dependencies": {
-        "browserslist": "^4.23.3",
-        "caniuse-lite": "^1.0.30001646",
+        "browserslist": "^4.24.4",
+        "caniuse-lite": "^1.0.30001702",
         "fraction.js": "^4.3.7",
         "normalize-range": "^0.1.2",
-        "picocolors": "^1.0.1",
+        "picocolors": "^1.1.1",
         "postcss-value-parser": "^4.2.0"
       },
       "bin": {
@@ -6930,9 +6980,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001700",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001700.tgz",
-      "integrity": "sha512-2S6XIXwaE7K7erT8dY+kLQcpa5ms63XlRkMkReXjle+kf6c5g38vyMl+Z5y8dSxOFDhcFe+nxnn261PLxBSQsQ==",
+      "version": "1.0.30001703",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001703.tgz",
+      "integrity": "sha512-kRlAGTRWgPsOj7oARC9m1okJEXdL/8fekFVcxA8Hl7GH4r/sN4OJn/i6Flde373T50KS7Y37oFbMwlE8+F42kQ==",
       "dev": true,
       "funding": [
         {
@@ -22412,9 +22462,9 @@
       "license": "MIT"
     },
     "node_modules/sass": {
-      "version": "1.85.1",
-      "resolved": "https://registry.npmjs.org/sass/-/sass-1.85.1.tgz",
-      "integrity": "sha512-Uk8WpxM5v+0cMR0XjX9KfRIacmSG86RH4DCCZjLU2rFh5tyutt9siAXJ7G+YfxQ99Q6wrRMbMlVl6KqUms71ag==",
+      "version": "1.86.0",
+      "resolved": "https://registry.npmjs.org/sass/-/sass-1.86.0.tgz",
+      "integrity": "sha512-zV8vGUld/+mP4KbMLJMX7TyGCuUp7hnkOScgCMsWuHtns8CWBoz+vmEhoGMXsaJrbUP8gj+F1dLvVe79sK8UdA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -22638,9 +22688,9 @@
       }
     },
     "node_modules/scratch-l10n": {
-      "version": "5.0.152",
-      "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-5.0.152.tgz",
-      "integrity": "sha512-tqK+2KRIFNyJBq53rwzx4J2pNcORJYp+spMfYQf8CNJI9iDlA4a6Q6twofW+1PhP8NFQIwo1r9T1WZdv+yaj3A==",
+      "version": "5.0.180",
+      "resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-5.0.180.tgz",
+      "integrity": "sha512-o7V5dV3ldrTrPF/YwijUBwBCN+KZ2cj7EBa3KSHrhjtF5UpazKavv3TgXWFfBMHh5Gp5ZfljMnfdyyhBPh9sZw==",
       "dev": true,
       "license": "AGPL-3.0-only",
       "dependencies": {
@@ -22761,9 +22811,9 @@
       }
     },
     "node_modules/scratch-storage": {
-      "version": "4.0.70",
-      "resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-4.0.70.tgz",
-      "integrity": "sha512-iuK6bcr01gqxhL2CB8kvob4pTe7mQJbxQu0Y3T3oFDHIioIqmNTkgGdXICRrXW/hc3W7vjsh7XkILLxYCSmUAw==",
+      "version": "4.0.92",
+      "resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-4.0.92.tgz",
+      "integrity": "sha512-OHv4A6pvzUopGJ0ATM6nQUZVoBVbw2VrZXZGpi7EFo9v32FVD6tv5mt7bI05MI6uJMJt1oPr7qbXQ1KhdtrTog==",
       "license": "AGPL-3.0-only",
       "dependencies": {
         "@babel/runtime": "^7.21.0",
@@ -22845,9 +22895,9 @@
       "license": "MIT"
     },
     "node_modules/selenium-webdriver": {
-      "version": "4.29.0",
-      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.29.0.tgz",
-      "integrity": "sha512-8XPGtDoji5xk7ZUCzFT1rqHmCp67DCzESsttId7DzmrJmlTRmRLF6X918rbwclcH89amcBNM4zB3lVPj404I0g==",
+      "version": "4.30.0",
+      "resolved": "https://registry.npmjs.org/selenium-webdriver/-/selenium-webdriver-4.30.0.tgz",
+      "integrity": "sha512-3DGtQI/xyAg05SrqzzpFaXRWYL+Kku3fsikCoBaxApKzhBMUX5UiHdPb2je2qKMf2PjJiEFaj0L5xELHYRbYMA==",
       "dev": true,
       "funding": [
         {
diff --git a/package.json b/package.json
index 19c4e2dd5..ebae01601 100644
--- a/package.json
+++ b/package.json
@@ -58,12 +58,12 @@
     "react-twitter-embed": "3.0.3",
     "react-use": "17.6.0",
     "scratch-parser": "6.0.0",
-    "scratch-storage": "^4.0.69"
+    "scratch-storage": "^4.0.91"
   },
   "devDependencies": {
     "@babel/cli": "7.26.4",
-    "@babel/core": "7.26.9",
-    "@babel/eslint-parser": "7.26.8",
+    "@babel/core": "7.26.10",
+    "@babel/eslint-parser": "7.26.10",
     "@babel/plugin-syntax-dynamic-import": "7.8.3",
     "@babel/plugin-transform-async-to-generator": "7.25.9",
     "@babel/plugin-transform-object-rest-spread": "7.25.9",
@@ -77,7 +77,7 @@
     "@scratch/scratch-gui": "^11.1.0-spork.15",
     "@types/jest": "29.5.14",
     "async": "3.2.6",
-    "autoprefixer": "10.4.20",
+    "autoprefixer": "10.4.21",
     "babel-loader": "8.4.1",
     "babel-plugin-transform-require-context": "0.1.1",
     "bowser": "1.9.4",
@@ -145,10 +145,10 @@
     "redux-mock-store": "1.5.5",
     "redux-thunk": "2.4.2",
     "regenerator-runtime": "0.13.9",
-    "sass": "1.85.1",
+    "sass": "1.86.0",
     "sass-loader": "10.5.2",
-    "scratch-l10n": "5.0.152",
-    "selenium-webdriver": "4.29.0",
+    "scratch-l10n": "5.0.180",
+    "selenium-webdriver": "4.30.0",
     "slick-carousel": "1.8.1",
     "stream-browserify": "3.0.0",
     "style-loader": "4.0.0",