[
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.yml",
    "content": "name: Bug Report\ndescription: Report a bug or issue with Leetcode Patterns\nlabels: [\"bug\"]\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      description: A clear description of the bug.\n      placeholder: Describe the issue...\n    validations:\n      required: true\n  - type: textarea\n    id: steps\n    attributes:\n      label: Steps to Reproduce\n      description: How can we reproduce this issue?\n      placeholder: |\n        1. Go to '...'\n        2. Click on '...'\n        3. See error\n    validations:\n      required: true\n  - type: textarea\n    id: expected\n    attributes:\n      label: Expected Behavior\n      description: What did you expect to happen?\n    validations:\n      required: true\n  - type: textarea\n    id: screenshots\n    attributes:\n      label: Screenshots\n      description: If applicable, add screenshots to help explain the issue.\n    validations:\n      required: false\n  - type: dropdown\n    id: browser\n    attributes:\n      label: Browser\n      options:\n        - Chrome\n        - Firefox\n        - Safari\n        - Edge\n        - Other\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.yml",
    "content": "name: Feature Request\ndescription: Suggest a new feature or improvement\nlabels: [\"enhancement\"]\nbody:\n  - type: textarea\n    id: description\n    attributes:\n      label: Description\n      description: A clear description of the feature you'd like.\n      placeholder: I'd like to see...\n    validations:\n      required: true\n  - type: textarea\n    id: motivation\n    attributes:\n      label: Motivation\n      description: Why would this feature be useful?\n    validations:\n      required: false\n  - type: textarea\n    id: alternatives\n    attributes:\n      label: Alternatives Considered\n      description: Any alternative solutions or features you've considered.\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question_request.yml",
    "content": "name: Question Request\ndescription: Request a new question to be added to the list\nlabels: [\"question-request\"]\nbody:\n  - type: input\n    id: title\n    attributes:\n      label: Question Title\n      description: The title of the LeetCode question.\n      placeholder: e.g. Two Sum\n    validations:\n      required: true\n  - type: input\n    id: url\n    attributes:\n      label: LeetCode URL\n      description: Link to the question on LeetCode.\n      placeholder: https://leetcode.com/problems/two-sum/\n    validations:\n      required: true\n  - type: dropdown\n    id: difficulty\n    attributes:\n      label: Difficulty\n      options:\n        - Easy\n        - Medium\n        - Hard\n    validations:\n      required: true\n  - type: input\n    id: pattern\n    attributes:\n      label: Pattern(s)\n      description: Which pattern(s) does this question belong to?\n      placeholder: e.g. Arrays, Two Pointers\n    validations:\n      required: true\n  - type: textarea\n    id: reason\n    attributes:\n      label: Why should this be added?\n      description: Why is this question a good fit for the list?\n    validations:\n      required: false\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n    types: [opened, synchronize, reopened, closed, labeled]\n    branches: [main]\n\npermissions:\n  contents: write\n  pull-requests: write\n\njobs:\n  lint:\n    if: github.event.action != 'closed'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n\n      - run: npm ci\n\n      - run: npm run lint\n\n  test:\n    if: github.event.action != 'closed'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n\n      - run: npm ci\n\n      - run: npx vitest run --coverage\n\n  preview:\n    if: >-\n      always() &&\n      contains(github.event.pull_request.labels.*.name, 'preview') &&\n      (github.event.action == 'closed' || (needs.lint.result == 'success' && needs.test.result == 'success'))\n    needs: [lint, test]\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - if: github.event.action != 'closed'\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n\n      - if: github.event.action != 'closed'\n        run: npm ci\n\n      - if: github.event.action != 'closed'\n        name: Build\n        env:\n          NEXT_PUBLIC_BASE_PATH: /leetcode-patterns/pr-preview/pr-${{ github.event.number }}\n          NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}\n          NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}\n        run: npx next build\n\n      - if: github.event.action != 'closed'\n        name: Remove service worker from preview\n        run: rm -f out/sw.js\n\n      - uses: rossjrw/pr-preview-action@v1\n        with:\n          source-dir: out\n"
  },
  {
    "path": ".github/workflows/deploy.yml",
    "content": "name: Deploy to GitHub Pages\n\non:\n  push:\n    branches: [main]\n\n  workflow_run:\n    workflows: [Update Questions]\n    types:\n      - completed\n\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 22\n          cache: npm\n\n      - run: npm ci\n\n      - name: Build\n        env:\n          NEXT_PUBLIC_BASE_PATH: /leetcode-patterns\n          NEXT_PUBLIC_SUPABASE_URL: ${{ secrets.NEXT_PUBLIC_SUPABASE_URL }}\n          NEXT_PUBLIC_SUPABASE_ANON_KEY: ${{ secrets.NEXT_PUBLIC_SUPABASE_ANON_KEY }}\n        run: npm run build\n\n      - uses: JamesIves/github-pages-deploy-action@v4\n        with:\n          folder: out\n          branch: gh-pages\n          clean-exclude: pr-preview\n          force: false\n"
  },
  {
    "path": ".github/workflows/update-questions.yml",
    "content": "name: Update Questions\n\non:\n  schedule:\n    # Every Sunday at 8am EST (1pm UTC)\n    - cron: \"0 13 * * 0\"\n  workflow_dispatch:\n\npermissions:\n  contents: write\n\njobs:\n  update:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n\n      - uses: actions/setup-python@v5\n        with:\n          python-version: \"3.12\"\n\n      - name: Update question metadata\n        working-directory: cron\n        env:\n          LEETCODE_SESSION_TOKEN: ${{ secrets.LEETCODE_SESSION_TOKEN }}\n          LEETCODE_CSRF_TOKEN: ${{ secrets.LEETCODE_CSRF_TOKEN }}\n          LEETCODE_CF_CLEARANCE: ${{ secrets.LEETCODE_CF_CLEARANCE }}\n        run: python update_questions.py\n\n      - name: Commit and push 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 src/data/questions.json\n          git diff --cached --quiet || git commit --author=\"Sean Prashad <13009507+seanprashad@users.noreply.github.com>\" -m \"chore: update question metadata\" && git push\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.*\n.yarn/*\n!.yarn/patches\n!.yarn/plugins\n!.yarn/releases\n!.yarn/versions\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n.pnpm-debug.log*\n\n# env files (can opt-in for committing if needed)\n.env*\n\n# vercel\n.vercel\n\n# typescript\n*.tsbuildinfo\nnext-env.d.ts\n\n# python\n__pycache__\n"
  },
  {
    "path": ".husky/pre-push",
    "content": "npm run lint -- --fix\nnpm test\n"
  },
  {
    "path": ".npmrc",
    "content": "registry=https://registry.npmjs.org\n"
  },
  {
    "path": "LICENSE",
    "content": "Creative Commons Attribution-NonCommercial 4.0 International\n\nCopyright (c) 2026 Sean Prashad\n\nThis work is licensed under the Creative Commons\nAttribution-NonCommercial 4.0 International License.\n\nYou are free to:\n\n  Share — copy and redistribute the material in any medium or format\n  Adapt — remix, transform, and build upon the material\n\nUnder the following terms:\n\n  Attribution — You must give appropriate credit, provide a link to\n  the license, and indicate if changes were made. You may do so in\n  any reasonable manner, but not in any way that suggests the licensor\n  endorses you or your use.\n\n  NonCommercial — You may not use the material for commercial purposes.\n\n  No additional restrictions — You may not apply legal terms or\n  technological measures that legally restrict others from doing\n  anything the license permits.\n\nNotices:\n\n  You do not have to comply with the license for elements of the\n  material in the public domain or where your use is permitted by an\n  applicable exception or limitation.\n\n  No warranties are given. The license may not give you all of the\n  permissions necessary for your intended use. For example, other\n  rights such as publicity, privacy, or moral rights may limit how\n  you use the material.\n\nFull license text:\nhttps://creativecommons.org/licenses/by-nc/4.0/legalcode\n"
  },
  {
    "path": "README.md",
    "content": "<p align=\"center\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"public/images/logo-dark.png\" />\n    <source media=\"(prefers-color-scheme: light)\" srcset=\"public/images/logo-light.png\" />\n    <img alt=\"Leetcode Patterns\" src=\"public/images/logo-light.png\" width=\"500\" />\n  </picture>\n</p>\n\n<p align=\"center\">\n  <a href=\"https://github.com/seanprashad/leetcode-patterns/actions/workflows/deploy.yml\"><img src=\"https://github.com/seanprashad/leetcode-patterns/actions/workflows/deploy.yml/badge.svg\" alt=\"Deploy to GitHub Pages\" /></a>\n  <a href=\"https://github.com/seanprashad/leetcode-patterns/actions/workflows/update-questions.yml\"><img src=\"https://github.com/seanprashad/leetcode-patterns/actions/workflows/update-questions.yml/badge.svg\" alt=\"Update Questions\" /></a>\n</p>\n\n## Table of Contents\n\n- [Background](#background)\n- [Fundamentals](#fundamentals)\n- [Notes](#notes)\n- [Question List](#question-list)\n- [Solutions](#solutions)\n- [Contributing](#contributing)\n- [Suggestions](#suggestions)\n- [Acknowledgements](#acknowledgements)\n\n## Background\n\nThis repo is intended for any individual wanting to improve their problem\nsolving skills for software engineering interviews.\n\nProblems are grouped under their respective subtopic, in order to focus on\nrepeatedly applying common patterns rather than randomly tackling questions.\n\nAll questions are available on [leetcode.com] with some requiring [leetcode premium].\n\n## Fundamentals\n\nTo find the greatest amount of success when practicing, it is highly recommended\nto know the methods and runtimes of the following data structures and their\noperations:\n\n- Arrays\n- Maps\n- Linked Lists\n- Queues\n- Heaps\n- Stacks\n- Trees\n- Graphs\n\nIn addition, you should have a good grasp on common algorithms such as:\n\n- Breadth-first search\n- Depth-first search\n- Binary search\n- Recursion\n\n## Notes\n\n[This pdf] contains information for the main data structures in Java.\n\nOther useful methods to know include [`substring()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#substring-int-int-), [`toCharArray()`](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#toCharArray--), [`Math.max()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#max-int-int-),\n[`Math.min()`](https://docs.oracle.com/javase/8/docs/api/java/lang/Math.html#min-int-int-), and [`Arrays.fill()`](https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html#fill-int:A-int-).\n\n## Question List\n\nThe entire question list can be found here:\nhttps://seanprashad.com/leetcode-patterns/.\n\n## Solutions\n\nSolutions written in Java can be found in the [solutions] branch.\n\n## Contributing\n\nThe app is built with [Next.js] (App Router), [React] 19, [TypeScript], [Tailwind CSS] v4, [TanStack Table] v8, [Lucide React] for icons, and Google Analytics via `@next/third-parties`. Tests use [Vitest] + [React Testing Library].\n\n```bash\nnpm install\nnpm run dev         # http://localhost:3000\nnpm test            # single run\nnpm run test:watch  # watch mode\n```\n\nA [Husky] `pre-push` hook runs `npm test` automatically before every push. This is set up for every clone via the `prepare` script.\n\n## Acknowledgements\n\nThis list is heavily inspired from [Grokking the Coding Interview] with\nadditional problems extracted from the [Blind 75 list] and this hackernoon article\non [14 patterns to ace any coding interview question].\n\n[leetcode.com]: https://leetcode.com\n[leetcode premium]: https://leetcode.com/subscribe/\n[next.js]: https://nextjs.org\n[react]: https://react.dev\n[typescript]: https://www.typescriptlang.org\n[tailwind css]: https://tailwindcss.com\n[tanstack table]: https://tanstack.com/table\n[lucide react]: https://lucide.dev\n[vitest]: https://vitest.dev\n[react testing library]: https://testing-library.com/docs/react-testing-library/intro\n[husky]: https://typicode.github.io/husky\n[this pdf]: https://drive.google.com/open?id=1ao4ZA28zzBttDkuS6MLQI52gDs_CJZEm\n[solutions]: https://github.com/SeanPrashad/leetcode-patterns/tree/solutions\n[grokking the coding interview]: https://www.educative.io/courses/grokking-the-coding-interview\n[issue]: https://github.com/SeanPrashad/leetcode-patterns/issues/new\n[blind 75 list]: https://www.teamblind.com/article/New-Year-Gift---Curated-List-of-Top-100-LeetCode-Questions-to-Save-Your-Time-OaM1orEU?utm_source=share&utm_medium=ios_app\n[14 patterns to ace any coding interview question]: https://hackernoon.com/14-patterns-to-ace-any-coding-interview-question-c5bb3357f6ed\n"
  },
  {
    "path": "cron/leetcode/__init__.py",
    "content": "from leetcode.models import (\n    Configuration,\n    ApiClient,\n    DefaultApi,\n    GraphqlQuery,\n    GraphqlQueryGetQuestionDetailVariables,\n    GraphqlQuestionDetail,\n    GraphqlData,\n    GraphqlResponse,\n)\n\nfrom leetcode import auth\nfrom leetcode import rest\n\n__all__ = [\n    \"Configuration\",\n    \"ApiClient\",\n    \"DefaultApi\",\n    \"GraphqlQuery\",\n    \"GraphqlQueryGetQuestionDetailVariables\",\n    \"GraphqlQuestionDetail\",\n    \"GraphqlData\",\n    \"GraphqlResponse\",\n    \"auth\",\n    \"rest\",\n]\n"
  },
  {
    "path": "cron/leetcode/auth.py",
    "content": "# Placeholder for leetcode.auth compatibility.\n# Authentication is handled via Configuration.api_key in the main module.\n"
  },
  {
    "path": "cron/leetcode/models.py",
    "content": "import json\nimport urllib.request\n\nfrom leetcode.rest import ApiException\n\n\nclass Configuration:\n    def __init__(self):\n        self.host = \"https://leetcode.com\"\n        self.api_key = {}\n        self.debug = False\n\n\nclass GraphqlQuery:\n    def __init__(self, query=None, variables=None, operation_name=None):\n        self.query = query\n        self.variables = variables\n        self.operation_name = operation_name\n\n\nclass GraphqlQueryGetQuestionDetailVariables:\n    def __init__(self, title_slug=None):\n        self.title_slug = title_slug\n\n\nclass GraphqlQuestionDetail:\n    def __init__(self, question_id=None, title=None, difficulty=None,\n                 company_tag_stats_v2=None, is_paid_only=None, topic_tags=None):\n        self.question_id = question_id\n        self.title = title\n        self.difficulty = difficulty\n        self.company_tag_stats_v2 = company_tag_stats_v2\n        self.is_paid_only = is_paid_only\n        self.topic_tags = topic_tags\n\n\nclass GraphqlData:\n    def __init__(self, question=None):\n        self.question = question\n\n\nclass GraphqlResponse:\n    def __init__(self, data=None):\n        self.data = data\n\n\nclass ApiClient:\n    def __init__(self, configuration=None):\n        self.configuration = configuration or Configuration()\n\n\nclass DefaultApi:\n    def __init__(self, api_client=None):\n        self.api_client = api_client or ApiClient()\n\n    def graphql_post(self, body=None):\n        config = self.api_client.configuration\n\n        variables = {}\n        if body.variables and hasattr(body.variables, \"title_slug\"):\n            variables[\"titleSlug\"] = body.variables.title_slug\n\n        payload = {\"query\": body.query, \"variables\": variables}\n        if body.operation_name:\n            payload[\"operationName\"] = body.operation_name\n\n        data = json.dumps(payload).encode(\"utf-8\")\n\n        cookie = (\n            f\"csrftoken={config.api_key.get('csrftoken') or ''}; \"\n            f\"LEETCODE_SESSION={config.api_key.get('LEETCODE_SESSION') or ''}; \"\n            f\"cf_clearance={config.api_key.get('cf_clearance') or ''}\"\n        )\n\n        url = config.host + \"/graphql\"\n        req = urllib.request.Request(url, data=data, method=\"POST\")\n        req.add_header(\"Content-Type\", \"application/json\")\n        req.add_header(\"Accept\", \"application/json\")\n        req.add_header(\"User-Agent\", \"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36\")\n        req.add_header(\"x-csrftoken\", config.api_key.get(\"x-csrftoken\") or \"\")\n        req.add_header(\"Referer\", config.api_key.get(\"Referer\") or \"https://leetcode.com\")\n        req.add_header(\"Cookie\", cookie)\n\n        try:\n            with urllib.request.urlopen(req) as resp:\n                resp_data = json.loads(resp.read().decode(\"utf-8\"))\n        except urllib.error.HTTPError as e:\n            raise ApiException(\n                status=e.code,\n                reason=e.reason,\n                body=e.read().decode(\"utf-8\"),\n            )\n\n        question_data = resp_data.get(\"data\", {}).get(\"question\", {})\n\n        topic_tags_raw = question_data.get(\"topicTags\")\n        topic_tags = [type(\"Tag\", (), {\"name\": t[\"name\"]}) for t in topic_tags_raw]\n\n        question = GraphqlQuestionDetail(\n            question_id=question_data.get(\"questionId\"),\n            title=question_data.get(\"title\"),\n            difficulty=question_data.get(\"difficulty\"),\n            company_tag_stats_v2=question_data.get(\"companyTagStatsV2\"),\n            is_paid_only=question_data.get(\"isPaidOnly\"),\n            topic_tags=topic_tags,\n        )\n\n        return GraphqlResponse(data=GraphqlData(question=question))\n"
  },
  {
    "path": "cron/leetcode/rest.py",
    "content": "class ApiException(Exception):\n    def __init__(self, status=None, reason=None, body=None):\n        self.status = status\n        self.reason = reason\n        self.body = body\n\n    def __str__(self):\n        msg = f\"({self.status})\\nReason: {self.reason}\\n\"\n        if self.body:\n            msg += f\"HTTP response body: {self.body}\\n\"\n        return msg\n"
  },
  {
    "path": "cron/update_questions.py",
    "content": "import os\nimport json\nimport leetcode\nimport leetcode.auth\nfrom datetime import datetime\nfrom leetcode.rest import ApiException\n\n\ndef create_leetcode_api():\n    leetcode_session_token = os.environ.get(\"LEETCODE_SESSION_TOKEN\")\n    csrf_token = os.environ.get(\"LEETCODE_CSRF_TOKEN\")\n    cf_clearance = os.environ.get(\"LEETCODE_CF_CLEARANCE\")\n\n    if not leetcode_session_token:\n        print(\"❌ LEETCODE_SESSION_TOKEN environment variable is required\")\n        exit(1)\n\n    if not csrf_token:\n        print(\"❌ LEETCODE_CSRF_TOKEN environment variable is required\")\n        exit(1)\n\n    if not cf_clearance:\n        print(\"❌ LEETCODE_CF_CLEARANCE environment variable is required\")\n        exit(1)\n\n    configuration = leetcode.Configuration()\n\n    configuration.api_key[\"x-csrftoken\"] = csrf_token\n    configuration.api_key[\"csrftoken\"] = csrf_token\n    configuration.api_key[\"LEETCODE_SESSION\"] = leetcode_session_token\n    configuration.api_key[\"cf_clearance\"] = cf_clearance\n    configuration.api_key[\"Referer\"] = \"https://leetcode.com\"\n    configuration.debug = False\n\n    return leetcode.DefaultApi(leetcode.ApiClient(configuration))\n\n\ndef get_question_metadata(api, title_slug):\n    graphql_request = leetcode.GraphqlQuery(\n        query='''query questionData($titleSlug: String!) {\n            question(titleSlug: $titleSlug) {\n                questionId\n                title\n                difficulty\n                companyTagStatsV2\n                isPaidOnly\n                topicTags {\n                    name\n                }\n            }\n        }\n        ''',\n        variables=leetcode.GraphqlQueryGetQuestionDetailVariables(\n            title_slug=title_slug)\n    )\n\n    try:\n        response = api.graphql_post(body=graphql_request)\n        if not response.data.question:\n            print(f'❌ Empty response body for question: {title_slug}')\n            exit(1)\n        return response\n    except ApiException as e:\n        print(\n            f'Exception occurred when contacting the Leetcode GraphQL API: ${e}')\n        exit()\n\n\ndef construct_company_tag_list(company_tag_stats_v2):\n    companies = []\n\n    tag_stats = json.loads(company_tag_stats_v2)\n    # \"three_months\" = 0-3 months, \"six_months\" = 3-6 months, \"more_than_six_months\" = 6+ months\n    for tag in tag_stats[\"three_months\"] + tag_stats[\"six_months\"]:\n        companies.append({\n            \"name\": tag[\"name\"],\n            \"slug\": tag[\"slug\"],\n            \"frequency\": tag[\"timesEncountered\"]\n        })\n\n    return sorted(companies, key=lambda d: d['frequency'], reverse=True)\n\n\ndef update_question_metadata(question, response):\n    print(f'''🔄 Updating question metadata for {question[\"title\"]}''')\n\n    question_id = response.data.question.question_id\n    question_title = response.data.question.title\n    question_difficulty = response.data.question.difficulty\n    question_company_tag_stats_v2 = response.data.question.company_tag_stats_v2\n    question_is_premium = response.data.question.is_paid_only\n    question_topic_tags = response.data.question.topic_tags\n\n    companies = construct_company_tag_list(question_company_tag_stats_v2)\n    patterns = [tag.name for tag in question_topic_tags]\n\n    question[\"id\"] = int(question_id)\n    question[\"title\"] = question_title\n    question[\"difficulty\"] = question_difficulty\n    question[\"pattern\"] = patterns\n    question[\"companies\"] = companies\n    question[\"premium\"] = question_is_premium\n\n\ndef read_questions(file_name):\n    print(f\"💾 Loading {file_name}\")\n\n    try:\n        with open(file_name, \"r\") as file:\n            questions = json.load(file)\n            print(f\"✅ Finished loading {file_name}\")\n            return questions\n    except Exception as e:\n        print(\n            f\"❌ Exception occurred when reading {file_name}: {e}\")\n        exit()\n\n\ndef write_questions(file_name, questions):\n    print(f\"💾 Updating {file_name}\")\n\n    try:\n        with open(file_name, \"w\") as file:\n            questions[\"updated\"] = str(datetime.now().isoformat())\n            json.dump(questions, file, indent=2)\n            print(f\"✅ Finished updating {file_name}\")\n    except Exception as e:\n        print(\n            f\"❌ Exception occurred when writing {file_name}: {e}\")\n        exit()\n\n\ndef main(file_name):\n    api = create_leetcode_api()\n    questions = read_questions(file_name)\n\n    for question in questions[\"data\"]:\n        title_slug = question[\"slug\"]\n\n        response = get_question_metadata(api, title_slug)\n\n        update_question_metadata(question, response)\n\n    write_questions(file_name, questions)\n\n\nif __name__ == \"__main__\":\n    file_name = os.path.join(os.path.dirname(os.path.abspath(__file__)), \"..\", \"src\", \"data\", \"questions.json\")\n    startTime = datetime.now()\n\n    main(file_name)\n\n    print(f\"⏱️  Data updated in {datetime.now() - startTime} seconds\")\n"
  },
  {
    "path": "eslint.config.mjs",
    "content": "import { defineConfig, globalIgnores } from \"eslint/config\";\nimport nextVitals from \"eslint-config-next/core-web-vitals\";\nimport nextTs from \"eslint-config-next/typescript\";\n\nconst eslintConfig = defineConfig([\n  ...nextVitals,\n  ...nextTs,\n  // Override default ignores of eslint-config-next.\n  globalIgnores([\n    // Default ignores of eslint-config-next:\n    \".next/**\",\n    \"out/**\",\n    \"build/**\",\n    \"next-env.d.ts\",\n  ]),\n]);\n\nexport default eslintConfig;\n"
  },
  {
    "path": "next.config.ts",
    "content": "import type { NextConfig } from \"next\";\n\nconst nextConfig: NextConfig = {\n  reactStrictMode: false,\n  output: \"export\",\n  trailingSlash: false,\n  basePath: process.env.NEXT_PUBLIC_BASE_PATH ?? \"\",\n  images: { unoptimized: true },\n};\n\nexport default nextConfig;\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"leetcode-patterns-v2\",\n  \"version\": \"0.1.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build && node scripts/generate-sw-precache.mjs\",\n    \"start\": \"next start\",\n    \"lint\": \"eslint\",\n    \"test\": \"vitest run\",\n    \"test:watch\": \"vitest\",\n    \"prepare\": \"husky\"\n  },\n  \"dependencies\": {\n    \"@next/third-parties\": \"^16.1.6\",\n    \"@supabase/supabase-js\": \"^2.99.0\",\n    \"@tanstack/react-table\": \"^8.21.3\",\n    \"@tanstack/react-virtual\": \"^3.13.21\",\n    \"lucide-react\": \"^0.577.0\",\n    \"next\": \"16.1.7\",\n    \"react\": \"19.2.3\",\n    \"react-dom\": \"19.2.3\"\n  },\n  \"devDependencies\": {\n    \"@tailwindcss/postcss\": \"^4\",\n    \"@testing-library/jest-dom\": \"^6.9.1\",\n    \"@testing-library/react\": \"^16.3.2\",\n    \"@testing-library/user-event\": \"^14.6.1\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19\",\n    \"@types/react-dom\": \"^19\",\n    \"@vitejs/plugin-react\": \"^5.1.4\",\n    \"@vitest/coverage-v8\": \"^4.0.18\",\n    \"eslint\": \"^9\",\n    \"eslint-config-next\": \"16.1.6\",\n    \"happy-dom\": \"^20.8.3\",\n    \"husky\": \"^9.1.7\",\n    \"jsdom\": \"^28.1.0\",\n    \"tailwindcss\": \"^4\",\n    \"typescript\": \"^5\",\n    \"vitest\": \"^4.0.18\"\n  }\n}\n"
  },
  {
    "path": "postcss.config.mjs",
    "content": "const config = {\n  plugins: {\n    \"@tailwindcss/postcss\": {},\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "public/.nojekyll",
    "content": "\n"
  },
  {
    "path": "public/manifest.json",
    "content": "{\n  \"short_name\": \"leetcode-patterns\",\n  \"name\": \"Leetcode Patterns\",\n  \"description\": \"A curated list of LeetCode questions grouped by patterns.\",\n  \"icons\": [\n    {\n      \"src\": \"favicon.ico\",\n      \"sizes\": \"64x64 32x32 24x24 16x16\",\n      \"type\": \"image/x-icon\"\n    }\n  ],\n  \"start_url\": \".\",\n  \"display\": \"standalone\",\n  \"theme_color\": \"#09090b\",\n  \"background_color\": \"#ffffff\"\n}\n"
  },
  {
    "path": "public/robots.txt",
    "content": "User-agent: *\nAllow: /\n"
  },
  {
    "path": "public/sw.js",
    "content": "// Service Worker — cache-first for static assets, network-first for navigation\nconst CACHE_NAME = \"lc-patterns-v2\";\n\nconst PRECACHE_URLS = [\n  \"./\",\n  \"./manifest.json\",\n];\n\nself.addEventListener(\"install\", (event) => {\n  event.waitUntil(\n    caches.open(CACHE_NAME).then((cache) => cache.addAll(PRECACHE_URLS))\n  );\n  self.skipWaiting();\n});\n\nself.addEventListener(\"activate\", (event) => {\n  event.waitUntil(\n    caches.keys().then((keys) =>\n      Promise.all(keys.filter((k) => k !== CACHE_NAME).map((k) => caches.delete(k)))\n    )\n  );\n  self.clients.claim();\n});\n\nself.addEventListener(\"fetch\", (event) => {\n  const { request } = event;\n\n  // Skip non-GET and chrome-extension requests\n  if (request.method !== \"GET\" || !request.url.startsWith(\"http\")) return;\n\n  // Skip analytics and external API calls\n  if (\n    request.url.includes(\"google-analytics.com\") ||\n    request.url.includes(\"googletagmanager.com\") ||\n    request.url.includes(\"s2/favicons\")\n  ) return;\n\n  // Navigation requests: network-first with cache fallback\n  if (request.mode === \"navigate\") {\n    event.respondWith(\n      fetch(request)\n        .then((response) => {\n          const clone = response.clone();\n          caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));\n          return response;\n        })\n        .catch(() => caches.match(request).then((cached) => cached || caches.match(\"./\")))\n    );\n    return;\n  }\n\n  // Static assets: cache-first with network fallback\n  event.respondWith(\n    caches.match(request).then(\n      (cached) =>\n        cached ||\n        fetch(request).then((response) => {\n          // Only cache successful same-origin responses\n          if (response.ok && request.url.startsWith(self.location.origin)) {\n            const clone = response.clone();\n            caches.open(CACHE_NAME).then((cache) => cache.put(request, clone));\n          }\n          return response;\n        })\n    )\n  );\n});\n"
  },
  {
    "path": "scripts/generate-sw-precache.mjs",
    "content": "import { readFileSync, writeFileSync, readdirSync, statSync } from \"fs\";\nimport { join, relative } from \"path\";\n\nconst OUT_DIR = \"out\";\nconst SW_PATH = join(OUT_DIR, \"sw.js\");\n\nconst PRECACHE_EXTENSIONS = new Set([\n  \".html\", \".js\", \".css\", \".json\", \".txt\", \".woff\", \".woff2\",\n]);\n\nfunction collectFiles(dir, base = dir) {\n  const entries = [];\n  for (const entry of readdirSync(dir)) {\n    const full = join(dir, entry);\n    if (statSync(full).isDirectory()) {\n      entries.push(...collectFiles(full, base));\n    } else {\n      const rel = \"./\" + relative(base, full);\n      if (rel === \"./sw.js\") continue;\n      if (rel.endsWith(\".map\")) continue;\n      const ext = rel.slice(rel.lastIndexOf(\".\"));\n      if (!PRECACHE_EXTENSIONS.has(ext)) continue;\n      entries.push(rel);\n    }\n  }\n  return entries;\n}\n\nconst files = collectFiles(OUT_DIR);\nconst sw = readFileSync(SW_PATH, \"utf-8\");\n\n// Replace the placeholder PRECACHE_URLS array with the full file list\nconst updated = sw.replace(\n  /const PRECACHE_URLS = \\[[\\s\\S]*?\\];/,\n  `const PRECACHE_URLS = ${JSON.stringify(files, null, 2)};`\n);\n\nwriteFileSync(SW_PATH, updated);\nconsole.log(`Precache manifest: ${files.length} files injected into sw.js`);\n"
  },
  {
    "path": "src/app/globals.css",
    "content": "@import \"tailwindcss\";\n\n@custom-variant dark (&:where(.dark, .dark *));\n\n:root {\n  --background: #ffffff;\n  --foreground: #171717;\n}\n\n.dark {\n  --background: #0a0a0a;\n  --foreground: #ededed;\n}\n\n@theme inline {\n  --color-background: var(--background);\n  --color-foreground: var(--foreground);\n  --font-sans: var(--font-geist-sans);\n  --font-mono: var(--font-geist-mono);\n}\n\nbody {\n  background: var(--background);\n  color: var(--foreground);\n  font-family: Arial, Helvetica, sans-serif;\n}\n\n@keyframes fadeInUp {\n  from {\n    opacity: 0;\n    transform: translateY(1rem);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n"
  },
  {
    "path": "src/app/layout.tsx",
    "content": "import type { Metadata } from \"next\";\nimport { GoogleAnalytics } from \"@next/third-parties/google\";\nimport { Geist, Geist_Mono, Dancing_Script } from \"next/font/google\";\nimport \"./globals.css\";\nimport ServiceWorkerRegistrar from \"@/components/layout/ServiceWorkerRegistrar\";\nimport { AuthProvider } from \"@/components/layout/AuthContext\";\n\nconst geistSans = Geist({\n  variable: \"--font-geist-sans\",\n  subsets: [\"latin\"],\n});\n\nconst geistMono = Geist_Mono({\n  variable: \"--font-geist-mono\",\n  subsets: [\"latin\"],\n});\n\nconst dancingScript = Dancing_Script({\n  variable: \"--font-dancing-script\",\n  subsets: [\"latin\"],\n});\n\nconst siteUrl = \"https://seanprashad.com/leetcode-patterns\";\nconst basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? \"\";\n\nexport const metadata: Metadata = {\n  title: \"Leetcode Patterns\",\n  description:\n    \"A curated list of LeetCode questions grouped by pattern to help you ace coding interviews. Filter by difficulty, company, and topic.\",\n  manifest: `${basePath}/manifest.json`,\n  metadataBase: new URL(siteUrl),\n  alternates: { canonical: \"/\" },\n  openGraph: {\n    title: \"Leetcode Patterns\",\n    description:\n      \"A curated list of LeetCode questions grouped by pattern to help you ace coding interviews.\",\n    url: siteUrl,\n    siteName: \"Leetcode Patterns\",\n    images: [\n      {\n        url: `${basePath}/images/og-image.png`,\n        width: 1200,\n        height: 630,\n        alt: \"Leetcode Patterns – A curated list of LeetCode questions grouped by pattern\",\n      },\n    ],\n    type: \"website\",\n  },\n  twitter: {\n    card: \"summary_large_image\",\n    title: \"Leetcode Patterns\",\n    description:\n      \"A curated list of LeetCode questions grouped by pattern to help you ace coding interviews.\",\n    images: [`${basePath}/images/og-image.png`],\n  },\n  keywords: [\n    \"leetcode\",\n    \"coding interview\",\n    \"data structures\",\n    \"algorithms\",\n    \"interview prep\",\n    \"blind 75\",\n    \"leetcode patterns\",\n  ],\n  authors: [{ name: \"Sean Prashad\", url: \"https://github.com/SeanPrashad\" }],\n};\n\nexport default function RootLayout({\n  children,\n}: Readonly<{\n  children: React.ReactNode;\n}>) {\n  return (\n    <html lang=\"en\" suppressHydrationWarning>\n      <head>\n        <script\n          dangerouslySetInnerHTML={{\n            __html: `(function(){try{var t=localStorage.getItem(\"theme\");if(t===\"dark\"||(t!==\"light\"&&matchMedia(\"(prefers-color-scheme:dark)\").matches))document.documentElement.classList.add(\"dark\")}catch(e){}})();if(location.hash&&location.hash.indexOf(\"access_token\")>-1)window.__SUPABASE_AUTH_HASH__=location.hash`,\n          }}\n        />\n      </head>\n      <body\n        className={`${geistSans.variable} ${geistMono.variable} ${dancingScript.variable} antialiased`}\n      >\n        <AuthProvider>\n          {children}\n        </AuthProvider>\n        <ServiceWorkerRegistrar />\n      </body>\n      <GoogleAnalytics gaId=\"G-J7FBQPGZTW\" />\n    </html>\n  );\n}\n"
  },
  {
    "path": "src/app/not-found.tsx",
    "content": "import Link from \"next/link\";\n\nexport default function NotFound() {\n  return (\n    <div className=\"flex min-h-screen items-center justify-center px-4\">\n      <div className=\"text-center\">\n        <div\n          className=\"mx-auto mb-6 w-fit rounded-lg px-6 py-2 text-5xl font-bold text-amber-950 dark:text-amber-200\"\n          style={{ background: \"linear-gradient(90deg, #FFD200, #FFA500, #FF7800)\" }}\n        >\n          404\n        </div>\n        <h1 className=\"mb-2 text-2xl font-bold\">Page not found</h1>\n        <p className=\"mb-6 text-zinc-500\">\n          The page you&apos;re looking for doesn&apos;t exist.\n        </p>\n        <Link\n          href=\"/\"\n          className=\"inline-block rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n        >\n          ← Back to questions\n        </Link>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/app/page.tsx",
    "content": "import { Suspense } from \"react\";\nimport { MessageSquarePlus } from \"lucide-react\";\nimport ThemeToggle from \"@/components/layout/ThemeToggle\";\nimport GitHubLink from \"@/components/layout/GitHubLink\";\nimport UserMenu from \"@/components/layout/UserMenu\";\nimport Logo from \"@/components/layout/Logo\";\nimport ViewSwitcher from \"@/components/layout/ViewSwitcher\";\nimport AboutPanel from \"@/components/panels/AboutPanel\";\nimport AcknowledgementsPanel from \"@/components/panels/AcknowledgementsPanel\";\nimport TipsPanel from \"@/components/panels/TipsPanel\";\nimport questionsJson from \"@/data/questions.json\";\nimport { QuestionsData } from \"@/types/question\";\n\nconst { data: questions } = questionsJson as QuestionsData;\n\nexport default function Home() {\n  return (\n    <div className=\"mx-auto max-w-6xl px-4 py-6 sm:py-10\">\n      <div className=\"mb-4 flex items-start justify-between sm:mb-6\">\n        <div>\n          <h1>\n            <Logo />\n          </h1>\n          <p className=\"text-base italic text-zinc-500\">\n            by{\" \"}\n            <a\n              href=\"https://github.com/SeanPrashad\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"text-blue-600 hover:underline dark:text-blue-400\"\n            >\n              Sean Prashad\n            </a>\n            {\" · Est. 2019\"}\n          </p>\n          <p className=\"mt-1 text-sm text-zinc-500 sm:mt-2 sm:text-base\">\n            A collection of {questions.length} questions grouped by pattern to help you prepare for coding interviews.\n          </p>\n        </div>\n        <div className=\"flex shrink-0 items-center gap-2\">\n          <GitHubLink />\n          <span className=\"group/fb relative\">\n            <a\n              href=\"https://github.com/SeanPrashad/leetcode-patterns/issues/new/choose\"\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className=\"block rounded-lg border border-zinc-300 p-2 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n            >\n              <MessageSquarePlus className=\"h-4 w-4\" />\n            </a>\n            <span className=\"pointer-events-none absolute left-1/2 top-full z-10 mt-1.5 -translate-x-1/2 whitespace-nowrap rounded bg-zinc-800 px-2 py-1 text-xs text-white opacity-0 shadow transition-opacity group-hover/fb:opacity-100 dark:bg-zinc-200 dark:text-zinc-900\">\n              Feedback\n            </span>\n          </span>\n          <ThemeToggle />\n          <UserMenu />\n        </div>\n      </div>\n      {/* Side panel tabs – stacked flush in a fixed column */}\n      <div className=\"fixed left-0 top-0 bottom-0 z-30 flex flex-col items-start justify-center max-[1439px]:hidden\">\n        <AboutPanel />\n        <TipsPanel />\n        <AcknowledgementsPanel />\n      </div>\n      <Suspense>\n        <ViewSwitcher\n          questions={questions}\n          updatedDate={questionsJson.updated}\n        />\n      </Suspense>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/AuthContext.test.tsx",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { render, screen, cleanup, act } from \"@testing-library/react\";\n\ntype AuthCallback = (event: string, session: { user: Record<string, unknown> } | null) => void;\n\nconst { mockGetSession, mockOnAuthStateChange, mockTrackEvent, mockDownloadAndMerge, mockChannel } = vi.hoisted(() => {\n  const mockChannel = {\n    on: vi.fn().mockReturnThis(),\n    subscribe: vi.fn().mockReturnThis(),\n  };\n  return {\n    mockGetSession: vi.fn(),\n    mockOnAuthStateChange: vi.fn(),\n    mockTrackEvent: vi.fn(),\n    mockDownloadAndMerge: vi.fn(() => Promise.resolve()),\n    mockChannel,\n  };\n});\n\nvi.mock(\"@/lib/supabase\", () => ({\n  supabase: {\n    auth: {\n      getSession: mockGetSession,\n      onAuthStateChange: mockOnAuthStateChange,\n      setSession: vi.fn(),\n    },\n    channel: () => mockChannel,\n    removeChannel: vi.fn(),\n  },\n}));\n\nvi.mock(\"@/lib/sync\", () => ({\n  downloadAndMerge: mockDownloadAndMerge,\n  scheduleUpload: vi.fn(),\n  mergeFromRealtimePayload: vi.fn(),\n}));\n\nvi.mock(\"@/lib/analytics\", () => ({\n  trackEvent: mockTrackEvent,\n}));\n\nimport { AuthProvider, useAuth } from \"@/components/layout/AuthContext\";\n\nconst fakeUser = {\n  id: \"user-1\",\n  user_metadata: { user_name: \"testuser\", avatar_url: \"https://example.com/avatar.png\" },\n};\n\nfunction TestConsumer() {\n  const { user } = useAuth();\n  return <div>{user ? `signed-in:${(user as unknown as typeof fakeUser).user_metadata.user_name}` : \"signed-out\"}</div>;\n}\n\ndescribe(\"AuthProvider sign-in toast suppression\", () => {\n  let authCallback: AuthCallback;\n  const unsubscribe = vi.fn();\n\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    mockDownloadAndMerge.mockClear();\n    mockOnAuthStateChange.mockImplementation((cb: AuthCallback) => {\n      authCallback = cb;\n      return { data: { subscription: { unsubscribe } } };\n    });\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"shows toast on fresh sign-in when no existing session\", async () => {\n    mockGetSession.mockResolvedValue({ data: { session: null } });\n\n    await act(async () => {\n      render(\n        <AuthProvider>\n          <TestConsumer />\n        </AuthProvider>\n      );\n    });\n\n    expect(screen.getByText(\"signed-out\")).toBeInTheDocument();\n\n    await act(async () => {\n      authCallback(\"SIGNED_IN\", { user: fakeUser });\n    });\n\n    expect(screen.getByText(/Signed in as testuser/)).toBeInTheDocument();\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"sign_in\", { provider: \"github\" });\n  });\n\n  it(\"does NOT show toast when session already exists and SIGNED_IN fires again\", async () => {\n    mockGetSession.mockResolvedValue({ data: { session: { user: fakeUser } } });\n\n    await act(async () => {\n      render(\n        <AuthProvider>\n          <TestConsumer />\n        </AuthProvider>\n      );\n    });\n\n    expect(screen.getByText(\"signed-in:testuser\")).toBeInTheDocument();\n    mockTrackEvent.mockClear();\n\n    await act(async () => {\n      authCallback(\"SIGNED_IN\", { user: fakeUser });\n    });\n\n    expect(screen.queryByText(/Signed in as/)).not.toBeInTheDocument();\n    expect(mockTrackEvent).not.toHaveBeenCalledWith(\"sign_in\", expect.anything());\n  });\n\n  it(\"does NOT show toast when INITIAL_SESSION fires before getSession resolves\", async () => {\n    // Simulate getSession that never resolves before INITIAL_SESSION fires\n    let resolveGetSession!: (v: { data: { session: { user: typeof fakeUser } } }) => void;\n    mockGetSession.mockReturnValue(new Promise((r) => { resolveGetSession = r; }));\n\n    await act(async () => {\n      render(\n        <AuthProvider>\n          <TestConsumer />\n        </AuthProvider>\n      );\n    });\n\n    // Supabase fires INITIAL_SESSION before getSession resolves\n    await act(async () => {\n      authCallback(\"INITIAL_SESSION\", { user: fakeUser });\n    });\n\n    // Then SIGNED_IN fires (common Supabase behaviour on page load)\n    await act(async () => {\n      authCallback(\"SIGNED_IN\", { user: fakeUser });\n    });\n\n    expect(screen.queryByText(/Signed in as/)).not.toBeInTheDocument();\n    expect(mockTrackEvent).not.toHaveBeenCalledWith(\"sign_in\", expect.anything());\n\n    // Let getSession resolve to avoid dangling promise\n    await act(async () => {\n      resolveGetSession({ data: { session: { user: fakeUser } } });\n    });\n  });\n\n  it(\"shows toast again after sign-out then fresh sign-in\", async () => {\n    mockGetSession.mockResolvedValue({ data: { session: { user: fakeUser } } });\n\n    await act(async () => {\n      render(\n        <AuthProvider>\n          <TestConsumer />\n        </AuthProvider>\n      );\n    });\n\n    mockTrackEvent.mockClear();\n\n    // Simulate sign-out\n    await act(async () => {\n      authCallback(\"SIGNED_OUT\", null);\n    });\n\n    // Simulate fresh sign-in\n    await act(async () => {\n      authCallback(\"SIGNED_IN\", { user: fakeUser });\n    });\n\n    expect(screen.getByText(/Signed in as testuser/)).toBeInTheDocument();\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"sign_in\", { provider: \"github\" });\n  });\n});\n"
  },
  {
    "path": "src/components/layout/AuthContext.tsx",
    "content": "\"use client\";\n\nimport { createContext, useContext, useEffect, useState, useCallback, useRef, type ReactNode } from \"react\";\nimport { supabase } from \"@/lib/supabase\";\nimport { downloadAndMerge, scheduleUpload, flushPendingUpload, mergeFromRealtimePayload } from \"@/lib/sync\";\nimport { trackEvent } from \"@/lib/analytics\";\nimport type { User } from \"@supabase/supabase-js\";\n\ninterface AuthContextValue {\n  user: User | null;\n  loading: boolean;\n  signIn: () => Promise<void>;\n  signOut: () => Promise<void>;\n  syncNow: () => void;\n  syncVersion: number;\n}\n\nconst AuthContext = createContext<AuthContextValue>({\n  user: null,\n  loading: true,\n  signIn: async () => {},\n  signOut: async () => {},\n  syncNow: () => {},\n  syncVersion: 0,\n});\n\nexport function AuthProvider({ children }: { children: ReactNode }) {\n  const [user, setUser] = useState<User | null>(null);\n  const [loading, setLoading] = useState(true);\n  const [syncVersion, setSyncVersion] = useState(0);\n  const [toast, setToast] = useState<{ message: string; type: \"error\" | \"success\" } | null>(null);\n  const [toastFading, setToastFading] = useState(false);\n  const realtimeChannelRef = useRef<ReturnType<typeof supabase.channel> | null>(null);\n  const hasSessionRef = useRef(false);\n\n  useEffect(() => {\n    if (!toast) return;\n    const fadeTimer = setTimeout(() => setToastFading(true), 3000);\n    const removeTimer = setTimeout(() => { setToast(null); setToastFading(false); }, 3700);\n    return () => { clearTimeout(fadeTimer); clearTimeout(removeTimer); };\n  }, [toast]);\n\n  // Subscribe to realtime changes when user is signed in\n  useEffect(() => {\n    if (!user) {\n      // Clean up any existing subscription\n      if (realtimeChannelRef.current) {\n        supabase.removeChannel(realtimeChannelRef.current);\n        realtimeChannelRef.current = null;\n      }\n      return;\n    }\n\n    const channel = supabase\n      .channel(\"user_progress_sync\")\n      .on(\n        \"postgres_changes\",\n        {\n          event: \"UPDATE\",\n          schema: \"public\",\n          table: \"user_progress\",\n          filter: `user_id=eq.${user.id}`,\n        },\n        (payload) => {\n          const changed = mergeFromRealtimePayload(payload.new);\n          if (changed) {\n            trackEvent(\"realtime_sync\");\n            setSyncVersion((v) => v + 1);\n          }\n        }\n      )\n      .subscribe();\n\n    realtimeChannelRef.current = channel;\n\n    return () => {\n      supabase.removeChannel(channel);\n      realtimeChannelRef.current = null;\n    };\n  }, [user]);\n\n  useEffect(() => {\n    // Next.js App Router clears the URL hash during hydration before\n    // Supabase can detect it. We capture it early in an inline <script>\n    // and fall back to setSession if auto-detection missed it.\n    const savedHash = (window as Record<string, unknown>).__SUPABASE_AUTH_HASH__ as string | undefined;\n    if (savedHash) {\n      delete (window as Record<string, unknown>).__SUPABASE_AUTH_HASH__;\n      const params = new URLSearchParams(savedHash.substring(1));\n      const accessToken = params.get(\"access_token\");\n      const refreshToken = params.get(\"refresh_token\");\n      if (accessToken && refreshToken) {\n        supabase.auth.setSession({ access_token: accessToken, refresh_token: refreshToken });\n      }\n    }\n\n    supabase.auth.getSession().then(({ data: { session } }) => {\n      const u = session?.user ?? null;\n      setUser(u);\n      setLoading(false);\n      if (u) {\n        hasSessionRef.current = true;\n        downloadAndMerge(u.id).then(() => setSyncVersion((v) => v + 1));\n      }\n    });\n\n    const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {\n      const u = session?.user ?? null;\n      setUser(u);\n      if (u) downloadAndMerge(u.id).then(() => setSyncVersion((v) => v + 1));\n      if (event === \"INITIAL_SESSION\") {\n        // Supabase fires INITIAL_SESSION on load for existing sessions;\n        // mark it so the subsequent SIGNED_IN event doesn't show a toast.\n        if (u) hasSessionRef.current = true;\n      } else if (event === \"SIGNED_IN\") {\n        if (!hasSessionRef.current) {\n          trackEvent(\"sign_in\", { provider: \"github\" });\n          setToast({ message: `Signed in as ${u?.user_metadata?.user_name ?? \"user\"}`, type: \"success\" });\n        }\n        hasSessionRef.current = true;\n      }\n      if (event === \"SIGNED_OUT\") {\n        hasSessionRef.current = false;\n        trackEvent(\"sign_out\");\n      }\n    });\n\n    return () => subscription.unsubscribe();\n  }, []);\n\n  // Flush any pending debounced upload before the page unloads so that\n  // a refresh always sees the latest state in Supabase.\n  useEffect(() => {\n    const flush = () => flushPendingUpload();\n    const onVisChange = () => { if (document.visibilityState === \"hidden\") flush(); };\n    window.addEventListener(\"beforeunload\", flush);\n    document.addEventListener(\"visibilitychange\", onVisChange);\n    return () => {\n      window.removeEventListener(\"beforeunload\", flush);\n      document.removeEventListener(\"visibilitychange\", onVisChange);\n    };\n  }, []);\n\n  const signIn = useCallback(async () => {\n    const redirectTo = typeof window !== \"undefined\"\n      ? window.location.origin + window.location.pathname.replace(/\\/?$/, \"/\")\n      : undefined;\n    const { error } = await supabase.auth.signInWithOAuth({\n      provider: \"github\",\n      options: { redirectTo, scopes: \"\" },\n    });\n    if (error) {\n      trackEvent(\"sign_in_error\", { error: error.message });\n      setToast({ message: `Sign in failed: ${error.message}`, type: \"error\" });\n    }\n  }, []);\n\n  const signOut = useCallback(async () => {\n    const { error } = await supabase.auth.signOut();\n    if (error) {\n      setToast({ message: `Sign out failed: ${error.message}`, type: \"error\" });\n    } else {\n      setUser(null);\n      setToast({ message: \"Signed out\", type: \"success\" });\n    }\n  }, []);\n\n  const syncNow = useCallback(() => {\n    if (user) scheduleUpload(user.id);\n  }, [user]);\n\n  return (\n    <AuthContext.Provider value={{ user, loading, signIn, signOut, syncNow, syncVersion }}>\n      {children}\n      {toast && (\n        <div\n          className={`fixed inset-x-0 bottom-6 z-50 mx-auto w-fit animate-[fadeInUp_0.3s_ease-out] rounded-lg border px-4 py-3 text-sm font-medium shadow-lg transition-opacity duration-700 ease-in-out ${\n            toast.type === \"error\"\n              ? \"border-red-200 bg-red-50 text-red-800 dark:border-red-800 dark:bg-red-950 dark:text-red-200\"\n              : \"border-green-200 bg-green-50 text-green-800 dark:border-green-800 dark:bg-green-950 dark:text-green-200\"\n          } ${toastFading ? \"opacity-0\" : \"opacity-100\"}`}\n        >\n          {toast.type === \"error\" ? \"✕\" : \"✓\"} {toast.message}\n        </div>\n      )}\n    </AuthContext.Provider>\n  );\n}\n\nexport function useAuth() {\n  return useContext(AuthContext);\n}\n"
  },
  {
    "path": "src/components/layout/GitHubLink.tsx",
    "content": "const REPO = \"SeanPrashad/leetcode-patterns\";\n\nasync function getStarCount(): Promise<number | null> {\n  try {\n    const res = await fetch(`https://api.github.com/repos/${REPO}`, {\n      cache: \"force-cache\",\n    });\n    if (!res.ok) return null;\n    const data = await res.json();\n    return data.stargazers_count;\n  } catch {\n    return null;\n  }\n}\n\nfunction formatCount(n: number): string {\n  if (n >= 1000) return `${(n / 1000).toFixed(1).replace(/\\.0$/, \"\")}k`;\n  return String(n);\n}\n\nexport default async function GitHubLink() {\n  const stars = await getStarCount();\n\n  return (\n    <a\n      href={`https://github.com/${REPO}`}\n      target=\"_blank\"\n      rel=\"noopener noreferrer\"\n      title=\"Star on GitHub\"\n      className=\"flex items-center gap-1.5 rounded-lg border border-zinc-300 py-2 pl-2 pr-2.5 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n    >\n      <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n        <path d=\"M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12\" />\n      </svg>\n      {stars !== null && (\n        <span className=\"hidden text-xs font-medium text-zinc-600 sm:inline dark:text-zinc-400\">\n          ⭐ {formatCount(stars)}\n        </span>\n      )}\n    </a>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/Logo.tsx",
    "content": "export default function Logo() {\n  const basePath = process.env.NEXT_PUBLIC_BASE_PATH ?? \"\";\n\n  return (\n    <>\n      {/* eslint-disable-next-line @next/next/no-img-element */}\n      <img\n        src={`${basePath}/images/logo-light.png`}\n        alt=\"Leetcode Patterns\"\n        className=\"h-8 dark:hidden sm:h-10\"\n      />\n      {/* eslint-disable-next-line @next/next/no-img-element */}\n      <img\n        src={`${basePath}/images/logo-dark.png`}\n        alt=\"Leetcode Patterns\"\n        className=\"hidden h-8 dark:block sm:h-10\"\n      />\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/ServiceWorkerRegistrar.tsx",
    "content": "\"use client\";\n\nimport { useEffect } from \"react\";\nimport { registerServiceWorker } from \"@/lib/register-sw\";\n\nexport default function ServiceWorkerRegistrar() {\n  useEffect(() => {\n    registerServiceWorker(process.env.NEXT_PUBLIC_BASE_PATH ?? \"\");\n  }, []);\n\n  return null;\n}\n"
  },
  {
    "path": "src/components/layout/ThemeToggle.test.tsx",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\n\nconst { mockTrackEvent } = vi.hoisted(() => ({\n  mockTrackEvent: vi.fn(),\n}));\n\nvi.mock(\"@/lib/analytics\", () => ({\n  trackEvent: mockTrackEvent,\n}));\n\nimport ThemeToggle from \"@/components/layout/ThemeToggle\";\n\ndescribe(\"ThemeToggle analytics\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    document.documentElement.classList.remove(\"dark\");\n  });\n\n  it(\"tracks theme_toggle with 'dark' when switching to dark mode\", async () => {\n    const user = userEvent.setup();\n    render(<ThemeToggle />);\n    const button = screen.getByRole(\"button\", { name: /switch to dark mode/i });\n    await user.click(button);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"theme_toggle\", { theme: \"dark\" });\n  });\n\n  it(\"tracks theme_toggle with 'light' when switching to light mode\", async () => {\n    const user = userEvent.setup();\n    document.documentElement.classList.add(\"dark\");\n    render(<ThemeToggle />);\n    const button = screen.getByRole(\"button\", { name: /switch to light mode/i });\n    await user.click(button);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"theme_toggle\", { theme: \"light\" });\n  });\n});\n"
  },
  {
    "path": "src/components/layout/ThemeToggle.tsx",
    "content": "\"use client\";\n\nimport { useCallback, useSyncExternalStore } from \"react\";\nimport { Sun, Moon } from \"lucide-react\";\nimport { trackEvent } from \"@/lib/analytics\";\n\nfunction subscribe(callback: () => void) {\n  const observer = new MutationObserver(callback);\n  observer.observe(document.documentElement, {\n    attributes: true,\n    attributeFilter: [\"class\"],\n  });\n  return () => observer.disconnect();\n}\n\nfunction getSnapshot() {\n  return document.documentElement.classList.contains(\"dark\");\n}\n\nfunction getServerSnapshot() {\n  return false;\n}\n\nexport default function ThemeToggle() {\n  const dark = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);\n\n  const toggle = useCallback(() => {\n    const next = !dark;\n    document.documentElement.classList.toggle(\"dark\", next);\n    localStorage.setItem(\"theme\", next ? \"dark\" : \"light\");\n    trackEvent(\"theme_toggle\", { theme: next ? \"dark\" : \"light\" });\n  }, [dark]);\n\n  return (\n    <button\n      onClick={toggle}\n      aria-label={dark ? \"Switch to light mode\" : \"Switch to dark mode\"}\n      title={dark ? \"Switch to light mode\" : \"Switch to dark mode\"}\n      className=\"rounded-lg border border-zinc-300 p-2 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n    >\n      {dark ? (\n        <Sun className=\"h-4 w-4 text-yellow-500\" />\n      ) : (\n        <Moon className=\"h-4 w-4 text-blue-600\" />\n      )}\n    </button>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/UserMenu.test.tsx",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { render, screen, cleanup } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\n\nconst { mockUseAuth } = vi.hoisted(() => ({\n  mockUseAuth: { current: { user: null, loading: false, signIn: vi.fn(), signOut: vi.fn(), syncNow: vi.fn(), syncVersion: 0 } },\n}));\n\nvi.mock(\"@/components/layout/AuthContext\", () => ({\n  useAuth: () => mockUseAuth.current,\n}));\n\nvi.mock(\"@/lib/supabase\", () => ({\n  supabase: {},\n}));\n\nimport UserMenu from \"@/components/layout/UserMenu\";\n\ndescribe(\"UserMenu\", () => {\n  beforeEach(() => {\n    mockUseAuth.current = {\n      user: null,\n      loading: false,\n      signIn: vi.fn(),\n      signOut: vi.fn(),\n      syncNow: vi.fn(),\n      syncVersion: 0,\n    };\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"renders nothing when loading\", () => {\n    mockUseAuth.current = { ...mockUseAuth.current, loading: true };\n    const { container } = render(<UserMenu />);\n    expect(container.innerHTML).toBe(\"\");\n  });\n\n  it(\"renders Sign in with GitHub button when no user\", () => {\n    render(<UserMenu />);\n    expect(screen.getByRole(\"button\", { name: \"Sign in with GitHub\" })).toBeInTheDocument();\n  });\n\n  it(\"renders user avatar when signed in\", () => {\n    mockUseAuth.current = {\n      ...mockUseAuth.current,\n      user: { user_metadata: { avatar_url: \"https://example.com/avatar.png\", user_name: \"testuser\" } } as never,\n    };\n    render(<UserMenu />);\n    const avatar = screen.getByAltText(\"testuser\");\n    expect(avatar).toBeInTheDocument();\n    expect(avatar).toHaveAttribute(\"src\", \"https://example.com/avatar.png\");\n  });\n\n  it(\"shows dropdown with Signed in as and username when avatar clicked\", async () => {\n    mockUseAuth.current = {\n      ...mockUseAuth.current,\n      user: { user_metadata: { avatar_url: \"https://example.com/avatar.png\", user_name: \"testuser\" } } as never,\n    };\n    const user = userEvent.setup();\n    render(<UserMenu />);\n    await user.click(screen.getByAltText(\"testuser\"));\n    expect(screen.getByText(\"Signed in as\")).toBeInTheDocument();\n    expect(screen.getByText(\"testuser\")).toBeInTheDocument();\n  });\n\n  it(\"calls signOut when Sign out is clicked\", async () => {\n    const mockSignOut = vi.fn();\n    mockUseAuth.current = {\n      ...mockUseAuth.current,\n      user: { user_metadata: { avatar_url: \"https://example.com/avatar.png\", user_name: \"testuser\" } } as never,\n      signOut: mockSignOut,\n    };\n    const user = userEvent.setup();\n    render(<UserMenu />);\n    await user.click(screen.getByAltText(\"testuser\"));\n    await user.click(screen.getByText(\"Sign out\"));\n    expect(mockSignOut).toHaveBeenCalled();\n  });\n\n  it(\"closes dropdown when clicking outside\", async () => {\n    mockUseAuth.current = {\n      ...mockUseAuth.current,\n      user: { user_metadata: { avatar_url: \"https://example.com/avatar.png\", user_name: \"testuser\" } } as never,\n    };\n    const user = userEvent.setup();\n    render(<UserMenu />);\n    await user.click(screen.getByAltText(\"testuser\"));\n    expect(screen.getByText(\"Signed in as\")).toBeInTheDocument();\n    await user.click(document.body);\n    expect(screen.queryByText(\"Signed in as\")).not.toBeInTheDocument();\n  });\n});\n"
  },
  {
    "path": "src/components/layout/UserMenu.tsx",
    "content": "\"use client\";\n\nimport { useState, useRef, useEffect } from \"react\";\nimport { useAuth } from \"./AuthContext\";\n\nexport default function UserMenu() {\n  const { user, loading, signIn, signOut } = useAuth();\n  const [open, setOpen] = useState(false);\n  const menuRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    if (!open) return;\n    const handleClick = (e: MouseEvent) => {\n      if (menuRef.current && !menuRef.current.contains(e.target as Node)) {\n        setOpen(false);\n      }\n    };\n    document.addEventListener(\"mousedown\", handleClick);\n    return () => document.removeEventListener(\"mousedown\", handleClick);\n  }, [open]);\n\n  if (loading) return null;\n\n  if (!user) {\n    return (\n      <button\n        onClick={signIn}\n        className=\"rounded-lg border border-zinc-300 px-3 py-1.5 text-sm font-medium transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n      >\n        Sign in with GitHub\n      </button>\n    );\n  }\n\n  return (\n    <div className=\"relative\" ref={menuRef}>\n      <button\n        onClick={() => setOpen((prev) => !prev)}\n        className=\"flex items-center gap-2 rounded-lg border border-zinc-300 p-1 transition-colors hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n      >\n        {/* eslint-disable-next-line @next/next/no-img-element */}\n        <img\n          src={user.user_metadata.avatar_url}\n          alt={user.user_metadata.user_name}\n          className=\"h-6 w-6 rounded-full\"\n        />\n      </button>\n      {open && (\n        <div className=\"absolute right-0 top-full z-10 mt-1.5 w-48 rounded-lg border border-zinc-200 bg-white p-2 shadow-lg dark:border-zinc-700 dark:bg-zinc-900\">\n          <p className=\"px-2 py-1 text-xs text-zinc-500\">Signed in as</p>\n          <p className=\"truncate px-2 pb-1 text-sm font-medium\">\n            {user.user_metadata.user_name}\n          </p>\n          <button\n            onClick={() => { setOpen(false); signOut(); }}\n            className=\"w-full rounded px-2 py-1 text-left text-sm text-red-600 hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n          >\n            Sign out\n          </button>\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/layout/ViewSwitcher.tsx",
    "content": "\"use client\";\n\nimport { useState, useEffect, useSyncExternalStore, useCallback, useRef } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport { TableProperties, Map, Trophy } from \"lucide-react\";\nimport QuestionsTable from \"@/components/questions/QuestionsTable\";\nimport RoadmapView from \"@/components/roadmaps/RoadmapView\";\nimport { Question } from \"@/types/question\";\nimport { beginnerRoadmap, experiencedRoadmap } from \"@/data/roadmaps\";\nimport { trackEvent } from \"@/lib/analytics\";\n\ntype View = \"table\" | \"beginner\" | \"experienced\";\n\nconst VIEW_KEY = \"leetcode-patterns-view\";\n\nconst views: { id: View; label: string; icon: typeof TableProperties; description: string }[] = [\n  { id: \"table\", label: \"All Questions\", icon: TableProperties, description: \"Browse all questions with filters\" },\n  { id: \"beginner\", label: \"Beginner Roadmap\", icon: Map, description: \"Structured path for newcomers\" },\n  { id: \"experienced\", label: \"Experienced Roadmap\", icon: Trophy, description: \"Must-know problems for experienced engineers\" },\n];\n\nfunction isValidView(v: string | null): v is View {\n  return v !== null && views.some((view) => view.id === v);\n}\n\nexport default function ViewSwitcher({\n  questions,\n  updatedDate,\n}: {\n  questions: Question[];\n  updatedDate: string;\n}) {\n  const searchParams = useSearchParams();\n\n  const paramView = searchParams.get(\"view\");\n\n  const storedView = useSyncExternalStore(\n    () => () => {},\n    () => {\n      if (isValidView(paramView)) return paramView;\n      const stored = localStorage.getItem(VIEW_KEY) as View | null;\n      return stored && isValidView(stored) ? stored : \"table\";\n    },\n    () => (isValidView(paramView) ? paramView : \"table\"),\n  );\n\n  const [activeView, setActiveView] = useState<View>(storedView);\n  const [displayedView, setDisplayedView] = useState<View>(storedView);\n  const [fading, setFading] = useState(false);\n  const pendingView = useRef<View | null>(null);\n\n  const switchView = useCallback((view: View) => {\n    setActiveView(view);\n    localStorage.setItem(VIEW_KEY, view);\n    const params = new URLSearchParams(window.location.search);\n    if (view === \"table\") {\n      params.delete(\"view\");\n    } else {\n      params.set(\"view\", view);\n    }\n    const qs = params.toString();\n    window.history.replaceState(null, \"\", qs ? `?${qs}` : window.location.pathname);\n    trackEvent(\"switch_view\", { view });\n\n    pendingView.current = view;\n    setFading(true);\n  }, []);\n\n  useEffect(() => {\n    if (!fading || pendingView.current === null) return;\n    const timeout = setTimeout(() => {\n      if (pendingView.current !== null) {\n        setDisplayedView(pendingView.current);\n        pendingView.current = null;\n        setFading(false);\n      }\n    }, 200);\n    return () => clearTimeout(timeout);\n  }, [fading]);\n\n  const handleTransitionEnd = useCallback(() => {\n    if (fading && pendingView.current !== null) {\n      setDisplayedView(pendingView.current);\n      pendingView.current = null;\n      setFading(false);\n    }\n  }, [fading]);\n\n  return (\n    <>\n      {/* View tabs */}\n      <div className=\"relative mb-4 flex gap-1 overflow-hidden rounded-lg p-1 dark:bg-zinc-900\"\n        style={{ background: \"linear-gradient(90deg, #FFD200, #FFA500, #FF7800)\" }}\n      >\n        <div className=\"pointer-events-none absolute inset-0 hidden dark:block\"\n          style={{ background: \"linear-gradient(90deg, rgba(255,210,0,0.08), rgba(255,165,0,0.08), rgba(255,120,0,0.08))\", backgroundColor: \"rgba(24,24,27,0.60)\" }}\n        />\n        {views.map((v) => {\n          const Icon = v.icon;\n          const isActive = activeView === v.id;\n          return (\n            <button\n              key={v.id}\n              onClick={() => switchView(v.id)}\n              className={`relative flex flex-1 items-center justify-center gap-2 rounded-md px-3 py-2 text-sm font-bold transition-all ${\n                isActive\n                  ? \"bg-white/80 text-zinc-900 shadow-sm dark:bg-zinc-800/80 dark:text-amber-200 dark:shadow-amber-900/20\"\n                  : \"text-amber-950 hover:bg-white/40 hover:text-zinc-900 dark:text-amber-200/70 dark:hover:bg-zinc-800/40 dark:hover:text-amber-200\"\n              }`}\n            >\n              <Icon className=\"h-4 w-4\" />\n              <span className=\"hidden sm:inline\">{v.label}</span>\n            </button>\n          );\n        })}\n      </div>\n\n      {/* View content */}\n      <div\n        className={`transition-opacity duration-150 ${fading ? \"opacity-0\" : \"opacity-100\"}`}\n        onTransitionEnd={handleTransitionEnd}\n      >\n        {displayedView === \"table\" && (\n          <QuestionsTable data={questions} updatedDate={updatedDate} />\n        )}\n        {displayedView === \"beginner\" && (\n          <RoadmapView roadmap={beginnerRoadmap} questions={questions} />\n        )}\n        {displayedView === \"experienced\" && (\n          <RoadmapView roadmap={experiencedRoadmap} questions={questions} />\n        )}\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/panels/AboutPanel.tsx",
    "content": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { Info, X } from \"lucide-react\";\nimport { trackEvent } from \"@/lib/analytics\";\n\nexport default function AboutPanel() {\n  const [open, setOpen] = useState(false);\n\n  useEffect(() => {\n    if (!open) return;\n    const handleKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") { setOpen(false); trackEvent(\"panel_close\", { panel: \"about\" }); }\n    };\n    window.addEventListener(\"keydown\", handleKey);\n    return () => window.removeEventListener(\"keydown\", handleKey);\n  }, [open]);\n\n  return (\n    <>\n      {/* Tab button – rendered inline inside the fixed flex wrapper in page.tsx */}\n      <button\n        onClick={() => { setOpen(true); trackEvent(\"panel_open\", { panel: \"about\" }); }}\n        className=\"rounded-r-xl bg-emerald-600 px-2.5 py-4 text-white shadow-lg transition-colors hover:bg-emerald-700\"\n        aria-label=\"Open about\"\n      >\n        <span className=\"flex items-center gap-2 text-sm font-semibold [writing-mode:vertical-lr]\">\n          <Info className=\"h-4 w-4 rotate-90\" />\n          About\n        </span>\n      </button>\n\n      {/* Panel */}\n      <div\n        className={`fixed left-0 top-0 z-50 h-full w-80 transform border-r border-zinc-200 bg-white shadow-xl transition-transform duration-300 ease-in-out dark:border-zinc-700 dark:bg-zinc-900 ${\n          open ? \"translate-x-0\" : \"-translate-x-full\"\n        }`}\n      >\n        <div className=\"flex items-center justify-between bg-emerald-600 px-4 py-3 text-white\">\n          <h2 className=\"flex items-center gap-2 text-base font-semibold\">\n            <Info className=\"h-4 w-4\" />\n            About\n          </h2>\n          <button\n            onClick={() => { setOpen(false); trackEvent(\"panel_close\", { panel: \"about\" }); }}\n            className=\"rounded p-1 transition-colors hover:bg-emerald-700\"\n          >\n            <X className=\"h-4 w-4\" />\n          </button>\n        </div>\n        <div className=\"h-[calc(100%-49px)] overflow-y-auto px-5 py-5\">\n          <div className=\"space-y-5 text-sm leading-relaxed text-zinc-600 dark:text-zinc-400\">\n            <p>\n              In <span className=\"font-semibold text-zinc-900 dark:text-zinc-100\">2019</span>, as a broke college\n              student who couldn&apos;t afford premium interview resources, I spent countless hours searching\n              for free materials and teaching myself React to build{\" \"}\n              <span className=\"font-semibold text-zinc-900 dark:text-zinc-100\">Leetcode Patterns</span>.\n            </p>\n            <p>\n              I believe <span className=\"font-semibold text-zinc-900 dark:text-zinc-100\">everyone</span> deserves\n              access to high-quality interview material - regardless of their financial situation. It&apos;s why I chose to make this website{\" \"}\n              <a\n                href=\"https://github.com/seanprashad/leetcode-patterns/blob/main/LICENSE\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"font-semibold text-blue-600 hover:underline dark:text-blue-400\"\n              >free and open source</a>.\n            </p>\n            <p>\n              Best of luck on your journey!\n            </p>\n            <p className=\"text-2xl text-zinc-900 dark:text-zinc-100\" style={{ fontFamily: \"var(--font-dancing-script)\" }}>\n              <a\n                href=\"https://github.com/SeanPrashad\"\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"underline decoration-dotted underline-offset-4 hover:text-blue-600 hover:decoration-solid dark:hover:text-blue-400\"\n              >Sean</a>\n            </p>\n          </div>\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/panels/AcknowledgementsPanel.tsx",
    "content": "\"use client\";\n\nimport { useState, useEffect } from \"react\";\nimport { Heart, X } from \"lucide-react\";\nimport { trackEvent } from \"@/lib/analytics\";\n\nconst sources = [\n  {\n    title: \"Blind Curated 75 Question List\",\n    url: \"https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-100-LeetCode-Questions-to-Save-Your-Time-OaM1orEU\",\n    image: \"/images/Blind.png\",\n  },\n  {\n    title: \"Grokking the Coding Interview: Patterns for Coding Questions\",\n    url: \"https://www.designgurus.io/course/grokking-the-coding-interview\",\n    image: \"/images/DesignGurus.png\",\n  },\n  {\n    title: \"14 Patterns to Ace Any Coding Interview Question\",\n    url: \"https://hackernoon.com/14-patterns-to-ace-any-coding-interview-question-c5bb3357f6ed\",\n    image: \"/images/Hackernoon.png\",\n  },\n];\n\nexport default function AcknowledgementsPanel() {\n  const [open, setOpen] = useState(false);\n\n  useEffect(() => {\n    if (!open) return;\n    const handleKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") { setOpen(false); trackEvent(\"panel_close\", { panel: \"acknowledgements\" }); }\n    };\n    window.addEventListener(\"keydown\", handleKey);\n    return () => window.removeEventListener(\"keydown\", handleKey);\n  }, [open]);\n\n  return (\n    <>\n      {/* Tab button – rendered inline inside the fixed flex wrapper in page.tsx */}\n      <button\n        onClick={() => { setOpen(true); trackEvent(\"panel_open\", { panel: \"acknowledgements\" }); }}\n        className=\"rounded-r-xl bg-amber-600 px-2.5 py-4 text-white shadow-lg transition-colors hover:bg-amber-700\"\n        aria-label=\"Open acknowledgements\"\n      >\n        <span className=\"flex items-center gap-2 text-sm font-semibold [writing-mode:vertical-lr]\">\n          <Heart className=\"h-4 w-4 rotate-90\" />\n          Acknowledgements\n        </span>\n      </button>\n\n      {/* Panel */}\n      <div\n        className={`fixed left-0 top-0 z-50 h-full w-80 transform border-r border-zinc-200 bg-white shadow-xl transition-transform duration-300 ease-in-out dark:border-zinc-700 dark:bg-zinc-900 ${\n          open ? \"translate-x-0\" : \"-translate-x-full\"\n        }`}\n      >\n        <div className=\"flex items-center justify-between bg-amber-600 px-4 py-3 text-white\">\n          <h2 className=\"flex items-center gap-2 text-base font-semibold\">\n            <Heart className=\"h-4 w-4\" />\n            Acknowledgements\n          </h2>\n          <button\n            onClick={() => { setOpen(false); trackEvent(\"panel_close\", { panel: \"acknowledgements\" }); }}\n            className=\"rounded p-1 transition-colors hover:bg-amber-700\"\n          >\n            <X className=\"h-4 w-4\" />\n          </button>\n        </div>\n        <div className=\"h-[calc(100%-49px)] overflow-y-auto px-4 py-4\">\n          <p className=\"mb-4 text-xs text-zinc-500\">\n            Leetcode Patterns wouldn&apos;t exist without the following resources:\n          </p>\n          <div className=\"space-y-4\">\n            {sources.map((source) => (\n              <a\n                key={source.title}\n                href={source.url}\n                target=\"_blank\"\n                rel=\"noopener noreferrer\"\n                className=\"block overflow-hidden rounded-lg border border-zinc-200 transition-colors hover:border-blue-300 dark:border-zinc-800 dark:hover:border-blue-700\"\n              >\n                {/* eslint-disable-next-line @next/next/no-img-element */}\n              <img\n                  src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? \"\"}${source.image}`}\n                  alt={source.title}\n                  className=\"h-32 w-full object-cover\"\n                />\n                <div className=\"p-3\">\n                  <p className=\"text-sm font-medium text-blue-600 dark:text-blue-400\">\n                    {source.title}\n                  </p>\n                </div>\n              </a>\n            ))}\n          </div>\n        </div>\n      </div>\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/panels/TipsPanel.tsx",
    "content": "\"use client\";\n\nimport { useState, useCallback, useEffect, Fragment } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { Lightbulb, X, Copy, Check } from \"lucide-react\";\nimport { trackEvent } from \"@/lib/analytics\";\n\nfunction formatApproach(text: string) {\n  const parts = text.split(/(O\\([^)]*\\)|\\bK\\b)/g);\n  return parts.map((part, i) =>\n    /^O\\(/.test(part) || /^\\bK\\b$/.test(part) ? (\n      <code key={i} className=\"rounded bg-zinc-100 px-1 py-0.5 font-mono text-xs dark:bg-zinc-800\">{part}</code>\n    ) : (\n      part\n    )\n  );\n}\n\nconst tipGroups = [\n  { label: \"Arrays & Strings\", tips: [\n    { condition: \"If input array is sorted\", approaches: [\"Binary search\", \"Two pointers\"] },\n    { condition: \"If need O(1) lookup\", approaches: [\"Hash table\", \"Hash set\"] },\n    { condition: \"If must solve in-place\", approaches: [\"Swap corresponding values\", \"Store multiple values in the same pointer\"] },\n    { condition: \"If asked for common strings\", approaches: [\"Map\", \"Trie\"] },\n    { condition: \"If asked to count bits or use XOR\", approaches: [\"Bit manipulation\"] },\n  ]},\n  { label: \"Subarrays & Sequences\", tips: [\n    { condition: \"If asked for max/min subarray/subset\", approaches: [\"Dynamic programming\", \"Sliding window\"] },\n    { condition: \"If asked for sliding window max/min\", approaches: [\"Monotonic queue\"] },\n    { condition: \"If asked for next greater/smaller element\", approaches: [\"Monotonic stack\"] },\n    { condition: \"If need range sum/frequency queries\", approaches: [\"Prefix sum\", \"Binary indexed tree\", \"Segment tree\"] },\n  ]},\n  { label: \"Trees & Graphs\", tips: [\n    { condition: \"If given a tree\", approaches: [\"DFS\", \"BFS\"] },\n    { condition: \"If given a graph\", approaches: [\"DFS\", \"BFS\", \"Union-Find\"] },\n    { condition: \"If given a matrix\", approaches: [\"BFS\", \"DFS\", \"Dynamic programming\"] },\n    { condition: \"If asked for connectivity/grouping\", approaches: [\"Union-Find\", \"DFS\"] },\n    { condition: \"If asked for ordering/scheduling\", approaches: [\"Topological sort\"] },\n  ]},\n  { label: \"Linked Lists & Stacks\", tips: [\n    { condition: \"If given a linked list\", approaches: [\"Two pointers\"] },\n    { condition: \"If recursion is banned\", approaches: [\"Stack\"] },\n  ]},\n  { label: \"Sorting & Intervals\", tips: [\n    { condition: \"If asked for top/least K items\", approaches: [\"Heap\", \"Quickselect\", \"Bucket sort\"] },\n    { condition: \"If asked to merge sorted lists/intervals\", approaches: [\"Merge sort\", \"Heap\"] },\n    { condition: \"If asked for overlapping intervals\", approaches: [\"Sorting\", \"Sweep line\"] },\n    { condition: \"If given a stream of data\", approaches: [\"Heap\", \"Design\"] },\n  ]},\n  { label: \"Optimization\", tips: [\n    { condition: \"If asked for all permutations/subsets\", approaches: [\"Backtracking\"] },\n    { condition: \"If need to count/divide optimally\", approaches: [\"Greedy\", \"Dynamic programming\"] },\n  ]},\n  { label: \"General\", tips: [\n    { condition: \"Else\", approaches: [\"Map/Set for O(1) time & O(n) space\", \"Sort input for O(nlogn) time and O(1) space\"] },\n  ]},\n];\n\nexport default function TipsPanel() {\n  const [open, setOpen] = useState(false);\n  const [copied, setCopied] = useState(false);\n  const [toastFading, setToastFading] = useState(false);\n\n  useEffect(() => {\n    if (!open) return;\n    const handleKey = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") { setOpen(false); trackEvent(\"panel_close\", { panel: \"tips\" }); }\n    };\n    window.addEventListener(\"keydown\", handleKey);\n    return () => window.removeEventListener(\"keydown\", handleKey);\n  }, [open]);\n\n  const copyToClipboard = useCallback(() => {\n    const text = tipGroups\n      .map((group) => {\n        const rows = group.tips\n          .map((tip) => `  ${tip.condition} → ${tip.approaches.join(\", \")}`)\n          .join(\"\\n\");\n        return `${group.label}\\n${rows}`;\n      })\n      .join(\"\\n\\n\");\n    navigator.clipboard.writeText(text);\n    setCopied(true);\n    setToastFading(false);\n    trackEvent(\"copy_tips\");\n  }, []);\n\n  useEffect(() => {\n    if (!copied) return;\n    const fadeTimer = setTimeout(() => setToastFading(true), 1500);\n    const removeTimer = setTimeout(() => { setCopied(false); setToastFading(false); }, 2200);\n    return () => { clearTimeout(fadeTimer); clearTimeout(removeTimer); };\n  }, [copied]);\n\n  return (\n    <>\n      {/* Tab button – rendered inline inside the fixed flex wrapper in page.tsx */}\n      <button\n        onClick={() => { setOpen(true); trackEvent(\"panel_open\", { panel: \"tips\" }); }}\n        className=\"rounded-r-xl bg-blue-600 px-2.5 py-4 text-white shadow-lg transition-colors hover:bg-blue-700\"\n        aria-label=\"Open tips\"\n      >\n        <span className=\"flex items-center gap-2 text-sm font-semibold [writing-mode:vertical-lr]\">\n          <Lightbulb className=\"h-4 w-4 rotate-90\" />\n          Helpful Tips\n        </span>\n      </button>\n\n      {/* Panel */}\n      <div\n        className={`fixed left-0 top-0 z-50 h-full w-80 transform border-r border-zinc-200 bg-white shadow-xl transition-transform duration-300 ease-in-out dark:border-zinc-700 dark:bg-zinc-900 ${\n          open ? \"translate-x-0\" : \"-translate-x-full\"\n        }`}\n      >\n        <div className=\"flex items-center justify-between bg-blue-600 px-4 py-3 text-white\">\n          <h2 className=\"flex items-center gap-2 text-base font-semibold\">\n            <Lightbulb className=\"h-4 w-4\" />\n            Helpful Tips\n          </h2>\n          <div className=\"flex items-center gap-1\">\n            <button\n              onClick={copyToClipboard}\n              className=\"rounded p-1 transition-colors hover:bg-blue-700\"\n              title=\"Copy to clipboard\"\n            >\n              {copied ? <Check className=\"h-4 w-4\" /> : <Copy className=\"h-4 w-4\" />}\n            </button>\n            <button\n              onClick={() => { setOpen(false); trackEvent(\"panel_close\", { panel: \"tips\" }); }}\n              className=\"rounded p-1 transition-colors hover:bg-blue-700\"\n            >\n              <X className=\"h-4 w-4\" />\n            </button>\n          </div>\n        </div>\n        <div className=\"h-[calc(100%-49px)] overflow-y-auto px-4 py-4\">\n          <p className=\"mb-4 text-xs text-zinc-500\">\n            Based on the problem constraints, use these heuristics to identify possible approaches when unsure.\n          </p>\n          <table className=\"w-full text-sm\">\n            <thead>\n              <tr className=\"border-b border-zinc-200 dark:border-zinc-700\">\n                <th className=\"pb-2 text-left font-semibold\">Condition</th>\n                <th className=\"pb-2 text-left font-semibold\">Approach</th>\n              </tr>\n            </thead>\n            <tbody>\n              {tipGroups.map((group) => (\n                <Fragment key={group.label}>\n                  <tr>\n                    <td colSpan={2} className=\"pt-4 pb-1 text-xs font-semibold uppercase tracking-wide text-zinc-400 dark:text-zinc-500\">\n                      {group.label}\n                    </td>\n                  </tr>\n                  {group.tips.map((tip) => (\n                    <tr key={tip.condition} className=\"border-b border-zinc-100 dark:border-zinc-800\">\n                      <td className=\"py-2 pr-3 align-top text-zinc-700 dark:text-zinc-300\">{formatApproach(tip.condition)}</td>\n                      <td className=\"py-2 align-top text-zinc-600 dark:text-zinc-400\">\n                        {tip.approaches.map((a, i) => (\n                          <span key={i}>{i > 0 && \", \"}{formatApproach(a)}</span>\n                        ))}\n                      </td>\n                    </tr>\n                  ))}\n                </Fragment>\n              ))}\n            </tbody>\n          </table>\n        </div>\n      </div>\n\n      {/* Copy toast – portalled to body so it centres on the viewport */}\n      {copied && createPortal(\n        <div\n          className={`fixed inset-x-0 bottom-6 z-50 mx-auto w-fit animate-[fadeInUp_0.3s_ease-out] rounded-lg border border-blue-200 bg-blue-50 px-4 py-3 text-sm font-medium text-blue-800 shadow-lg transition-opacity duration-700 ease-in-out dark:border-blue-800 dark:bg-blue-950 dark:text-blue-200 ${toastFading ? \"opacity-0\" : \"opacity-100\"}`}\n        >\n          ✓ Tips copied to clipboard\n        </div>,\n        document.body\n      )}\n    </>\n  );\n}\n"
  },
  {
    "path": "src/components/panels/panels.test.tsx",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { render, screen } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\n\nconst { mockTrackEvent } = vi.hoisted(() => ({\n  mockTrackEvent: vi.fn(),\n}));\n\nvi.mock(\"@/lib/analytics\", () => ({\n  trackEvent: mockTrackEvent,\n}));\n\nimport AboutPanel from \"@/components/panels/AboutPanel\";\nimport TipsPanel from \"@/components/panels/TipsPanel\";\nimport AcknowledgementsPanel from \"@/components/panels/AcknowledgementsPanel\";\n\ndescribe(\"AboutPanel analytics\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n  });\n\n  it(\"tracks panel_open when About tab is clicked\", async () => {\n    const user = userEvent.setup();\n    render(<AboutPanel />);\n    await user.click(screen.getByLabelText(\"Open about\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"panel_open\", { panel: \"about\" });\n  });\n\n  it(\"tracks panel_close when close button is clicked\", async () => {\n    const user = userEvent.setup();\n    render(<AboutPanel />);\n    await user.click(screen.getByLabelText(\"Open about\"));\n    mockTrackEvent.mockClear();\n    const heading = screen.getByRole(\"heading\", { name: /about/i });\n    const closeBtn = heading.closest(\"div\")!.querySelector(\"button\")!;\n    await user.click(closeBtn);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"panel_close\", { panel: \"about\" });\n  });\n});\n\ndescribe(\"TipsPanel analytics\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n  });\n\n  it(\"tracks panel_open when Tips tab is clicked\", async () => {\n    const user = userEvent.setup();\n    render(<TipsPanel />);\n    await user.click(screen.getByLabelText(\"Open tips\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"panel_open\", { panel: \"tips\" });\n  });\n\n  it(\"tracks panel_close when close button is clicked\", async () => {\n    const user = userEvent.setup();\n    render(<TipsPanel />);\n    await user.click(screen.getByLabelText(\"Open tips\"));\n    mockTrackEvent.mockClear();\n    const heading = screen.getByRole(\"heading\", { name: /helpful tips/i });\n    const buttons = heading.closest(\"div\")!.querySelectorAll(\"button\");\n    const closeBtn = buttons[buttons.length - 1];\n    await user.click(closeBtn);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"panel_close\", { panel: \"tips\" });\n  });\n});\n\ndescribe(\"AcknowledgementsPanel analytics\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n  });\n\n  it(\"tracks panel_open when Acknowledgements tab is clicked\", async () => {\n    const user = userEvent.setup();\n    render(<AcknowledgementsPanel />);\n    await user.click(screen.getByLabelText(\"Open acknowledgements\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"panel_open\", { panel: \"acknowledgements\" });\n  });\n\n  it(\"tracks panel_close when close button is clicked\", async () => {\n    const user = userEvent.setup();\n    render(<AcknowledgementsPanel />);\n    await user.click(screen.getByLabelText(\"Open acknowledgements\"));\n    mockTrackEvent.mockClear();\n    const heading = screen.getByRole(\"heading\", { name: /acknowledgements/i });\n    const closeBtn = heading.closest(\"div\")!.querySelector(\"button\")!;\n    await user.click(closeBtn);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"panel_close\", { panel: \"acknowledgements\" });\n  });\n});\n"
  },
  {
    "path": "src/components/questions/ConfirmModal.tsx",
    "content": "export default function ConfirmModal({\n  title,\n  message,\n  confirmLabel,\n  onConfirm,\n  onCancel,\n}: {\n  title: string;\n  message: React.ReactNode;\n  confirmLabel: string;\n  onConfirm: () => void;\n  onCancel: () => void;\n}) {\n  return (\n    <div\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n      onClick={onCancel}\n      role=\"dialog\"\n      aria-modal=\"true\"\n      aria-label={title}\n    >\n      <div\n        className=\"mx-4 w-full max-w-sm rounded-xl border border-zinc-200 bg-white p-6 shadow-xl dark:border-zinc-700 dark:bg-zinc-900\"\n        onClick={(e) => e.stopPropagation()}\n      >\n        <h2 className=\"mb-2 text-lg font-semibold\">\n          {title}\n        </h2>\n        <p className=\"mb-4 text-sm text-zinc-500\">\n          {message}\n        </p>\n        <div className=\"flex justify-end gap-2\">\n          <button\n            onClick={onCancel}\n            className=\"rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n          >\n            Cancel\n          </button>\n          <button\n            onClick={onConfirm}\n            className=\"rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700\"\n          >\n            {confirmLabel}\n          </button>\n        </div>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/questions/FilterToolbar.tsx",
    "content": "import { useState, useMemo, useEffect, useRef } from \"react\";\nimport { type Table } from \"@tanstack/react-table\";\nimport { Question } from \"@/types/question\";\nimport { RotateCcw, Shuffle, Download, Upload, Trash2, StarOff, Dices, ListOrdered, CalendarOff } from \"lucide-react\";\nimport type { Reminder } from \"@/lib/reminders\";\nimport { trackEvent } from \"@/lib/analytics\";\n\nconst difficultyColor: Record<string, string> = {\n  Easy: \"text-green-700 dark:text-green-400\",\n  Medium: \"text-yellow-700 dark:text-yellow-400\",\n  Hard: \"text-red-700 dark:text-red-400\",\n};\n\ninterface FilterToolbarProps {\n  table: Table<Question>;\n  globalFilter: string;\n  setGlobalFilter: (value: string) => void;\n  patterns: string[];\n  companies: [string, string][];\n  showStarredOnly: boolean;\n  setShowStarredOnly: (value: boolean) => void;\n  hideCompleted: boolean;\n  setHideCompleted: (value: boolean) => void;\n  hidePatterns: boolean;\n  setHidePatterns: (value: boolean) => void;\n  showDueOnly: boolean;\n  setShowDueOnly: (value: boolean) => void;\n  pickRandom: () => void;\n  shuffleOrder: number[] | null;\n  toggleShuffle: () => void;\n  exportProgress: () => void;\n  fileInputRef: React.RefObject<HTMLInputElement | null>;\n  importProgress: (file: File) => void;\n  starred: Set<number>;\n  notes: Record<number, string>;\n  completed: Set<number>;\n  reminders: Record<number, Reminder>;\n  setClearConfirm: (value: \"notes\" | \"questions\" | \"starred\" | \"reminders\" | null) => void;\n  searchRef: React.RefObject<HTMLInputElement | null>;\n  columnFilters: { id: string; value: unknown }[];\n}\n\nexport default function FilterToolbar({\n  table,\n  globalFilter,\n  setGlobalFilter,\n  patterns,\n  companies,\n  showStarredOnly,\n  setShowStarredOnly,\n  hideCompleted,\n  setHideCompleted,\n  hidePatterns,\n  setHidePatterns,\n  showDueOnly,\n  setShowDueOnly,\n  pickRandom,\n  shuffleOrder,\n  toggleShuffle,\n  exportProgress,\n  fileInputRef,\n  importProgress,\n  starred,\n  notes,\n  completed,\n  reminders,\n  setClearConfirm,\n  searchRef,\n  columnFilters,\n}: FilterToolbarProps) {\n  const difficultyFilter = useMemo(\n    () => (table.getColumn(\"difficulty\")?.getFilterValue() as string[]) ?? [],\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [table, columnFilters]\n  );\n  const [difficultyDropdownOpen, setDifficultyDropdownOpen] = useState(false);\n  const difficultyDropdownRef = useRef<HTMLDivElement>(null);\n\n  const patternFilter = useMemo(\n    () => (table.getColumn(\"pattern\")?.getFilterValue() as string[]) ?? [],\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [table, columnFilters]\n  );\n  const [patternDropdownOpen, setPatternDropdownOpen] = useState(false);\n  const [patternSearch, setPatternSearch] = useState(\"\");\n  const patternDropdownRef = useRef<HTMLDivElement>(null);\n\n  const filteredPatterns = useMemo(() => {\n    const list = patternSearch\n      ? patterns.filter((p) =>\n          p.toLowerCase().includes(patternSearch.toLowerCase())\n        )\n      : patterns;\n    return [...list].sort((a, b) => {\n      const aChecked = patternFilter.includes(a);\n      const bChecked = patternFilter.includes(b);\n      if (aChecked !== bChecked) return aChecked ? -1 : 1;\n      return 0;\n    });\n  }, [patterns, patternSearch, patternFilter]);\n\n  const companyFilter = useMemo(\n    () => (table.getColumn(\"companies\")?.getFilterValue() as string[]) ?? [],\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [table, columnFilters]\n  );\n  const [companyDropdownOpen, setCompanyDropdownOpen] = useState(false);\n  const [companySearch, setCompanySearch] = useState(\"\");\n  const companyDropdownRef = useRef<HTMLDivElement>(null);\n\n  const filteredCompanies = useMemo(() => {\n    const list = companySearch\n      ? companies.filter(([, name]) =>\n          name.toLowerCase().includes(companySearch.toLowerCase())\n        )\n      : companies;\n    return [...list].sort((a, b) => {\n      const aChecked = companyFilter.includes(a[0]);\n      const bChecked = companyFilter.includes(b[0]);\n      if (aChecked !== bChecked) return aChecked ? -1 : 1;\n      return 0;\n    });\n  }, [companies, companySearch, companyFilter]);\n\n  useEffect(() => {\n    const handleClick = (e: MouseEvent | TouchEvent) => {\n      if (difficultyDropdownRef.current && !difficultyDropdownRef.current.contains(e.target as Node)) {\n        setDifficultyDropdownOpen(false);\n      }\n      if (patternDropdownRef.current && !patternDropdownRef.current.contains(e.target as Node)) {\n        setPatternDropdownOpen(false);\n        setPatternSearch(\"\");\n      }\n      if (companyDropdownRef.current && !companyDropdownRef.current.contains(e.target as Node)) {\n        setCompanyDropdownOpen(false);\n        setCompanySearch(\"\");\n      }\n    };\n    document.addEventListener(\"mousedown\", handleClick);\n    document.addEventListener(\"touchstart\", handleClick);\n    return () => {\n      document.removeEventListener(\"mousedown\", handleClick);\n      document.removeEventListener(\"touchstart\", handleClick);\n    };\n  }, []);\n\n  return (\n    <div className=\"flex flex-wrap items-center justify-center gap-2 text-sm\">\n      <div className=\"relative\">\n        <input\n          ref={searchRef}\n          type=\"text\"\n          placeholder=\"Search\"\n          value={globalFilter}\n          onChange={(e) => setGlobalFilter(e.target.value)}\n          aria-label=\"Search questions\"\n          className=\"w-36 rounded border border-zinc-300 bg-white px-2 py-1.5 pr-7 shadow-sm focus:border-blue-500 focus:outline-none dark:border-zinc-700 dark:bg-zinc-900\"\n        />\n        <kbd className=\"pointer-events-none absolute right-2 top-1/2 -translate-y-1/2 rounded bg-zinc-200 px-1 py-0.5 text-[10px] font-mono leading-none text-zinc-500 dark:bg-zinc-700 dark:text-zinc-400\">/</kbd>\n      </div>\n      <div ref={difficultyDropdownRef} className=\"relative\">\n        <button\n          onClick={() => setDifficultyDropdownOpen((o) => !o)}\n          aria-expanded={difficultyDropdownOpen}\n          aria-haspopup=\"listbox\"\n          className=\"flex items-center gap-1 whitespace-nowrap rounded border border-zinc-300 bg-white px-2 py-1.5 shadow-sm dark:border-zinc-700 dark:bg-zinc-900\"\n        >\n          <span>\n            {difficultyFilter.length === 0\n              ? \"All Difficulties\"\n              : `Difficulty (${difficultyFilter.length})`}\n          </span>\n          <svg className=\"h-3 w-3 shrink-0 opacity-50\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n            <path d=\"M3 5l3 3 3-3\" />\n          </svg>\n        </button>\n        {difficultyDropdownOpen && (\n          <div className=\"absolute left-0 top-full z-20 mt-1 w-48 rounded-lg border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900\">\n            {difficultyFilter.length > 0 && (\n              <button\n                onClick={() => {\n                  table.getColumn(\"difficulty\")?.setFilterValue(undefined);\n                }}\n                className=\"w-full border-b border-zinc-200 px-3 py-1.5 text-left text-xs text-blue-600 hover:bg-zinc-50 dark:border-zinc-700 dark:text-blue-400 dark:hover:bg-zinc-800\"\n              >\n                Clear all ({difficultyFilter.length})\n              </button>\n            )}\n            <div className=\"py-1\">\n              {([\"Easy\", \"Medium\", \"Hard\"] as const).map((d) => (\n                <label\n                  key={d}\n                  className=\"flex cursor-pointer items-center gap-2 px-3 py-1.5 hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n                >\n                  <input\n                    type=\"checkbox\"\n                    checked={difficultyFilter.includes(d)}\n                    onChange={() => {\n                      const next = difficultyFilter.includes(d)\n                        ? difficultyFilter.filter((x) => x !== d)\n                        : [...difficultyFilter, d];\n                      table\n                        .getColumn(\"difficulty\")\n                        ?.setFilterValue(next.length ? next : undefined);\n                    }}\n                    className=\"h-3.5 w-3.5 accent-blue-600\"\n                  />\n                  <span className={`font-medium ${difficultyColor[d]}`}>{d}</span>\n                </label>\n              ))}\n            </div>\n          </div>\n        )}\n      </div>\n      <div ref={patternDropdownRef} className=\"relative\">\n        <button\n          onClick={() => setPatternDropdownOpen((o) => !o)}\n          aria-expanded={patternDropdownOpen}\n          aria-haspopup=\"listbox\"\n          className=\"flex items-center gap-1 whitespace-nowrap rounded border border-zinc-300 bg-white px-2 py-1.5 shadow-sm dark:border-zinc-700 dark:bg-zinc-900\"\n        >\n          <span>\n            {patternFilter.length === 0\n              ? \"All Patterns\"\n              : `Patterns (${patternFilter.length})`}\n          </span>\n          <svg className=\"h-3 w-3 shrink-0 opacity-50\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n            <path d=\"M3 5l3 3 3-3\" />\n          </svg>\n        </button>\n        {patternDropdownOpen && (\n          <div className=\"absolute left-0 top-full z-20 mt-1 w-64 rounded-lg border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900\">\n            <div className=\"border-b border-zinc-200 p-2 dark:border-zinc-700\">\n              <input\n                type=\"text\"\n                placeholder=\"Search patterns...\"\n                value={patternSearch}\n                onChange={(e) => setPatternSearch(e.target.value)}\n                className=\"w-full rounded border border-zinc-300 bg-transparent px-2 py-1 text-sm focus:border-blue-500 focus:outline-none dark:border-zinc-700\"\n                autoFocus\n              />\n            </div>\n            {patternFilter.length > 0 && (\n              <button\n                onClick={() => {\n                  table.getColumn(\"pattern\")?.setFilterValue(undefined);\n                  setPatternSearch(\"\");\n                }}\n                className=\"w-full border-b border-zinc-200 px-3 py-1.5 text-left text-xs text-blue-600 hover:bg-zinc-50 dark:border-zinc-700 dark:text-blue-400 dark:hover:bg-zinc-800\"\n              >\n                Clear all ({patternFilter.length})\n              </button>\n            )}\n            <div className=\"max-h-52 overflow-y-auto py-1\">\n              {filteredPatterns.length === 0 ? (\n                <p className=\"px-3 py-2 text-xs text-zinc-400\">No patterns found</p>\n              ) : (\n                filteredPatterns.map((p) => (\n                  <label\n                    key={p}\n                    className=\"flex cursor-pointer items-center gap-2 px-3 py-1.5 hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n                  >\n                    <input\n                      type=\"checkbox\"\n                      checked={patternFilter.includes(p)}\n                      onChange={() => {\n                        const next = patternFilter.includes(p)\n                          ? patternFilter.filter((x) => x !== p)\n                          : [...patternFilter, p];\n                        table\n                          .getColumn(\"pattern\")\n                          ?.setFilterValue(next.length ? next : undefined);\n                      }}\n                      className=\"h-3.5 w-3.5 accent-blue-600\"\n                    />\n                    {p}\n                  </label>\n                ))\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n      <div ref={companyDropdownRef} className=\"relative\">\n        <button\n          onClick={() => setCompanyDropdownOpen((o) => !o)}\n          aria-expanded={companyDropdownOpen}\n          aria-haspopup=\"listbox\"\n          className=\"flex items-center gap-1 whitespace-nowrap rounded border border-zinc-300 bg-white px-2 py-1.5 shadow-sm dark:border-zinc-700 dark:bg-zinc-900\"\n        >\n          <span>\n            {companyFilter.length === 0\n              ? \"All Companies\"\n              : `Companies (${companyFilter.length})`}\n          </span>\n          <svg className=\"h-3 w-3 shrink-0 opacity-50\" viewBox=\"0 0 12 12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\">\n            <path d=\"M3 5l3 3 3-3\" />\n          </svg>\n        </button>\n        {companyDropdownOpen && (\n          <div className=\"absolute left-0 top-full z-20 mt-1 w-64 rounded-lg border border-zinc-200 bg-white shadow-lg dark:border-zinc-700 dark:bg-zinc-900\">\n            <div className=\"border-b border-zinc-200 p-2 dark:border-zinc-700\">\n              <input\n                type=\"text\"\n                placeholder=\"Search companies...\"\n                value={companySearch}\n                onChange={(e) => setCompanySearch(e.target.value)}\n                className=\"w-full rounded border border-zinc-300 bg-transparent px-2 py-1 text-sm focus:border-blue-500 focus:outline-none dark:border-zinc-700\"\n                autoFocus\n              />\n            </div>\n            {companyFilter.length > 0 && (\n              <button\n                onClick={() => {\n                  table.getColumn(\"companies\")?.setFilterValue(undefined);\n                  setCompanySearch(\"\");\n                }}\n                className=\"w-full border-b border-zinc-200 px-3 py-1.5 text-left text-xs text-blue-600 hover:bg-zinc-50 dark:border-zinc-700 dark:text-blue-400 dark:hover:bg-zinc-800\"\n              >\n                Clear all ({companyFilter.length})\n              </button>\n            )}\n            <div className=\"max-h-52 overflow-y-auto py-1\">\n              {filteredCompanies.length === 0 ? (\n                <p className=\"px-3 py-2 text-xs text-zinc-400\">No companies found</p>\n              ) : (\n                filteredCompanies.map(([slug, name]) => (\n                  <label\n                    key={slug}\n                    className=\"flex cursor-pointer items-center gap-2 px-3 py-1.5 hover:bg-zinc-50 dark:hover:bg-zinc-800\"\n                  >\n                    <input\n                      type=\"checkbox\"\n                      checked={companyFilter.includes(slug)}\n                      onChange={() => {\n                        const next = companyFilter.includes(slug)\n                          ? companyFilter.filter((s) => s !== slug)\n                          : [...companyFilter, slug];\n                        table\n                          .getColumn(\"companies\")\n                          ?.setFilterValue(next.length ? next : undefined);\n                      }}\n                      className=\"h-3.5 w-3.5 accent-blue-600\"\n                    />\n                    {name}\n                  </label>\n                ))\n              )}\n            </div>\n          </div>\n        )}\n      </div>\n      <div className=\"flex flex-wrap items-center justify-center gap-1 px-1 py-0.5 sm:flex-nowrap sm:rounded-lg sm:border sm:border-zinc-200 sm:dark:border-zinc-800\">\n        {([\n          { label: \"Starred only\", checked: showStarredOnly, onChange: (v: boolean) => { setShowStarredOnly(v); trackEvent(\"show_starred_only\", { enabled: v }); } },\n          { label: \"Due for review\", checked: showDueOnly, onChange: (v: boolean) => { setShowDueOnly(v); trackEvent(\"show_due_only\", { enabled: v }); } },\n          { label: \"Hide completed\", checked: hideCompleted, onChange: (v: boolean) => { setHideCompleted(v); trackEvent(\"hide_completed\", { enabled: v }); } },\n          { label: \"Hide patterns\", checked: hidePatterns, onChange: (v: boolean) => { setHidePatterns(v); trackEvent(\"hide_patterns\", { enabled: v }); } },\n        ] as const).map((opt) => (\n          <label key={opt.label} className={`flex cursor-pointer items-center gap-1.5 whitespace-nowrap rounded px-2 py-1.5 transition-colors select-none ${opt.checked ? \"bg-blue-50 text-blue-700 dark:bg-blue-900/30 dark:text-blue-400\" : \"text-zinc-600 hover:bg-zinc-50 dark:text-zinc-400 dark:hover:bg-zinc-800\"}`}>\n            <input\n              type=\"checkbox\"\n              checked={opt.checked}\n              onChange={(e) => opt.onChange(e.target.checked)}\n              className=\"h-3.5 w-3.5 accent-blue-600\"\n            />\n            {opt.label}\n          </label>\n        ))}\n      </div>\n      {/* Random & Shuffle */}\n      <div className=\"flex items-center gap-1 rounded-lg border border-zinc-200 px-1 py-0.5 dark:border-zinc-800\">\n        <button\n          onClick={pickRandom}\n          className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n        >\n          <Shuffle className=\"h-3.5 w-3.5\" />\n          Random\n          <kbd className=\"rounded bg-zinc-200 px-1 py-0.5 text-[10px] font-mono leading-none text-zinc-500 dark:bg-zinc-700 dark:text-zinc-400\">r</kbd>\n        </button>\n        <button\n          onClick={toggleShuffle}\n          className={`inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors ${\n            shuffleOrder\n              ? \"bg-violet-50 text-violet-600 hover:bg-violet-100 dark:bg-violet-900/30 dark:text-violet-400 dark:hover:bg-violet-900/50\"\n              : \"hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n          }`}\n        >\n          {shuffleOrder ? <ListOrdered className=\"h-3.5 w-3.5\" /> : <Dices className=\"h-3.5 w-3.5\" />}\n          {shuffleOrder ? \"Unshuffle\" : \"Shuffle\"}\n        </button>\n      </div>\n      {/* Import & Export */}\n      <div className=\"flex items-center gap-1 rounded-lg border border-zinc-200 px-1 py-0.5 dark:border-zinc-800\">\n        <button\n          onClick={exportProgress}\n          className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n        >\n          <Download className=\"h-3.5 w-3.5\" />\n          Export\n        </button>\n        <button\n          onClick={() => fileInputRef.current?.click()}\n          className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n        >\n          <Upload className=\"h-3.5 w-3.5\" />\n          Import\n        </button>\n        <input\n          ref={fileInputRef}\n          type=\"file\"\n          accept=\".json\"\n          className=\"hidden\"\n          onChange={(e) => {\n            const file = e.target.files?.[0];\n            if (file) importProgress(file);\n            e.target.value = \"\";\n          }}\n        />\n      </div>\n\n      {/* Clear */}\n      {(starred.size > 0 || Object.keys(notes).length > 0 || completed.size > 0 || Object.keys(reminders).length > 0) && (\n        <div className=\"flex items-center gap-1 rounded-lg border border-red-200 px-1 py-0.5 dark:border-red-900/40\">\n          {starred.size > 0 && (\n            <button\n              onClick={() => setClearConfirm(\"starred\")}\n              className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/30 dark:hover:text-red-400\"\n            >\n              <StarOff className=\"h-3.5 w-3.5\" />\n              Stars\n            </button>\n          )}\n          {Object.keys(notes).length > 0 && (\n            <button\n              onClick={() => setClearConfirm(\"notes\")}\n              className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/30 dark:hover:text-red-400\"\n            >\n              <Trash2 className=\"h-3.5 w-3.5\" />\n              Notes\n            </button>\n          )}\n          {Object.keys(reminders).length > 0 && (\n            <button\n              onClick={() => setClearConfirm(\"reminders\")}\n              className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/30 dark:hover:text-red-400\"\n            >\n              <CalendarOff className=\"h-3.5 w-3.5\" />\n              Reminders\n            </button>\n          )}\n          {completed.size > 0 && (\n            <button\n              onClick={() => setClearConfirm(\"questions\")}\n              className=\"inline-flex items-center gap-1 whitespace-nowrap rounded px-2 py-1.5 transition-colors hover:bg-red-50 hover:text-red-600 dark:hover:bg-red-900/30 dark:hover:text-red-400\"\n            >\n              <RotateCcw className=\"h-3.5 w-3.5\" />\n              Progress\n            </button>\n          )}\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/questions/GroupHeaderRow.tsx",
    "content": "import { forwardRef } from \"react\";\nimport { ChevronRight, ChevronDown, RotateCcw } from \"lucide-react\";\n\nconst difficultyColor: Record<string, string> = {\n  Easy: \"text-green-700 dark:text-green-400\",\n  Medium: \"text-yellow-700 dark:text-yellow-400\",\n  Hard: \"text-red-700 dark:text-red-400\",\n};\n\nconst groupRowStyles: Record<string, string> = {\n  Easy: \"border-l-green-500 bg-green-50 hover:bg-green-100 dark:bg-green-900/30 dark:hover:bg-green-900/50\",\n  Medium: \"border-l-yellow-500 bg-yellow-50 hover:bg-yellow-100 dark:bg-yellow-900/30 dark:hover:bg-yellow-900/50\",\n  Hard: \"border-l-red-500 bg-red-50 hover:bg-red-100 dark:bg-red-900/30 dark:hover:bg-red-900/50\",\n};\n\ninterface GroupHeaderRowProps {\n  groupKey: string;\n  groupDone: number;\n  total: number;\n  isCollapsed: boolean;\n  toggleGroup: (group: string) => void;\n  setResetConfirmGroup: (group: string) => void;\n  colSpan: number;\n  dataIndex: number;\n}\n\nconst GroupHeaderRow = forwardRef<HTMLTableRowElement, GroupHeaderRowProps>(\n  function GroupHeaderRow({ groupKey, groupDone, total, isCollapsed, toggleGroup, setResetConfirmGroup, colSpan, dataIndex }, ref) {\n    return (\n      <tr\n        key={`header-${groupKey}`}\n        data-index={dataIndex}\n        ref={ref}\n        className={`cursor-pointer select-none border-l-4 ${groupRowStyles[groupKey] ?? \"\"}`}\n        onClick={() => toggleGroup(groupKey)}\n        onKeyDown={(e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); toggleGroup(groupKey); } }}\n        tabIndex={0}\n        role=\"button\"\n        aria-expanded={!isCollapsed}\n        aria-label={`${groupKey} group, ${groupDone} of ${total} completed`}\n      >\n        <td\n          colSpan={colSpan}\n          className=\"px-2 py-2.5 sm:px-4 sm:py-3\"\n        >\n          <span className=\"flex items-center gap-2\">\n            {isCollapsed ? (\n              <ChevronRight className=\"h-4 w-4\" />\n            ) : (\n              <ChevronDown className=\"h-4 w-4\" />\n            )}\n            <span className={`text-base font-bold ${difficultyColor[groupKey] ?? \"\"}`}>\n              {groupKey}\n            </span>\n            <span className=\"text-xs font-medium text-zinc-500 dark:text-zinc-400\">\n              {groupDone}/{total} completed\n            </span>\n            {groupDone > 0 && (\n              <button\n                onClick={(e) => {\n                  e.stopPropagation();\n                  setResetConfirmGroup(groupKey);\n                }}\n                className=\"ml-auto flex items-center gap-1 rounded px-1.5 py-1 text-xs text-zinc-400 transition-colors hover:bg-zinc-200 hover:text-zinc-600 dark:hover:bg-zinc-600 dark:hover:text-zinc-300\"\n              >\n                <RotateCcw className=\"h-3 w-3\" />\n                <span className=\"hidden sm:inline\">Reset</span>\n              </button>\n            )}\n          </span>\n        </td>\n      </tr>\n    );\n  }\n);\n\nexport default GroupHeaderRow;\n"
  },
  {
    "path": "src/components/questions/NoteModal.tsx",
    "content": "import { MAX_NOTE_LENGTH } from \"@/lib/storage\";\n\nexport interface EditingNote {\n  id: number;\n  title: string;\n  draft: string;\n  confirmDiscard: boolean;\n}\n\nexport default function NoteModal({\n  editingNote,\n  setEditingNote,\n  updateNote,\n  notes,\n}: {\n  editingNote: EditingNote;\n  setEditingNote: (note: EditingNote | null) => void;\n  updateNote: (id: number, value: string) => void;\n  notes: Record<number, string>;\n}) {\n  const saved = notes[editingNote.id] ?? \"\";\n  const hasChanges = editingNote.draft !== saved;\n  const tryDismiss = () => {\n    if (hasChanges) {\n      setEditingNote({ ...editingNote, confirmDiscard: true });\n    } else {\n      setEditingNote(null);\n    }\n  };\n  return (\n    <div\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n      onClick={tryDismiss}\n      role=\"dialog\"\n      aria-modal=\"true\"\n      aria-label={`Edit note for ${editingNote.title}`}\n    >\n      <div\n        className=\"mx-4 w-full max-w-lg rounded-xl border border-zinc-200 bg-white p-6 shadow-xl dark:border-zinc-700 dark:bg-zinc-900\"\n        onClick={(e) => e.stopPropagation()}\n      >\n        {editingNote.confirmDiscard ? (\n          <>\n            <h2 className=\"mb-2 text-lg font-semibold\">Unsaved changes</h2>\n            <p className=\"mb-3 text-sm text-zinc-500\">\n              Your note for <span className=\"font-medium text-foreground\">{editingNote.title}</span> has\n              been modified but not saved. Would you like to go back and save\n              your changes, or discard them?\n            </p>\n            <div className=\"mb-4 rounded-lg border border-zinc-200 bg-zinc-50 p-3 text-sm dark:border-zinc-700 dark:bg-zinc-800\">\n              <p className=\"mb-1 text-xs font-medium text-zinc-400\">Your unsaved note:</p>\n              <p className=\"whitespace-pre-wrap break-words text-zinc-600 dark:text-zinc-300\">\n                {editingNote.draft || <span className=\"italic text-zinc-400\">(empty)</span>}\n              </p>\n            </div>\n            <div className=\"flex justify-end gap-2\">\n              <button\n                onClick={() =>\n                  setEditingNote({ ...editingNote, confirmDiscard: false })\n                }\n                className=\"rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n              >\n                Keep editing\n              </button>\n              <button\n                onClick={() => setEditingNote(null)}\n                className=\"rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700\"\n              >\n                Discard\n              </button>\n            </div>\n          </>\n        ) : (\n          <>\n            <h2 className=\"mb-1 text-lg font-semibold\">{editingNote.title}</h2>\n            <p className=\"mb-4 text-sm text-zinc-500\">Add your notes below</p>\n            <textarea\n              autoFocus\n              rows={4}\n              value={editingNote.draft}\n              onChange={(e) =>\n                setEditingNote({ ...editingNote, draft: e.target.value })\n              }\n              onKeyDown={(e) => {\n                if (e.key === \"Enter\" && (e.metaKey || e.ctrlKey)) {\n                  e.preventDefault();\n                  updateNote(editingNote.id, editingNote.draft);\n                  setEditingNote(null);\n                }\n              }}\n              maxLength={MAX_NOTE_LENGTH}\n              placeholder=\"Write your notes here...\"\n              className=\"w-full resize-y rounded-lg border border-zinc-300 bg-transparent px-3 py-2 text-sm break-words focus:border-blue-500 focus:outline-none dark:border-zinc-700\"\n            />\n            <div className=\"mt-2 flex items-center justify-between\">\n              <span className={`text-xs ${editingNote.draft.length >= MAX_NOTE_LENGTH ? \"text-red-500\" : \"text-zinc-400\"}`}>\n                {editingNote.draft.length.toLocaleString()} / {MAX_NOTE_LENGTH.toLocaleString()} characters\n              </span>\n              {hasChanges ? (\n                <span className=\"text-xs text-amber-600 dark:text-amber-400\">\n                  ⚠ Unsaved changes · {navigator.platform?.includes(\"Mac\") ? \"⌘\" : \"Ctrl\"}+Enter to save\n                </span>\n              ) : (\n                <span className=\"text-xs text-zinc-400\">\n                  {navigator.platform?.includes(\"Mac\") ? \"⌘\" : \"Ctrl\"}+Enter to save\n                </span>\n              )}\n            </div>\n            <div className=\"mt-4 flex justify-end gap-2\">\n              <button\n                onClick={tryDismiss}\n                className=\"rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n              >\n                Cancel\n              </button>\n              <button\n                onClick={() => {\n                  updateNote(editingNote.id, editingNote.draft);\n                  setEditingNote(null);\n                }}\n                className=\"rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700\"\n              >\n                Done\n              </button>\n            </div>\n          </>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/questions/ProgressBar.tsx",
    "content": "export interface ProgressStats {\n  totals: { Easy: number; Medium: number; Hard: number };\n  done: { Easy: number; Medium: number; Hard: number };\n  total: number;\n  totalDone: number;\n}\n\nexport default function ProgressBar({ stats, pct }: { stats: ProgressStats; pct: number }) {\n  return (\n    <div className=\"group relative rounded-lg border border-zinc-200 bg-zinc-50 p-3 sm:p-4 dark:border-zinc-800 dark:bg-zinc-900\">\n      <div className=\"mb-2 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm font-medium\">\n        <span>{stats.totalDone}/{stats.total} completed ({pct}%)</span>\n        <div className=\"flex gap-4 sm:opacity-0 sm:transition-opacity sm:duration-500 sm:ease-in-out sm:group-hover:opacity-100\">\n          {stats.totals.Easy > 0 && (\n            <span className=\"text-green-700 dark:text-green-400\">\n              Easy: {stats.done.Easy}/{stats.totals.Easy}\n            </span>\n          )}\n          {stats.totals.Medium > 0 && (\n            <span className=\"text-yellow-700 dark:text-yellow-400\">\n              Medium: {stats.done.Medium}/{stats.totals.Medium}\n            </span>\n          )}\n          {stats.totals.Hard > 0 && (\n            <span className=\"text-red-700 dark:text-red-400\">\n              Hard: {stats.done.Hard}/{stats.totals.Hard}\n            </span>\n          )}\n        </div>\n      </div>\n      <div className=\"relative h-2 overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-700\" role=\"progressbar\" aria-valuenow={pct} aria-valuemin={0} aria-valuemax={100} aria-label=\"Completion progress\">\n        {/* Default: solid blue bar */}\n        <div\n          className=\"absolute inset-0 h-full bg-blue-500 transition-opacity duration-500 ease-in-out sm:group-hover:opacity-0 max-sm:hidden\"\n          style={{ width: `${pct}%` }}\n        />\n        {/* Hover: blended difficulty gradient */}\n        <div\n          className=\"absolute inset-0 h-full transition-opacity duration-500 ease-in-out max-sm:opacity-100 sm:opacity-0 sm:group-hover:opacity-100\"\n          style={{\n            width: `${pct}%`,\n            background: (() => {\n              if (!stats.totalDone) return \"var(--color-green-500)\";\n              const easyPct = (stats.done.Easy / stats.totalDone) * 100;\n              const medPct = ((stats.done.Easy + stats.done.Medium) / stats.totalDone) * 100;\n              return `linear-gradient(90deg, var(--color-green-500) ${Math.max(easyPct - 3, 0)}%, var(--color-yellow-500) ${Math.min(easyPct + 3, medPct - 3)}%, var(--color-yellow-500) ${Math.max(medPct - 3, easyPct + 3)}%, var(--color-red-500) ${medPct + 3}%)`;\n            })(),\n          }}\n        />\n      </div>\n\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/questions/QuestionRow.tsx",
    "content": "import { forwardRef } from \"react\";\nimport { flexRender, type Row } from \"@tanstack/react-table\";\nimport { Question } from \"@/types/question\";\n\nconst completedRowStyles: Record<string, string> = {\n  Easy: \"bg-green-100/60 hover:bg-green-100 dark:bg-green-900/30 dark:hover:bg-green-900/40\",\n  Medium: \"bg-yellow-100/60 hover:bg-yellow-100 dark:bg-yellow-900/30 dark:hover:bg-yellow-900/40\",\n  Hard: \"bg-red-100/60 hover:bg-red-100 dark:bg-red-900/30 dark:hover:bg-red-900/40\",\n};\n\ninterface QuestionRowProps {\n  row: Row<Question>;\n  completed: Set<number>;\n  toggleCompleted: (id: number) => void;\n  toggleStarred: (id: number) => void;\n  dataIndex: number;\n}\n\nconst QuestionRow = forwardRef<HTMLTableRowElement, QuestionRowProps>(\n  function QuestionRow({ row, completed, toggleCompleted, toggleStarred, dataIndex }, ref) {\n    const isDone = completed.has(row.original.id);\n    return (\n      <tr\n        key={row.id}\n        data-index={dataIndex}\n        ref={ref}\n        className={\n          isDone\n            ? `text-zinc-400 dark:text-zinc-500 ${completedRowStyles[row.original.difficulty]}`\n            : \"hover:bg-zinc-50 dark:hover:bg-zinc-900/50\"\n        }\n      >\n        {row.getVisibleCells().map((cell) => {\n          const meta = cell.column.columnDef.meta as { clickable?: boolean; toggleFn?: string; noStrikethrough?: boolean } | undefined;\n          const isClickable = meta?.clickable;\n          const onClick = isClickable\n            ? () => (meta?.toggleFn === \"starred\" ? toggleStarred : toggleCompleted)(row.original.id)\n            : undefined;\n          const strikethrough = isDone && !meta?.noStrikethrough ? \"line-through decoration-zinc-300 dark:decoration-zinc-600\" : \"\";\n          return (\n            <td\n              key={cell.id}\n              className={`px-2 py-2 sm:px-4 sm:py-3 ${strikethrough} ${isClickable ? \"cursor-pointer select-none\" : \"\"}`}\n              onClick={onClick}\n            >\n              {flexRender(cell.column.columnDef.cell, cell.getContext())}\n            </td>\n          );\n        })}\n      </tr>\n    );\n  }\n);\n\nexport default QuestionRow;\n"
  },
  {
    "path": "src/components/questions/QuestionsTable.test.tsx",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { render, screen, waitFor, cleanup, within } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport type { Question } from \"@/types/question\";\n\nconst { mockTrackEvent } = vi.hoisted(() => ({\n  mockTrackEvent: vi.fn(),\n}));\n\nvi.mock(\"@/lib/analytics\", () => ({\n  trackEvent: mockTrackEvent,\n}));\n\nvi.mock(\"next/navigation\", () => ({\n  useSearchParams: () => new URLSearchParams(),\n  useRouter: () => ({ replace: vi.fn() }),\n}));\n\nvi.mock(\"@/components/layout/AuthContext\", () => ({\n  useAuth: () => ({ user: null, loading: false, signIn: vi.fn(), signOut: vi.fn(), syncNow: vi.fn(), syncVersion: 0 }),\n}));\n\nimport QuestionsTable from \"@/components/questions/QuestionsTable\";\n\nconst testData: Question[] = [\n  {\n    id: 0,\n    title: \"Two Sum\",\n    slug: \"two-sum\",\n    pattern: [\"Arrays\"],\n    difficulty: \"Easy\",\n    premium: false,\n    companies: [{ name: \"Google\", slug: \"google\", frequency: 5 }],\n  },\n  {\n    id: 1,\n    title: \"Add Two Numbers\",\n    slug: \"add-two-numbers\",\n    pattern: [\"Linked List\"],\n    difficulty: \"Medium\",\n    premium: false,\n    companies: [{ name: \"Amazon\", slug: \"amazon\", frequency: 3 }],\n  },\n  {\n    id: 2,\n    title: \"Median of Two Sorted Arrays\",\n    slug: \"median-of-two-sorted-arrays\",\n    pattern: [\"Binary Search\"],\n    difficulty: \"Hard\",\n    premium: false,\n    companies: [],\n  },\n];\n\ndescribe(\"QuestionsTable analytics\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    localStorage.clear();\n    window.open = vi.fn();\n    URL.createObjectURL = vi.fn(() => \"blob:test\");\n    URL.revokeObjectURL = vi.fn();\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"tracks question_toggle when checking a question\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const checkboxes = screen.getAllByRole(\"checkbox\", { name: /^Mark .+ as (complete|incomplete)$/ });\n    const cell = checkboxes[0].closest(\"td\")!;\n    await user.click(cell);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"question_toggle\", {\n      question_id: 0,\n      completed: true,\n    });\n  });\n\n  it(\"tracks question_toggle with completed=false when unchecking\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const checkboxes = screen.getAllByRole(\"checkbox\", { name: /^Mark .+ as (complete|incomplete)$/ });\n    const cell = checkboxes[0].closest(\"td\")!;\n    await user.click(cell);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"question_toggle\", {\n      question_id: 0,\n      completed: false,\n    });\n  });\n\n  it(\"tracks note_save when saving a note\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Add note for Two Sum/ }));\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"my test note\");\n    await user.click(screen.getByText(\"Done\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"note_save\", {\n      question_id: 0,\n      has_content: true,\n    });\n  });\n\n  it(\"tracks search with debounce\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const searchInput = screen.getByPlaceholderText(\"Search\");\n    await user.type(searchInput, \"two\");\n    await waitFor(() => {\n      expect(mockTrackEvent).toHaveBeenCalledWith(\"search\", { query: \"two\" });\n    }, { timeout: 2000 });\n  });\n\n  it(\"tracks filter_difficulty when selecting a difficulty filter\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"All Difficulties\"));\n    await user.click(screen.getByLabelText(\"Easy\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"filter_difficulty\", { values: \"Easy\" });\n  });\n\n  it(\"tracks filter_pattern when selecting a pattern filter\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"All Patterns\"));\n    await user.click(screen.getByLabelText(\"Arrays\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"filter_pattern\", { values: \"Arrays\" });\n  });\n\n  it(\"tracks filter_company when selecting a company filter\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"All Companies\"));\n    await user.click(screen.getByLabelText(\"Amazon\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"filter_company\", { values: \"amazon\" });\n  });\n\n  it(\"tracks random_question when picking a random question\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Random/ }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\n      \"random_question\",\n      expect.objectContaining({ question_id: expect.any(Number), slug: expect.any(String) })\n    );\n  });\n\n  it(\"tracks export_progress when exporting\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Export/ }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"export_progress\", {\n      completed_count: 0,\n      notes_count: 0,\n    });\n  });\n\n  it(\"tracks hide_completed when toggling\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"hide_completed\", { enabled: true });\n  });\n\n  it(\"tracks hide_patterns when toggling\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    await user.click(screen.getByLabelText(\"Hide patterns\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"hide_patterns\", { enabled: true });\n  });\n\n  it(\"tracks sort_column when clicking a sortable header\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Title\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"sort_column\", {\n      column: \"title\",\n      direction: \"asc\",\n    });\n  });\n\n  it(\"tracks clear_all_progress when clearing all questions\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Progress/ }));\n    await user.click(screen.getByRole(\"button\", { name: \"Clear\" }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"clear_all_progress\");\n  });\n\n  it(\"tracks clear_all_notes when clearing all notes\", async () => {\n    localStorage.setItem(\"leetcode-patterns-notes\", JSON.stringify({ 0: \"test note\" }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Notes/ }));\n    await user.click(screen.getByRole(\"button\", { name: \"Clear\" }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"clear_all_notes\");\n  });\n\n  it(\"tracks reset_group when resetting a difficulty group\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const resetBtns = screen.getAllByText(\"Reset\");\n    await user.click(resetBtns[0]);\n    // Modal has heading \"Reset Easy progress\" - find the confirm button inside it\n    const modal = screen.getByText(\"Reset Easy progress\").closest(\"div[class*='rounded-xl']\")! as HTMLElement;\n    const confirmBtn = within(modal).getByRole(\"button\", { name: \"Reset\" });\n    await user.click(confirmBtn);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"reset_group\", { difficulty: \"Easy\" });\n  });\n\n  it(\"truncates long notes in the notes column\", async () => {\n    const longNote = \"a\".repeat(300);\n    localStorage.setItem(\"leetcode-patterns-notes\", JSON.stringify({ 0: longNote }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const noteBtn = screen.getByText(longNote);\n    expect(noteBtn).toHaveClass(\"truncate\", \"max-w-[100px]\");\n  });\n\n  it(\"shows progress stats scoped to filtered questions\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0, 1]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // Unfiltered: 2 of 3 completed\n    expect(screen.getByText(\"2/3 completed (67%)\")).toBeInTheDocument();\n\n    // Filter to Easy only - 1 of 1 completed, only Easy breakdown shown\n    await user.click(screen.getByText(\"All Difficulties\"));\n    await user.click(screen.getByLabelText(\"Easy\"));\n    expect(screen.getByText(\"1/1 completed (100%)\")).toBeInTheDocument();\n    expect(screen.getByText(/Easy: 1\\/1/)).toBeInTheDocument();\n    expect(screen.queryByText(/Medium:/)).not.toBeInTheDocument();\n    expect(screen.queryByText(/Hard:/)).not.toBeInTheDocument();\n  });\n\n  it(\"progress bar is not affected by hide completed toggle\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // Before hiding: 1 of 3 completed\n    expect(screen.getByText(\"1/3 completed (33%)\")).toBeInTheDocument();\n\n    // Toggle hide completed\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n\n    // Row is hidden but progress bar still shows 1/3\n    await waitFor(() => expect(screen.queryByText(\"Two Sum\")).not.toBeInTheDocument());\n    expect(screen.getByText(\"1/3 completed (33%)\")).toBeInTheDocument();\n  });\n\n  it(\"progress bar reflects all difficulties when hide completed is on\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0, 1]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // 2 of 3 completed\n    expect(screen.getByText(\"2/3 completed (67%)\")).toBeInTheDocument();\n\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n\n    // Progress bar still shows 2/3 with all difficulty breakdowns\n    expect(screen.getByText(\"2/3 completed (67%)\")).toBeInTheDocument();\n    expect(screen.getByText(/Easy: 1\\/1/)).toBeInTheDocument();\n    expect(screen.getByText(/Medium: 1\\/1/)).toBeInTheDocument();\n    expect(screen.getByText(/Hard: 0\\/1/)).toBeInTheDocument();\n  });\n\n  it(\"progress bar respects difficulty filter even when hide completed is on\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // Filter to Easy + hide completed\n    await user.click(screen.getByText(\"All Difficulties\"));\n    await user.click(screen.getByLabelText(\"Easy\"));\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n\n    // Two Sum (Easy, completed) is hidden from table but still counted in progress\n    await waitFor(() => expect(screen.queryByText(\"Two Sum\")).not.toBeInTheDocument());\n    expect(screen.getByText(\"1/1 completed (100%)\")).toBeInTheDocument();\n  });\n\n  it(\"progress bar respects pattern filter even when hide completed is on\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // Filter to Arrays pattern + hide completed\n    await user.click(screen.getByText(\"All Patterns\"));\n    await user.click(screen.getByLabelText(\"Arrays\"));\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n\n    // Two Sum is the only Arrays question, completed and hidden, but still in progress\n    await waitFor(() => expect(screen.queryByText(\"Two Sum\")).not.toBeInTheDocument());\n    expect(screen.getByText(\"1/1 completed (100%)\")).toBeInTheDocument();\n  });\n\n  it(\"progress bar excludes completed items that don't match active filters\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0, 1]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // Filter to Hard only + hide completed\n    await user.click(screen.getByText(\"All Difficulties\"));\n    await user.click(screen.getByLabelText(\"Hard\"));\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n\n    // Only the Hard question (id=2, not completed) should count - Easy/Medium are filtered out\n    expect(screen.getByText(\"0/1 completed (0%)\")).toBeInTheDocument();\n  });\n\n  it(\"tracks star_toggle when starring a question\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const starCheckboxes = screen.getAllByRole(\"checkbox\", { name: /^Star / });\n    await user.click(starCheckboxes[0].closest(\"td\")!);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"star_toggle\", {\n      question_id: 0,\n      starred: true,\n    });\n  });\n\n  it(\"tracks star_toggle with starred=false when unstarring\", async () => {\n    localStorage.setItem(\"leetcode-patterns-starred\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const starCheckboxes = screen.getAllByRole(\"checkbox\", { name: /^Star / });\n    await user.click(starCheckboxes[0].closest(\"td\")!);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"star_toggle\", {\n      question_id: 0,\n      starred: false,\n    });\n  });\n\n  it(\"filters to starred only when toggling starred only checkbox\", async () => {\n    localStorage.setItem(\"leetcode-patterns-starred\", JSON.stringify([0]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // All 3 questions visible\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n    expect(screen.getByText(\"Add Two Numbers\")).toBeInTheDocument();\n\n\n    await user.click(screen.getByLabelText(\"Starred only\"));\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n    await waitFor(() => expect(screen.queryByText(\"Add Two Numbers\")).not.toBeInTheDocument());\n    expect(screen.queryByText(\"Median of Two Sorted Arrays\")).not.toBeInTheDocument();\n  });\n\n  it(\"tracks clear_all_starred when clearing all stars\", async () => {\n    localStorage.setItem(\"leetcode-patterns-starred\", JSON.stringify([0, 1]));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Stars/ }));\n    await user.click(screen.getByRole(\"button\", { name: \"Clear\" }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"clear_all_starred\");\n  });\n\n  it(\"persists starred only checkbox to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    await user.click(screen.getByLabelText(\"Starred only\"));\n    expect(localStorage.getItem(\"leetcode-patterns-starred-only\")).toBe(\"true\");\n  });\n\n  it(\"restores starred only checkbox from localStorage\", () => {\n    localStorage.setItem(\"leetcode-patterns-starred-only\", \"true\");\n    localStorage.setItem(\"leetcode-patterns-starred\", JSON.stringify([0]));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    expect(screen.getByLabelText(\"Starred only\")).toBeChecked();\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n    expect(screen.queryByText(\"Add Two Numbers\")).not.toBeInTheDocument();\n  });\n\n  it(\"persists hide completed checkbox to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\nawait user.click(screen.getByLabelText(\"Hide completed\"));\n    expect(localStorage.getItem(\"leetcode-patterns-hide-completed\")).toBe(\"true\");\n  });\n\n  it(\"restores hide completed checkbox from localStorage\", () => {\n    localStorage.setItem(\"leetcode-patterns-hide-completed\", \"true\");\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    expect(screen.getByLabelText(\"Hide completed\")).toBeChecked();\n    expect(screen.queryByText(\"Two Sum\")).not.toBeInTheDocument();\n  });\n\n  it(\"persists hide patterns checkbox to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    await user.click(screen.getByLabelText(\"Hide patterns\"));\n    expect(localStorage.getItem(\"leetcode-patterns-hide-patterns\")).toBe(\"true\");\n  });\n\n  it(\"restores hide patterns checkbox from localStorage\", () => {\n    localStorage.setItem(\"leetcode-patterns-hide-patterns\", \"true\");\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    expect(screen.getByLabelText(\"Hide patterns\")).toBeChecked();\n  });\n\n  it(\"tracks shuffle_questions when clicking shuffle\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Shuffle/ }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"shuffle_questions\");\n  });\n\n  it(\"persists shuffle order to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Shuffle/ }));\n    const stored = localStorage.getItem(\"leetcode-patterns-shuffle-order\");\n    expect(stored).not.toBeNull();\n    const order = JSON.parse(stored!);\n    expect(order).toHaveLength(3);\n    expect(order.sort()).toEqual([0, 1, 2]);\n  });\n\n  it(\"removes difficulty group headers when shuffled\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const getGroupHeaders = () =>\n      screen.getAllByRole(\"button\", { name: /group/ });\n    expect(getGroupHeaders()).toHaveLength(3);\n\n    await user.click(screen.getByRole(\"button\", { name: /Shuffle/ }));\n    expect(screen.queryAllByRole(\"button\", { name: /group/ })).toHaveLength(0);\n  });\n\n  it(\"toggles between shuffle and restore on the same button\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const getGroupHeaders = () =>\n      screen.queryAllByRole(\"button\", { name: /group/ });\n\n    // Initially shows \"Shuffle\"\n    expect(screen.getByRole(\"button\", { name: /Shuffle/ })).toBeInTheDocument();\n\n    // Click to shuffle\n    await user.click(screen.getByRole(\"button\", { name: /Shuffle/ }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"shuffle_questions\");\n    expect(getGroupHeaders()).toHaveLength(0);\n\n    // Button now shows \"Unshuffle\"\n    expect(screen.getByRole(\"button\", { name: /Unshuffle/ })).toBeInTheDocument();\n\n    // Click to restore\n    await user.click(screen.getByRole(\"button\", { name: /Unshuffle/ }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"restore_order\");\n    expect(getGroupHeaders().length).toBeGreaterThan(0);\n\n    // Button shows \"Shuffle\" again\n    expect(screen.getByRole(\"button\", { name: /Shuffle/ })).toBeInTheDocument();\n  });\n\n  it(\"clears shuffle order from localStorage on restore\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Shuffle/ }));\n    expect(localStorage.getItem(\"leetcode-patterns-shuffle-order\")).not.toBeNull();\n\n    await user.click(screen.getByRole(\"button\", { name: /Unshuffle/ }));\n    expect(localStorage.getItem(\"leetcode-patterns-shuffle-order\")).toBeNull();\n  });\n\n  it(\"restores shuffle order from localStorage on mount\", () => {\n    localStorage.setItem(\"leetcode-patterns-shuffle-order\", JSON.stringify([2, 0, 1]));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const groupHeaders = screen.queryAllByRole(\"button\", { name: /group/ });\n    expect(groupHeaders).toHaveLength(0);\n    // Button shows \"Unshuffle\" since shuffle is active\n    expect(screen.getByRole(\"button\", { name: /Unshuffle/ })).toBeInTheDocument();\n    const rows = screen.getAllByRole(\"row\").filter((row) => row.querySelector(\"td\") && row.querySelectorAll(\"td\").length > 1);\n    expect(rows).toHaveLength(3);\n  });\n\n  it(\"tracks import_progress when importing a file\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const importContainer = screen.getByRole(\"button\", { name: /Import/ }).closest(\"div\")!;\n    const fileInput = importContainer.querySelector(\"input[type='file']\")! as HTMLInputElement;\n    const importData = JSON.stringify({ completed: [0, 1], notes: { 0: \"note\" } });\n    const file = new File([importData], \"progress.json\", { type: \"application/json\" });\n    await user.upload(fileInput, file);\n    await waitFor(() => {\n      expect(mockTrackEvent).toHaveBeenCalledWith(\"import_progress\", {\n        completed_count: 2,\n        notes_count: 1,\n      });\n    });\n  });\n\n  it(\"collapses and expands a difficulty group\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n    // Easy group visible with its question\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n    const easyGroup = screen.getByRole(\"button\", { name: /Easy group/ });\n\n    // Collapse\n    await user.click(easyGroup);\n    expect(screen.queryByText(\"Two Sum\")).not.toBeInTheDocument();\n\n    // Expand\n    await user.click(easyGroup);\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n  });\n\n  it(\"focuses search input when pressing /\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const searchInput = screen.getByPlaceholderText(\"Search\");\n    expect(searchInput).not.toHaveFocus();\n    await user.keyboard(\"/\");\n    expect(searchInput).toHaveFocus();\n  });\n\n  it(\"picks a random question when pressing r\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.keyboard(\"r\");\n    expect(mockTrackEvent).toHaveBeenCalledWith(\n      \"random_question\",\n      expect.objectContaining({ question_id: expect.any(Number), slug: expect.any(String) })\n    );\n  });\n\n  it(\"shows discard confirmation when closing note modal with unsaved changes\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Add note for Two Sum/ }));\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"unsaved text\");\n    await user.click(screen.getByText(\"Cancel\"));\n    expect(screen.getByText(\"Unsaved changes\")).toBeInTheDocument();\n\n    // Click \"Keep editing\" to go back\n    await user.click(screen.getByText(\"Keep editing\"));\n    expect(screen.getByPlaceholderText(\"Write your notes here...\")).toBeInTheDocument();\n  });\n\n  it(\"discards note changes when clicking Discard\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Add note for Two Sum/ }));\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"unsaved text\");\n    await user.click(screen.getByText(\"Cancel\"));\n    await user.click(screen.getByText(\"Discard\"));\n    // Modal is closed, note not saved\n    expect(screen.queryByText(\"Unsaved changes\")).not.toBeInTheDocument();\n    expect(screen.getAllByRole(\"button\", { name: /Add note for/ })).toHaveLength(3);\n  });\n\n  it(\"saves note with Cmd+Enter keyboard shortcut\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Add note for Two Sum/ }));\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"keyboard note\");\n    await user.keyboard(\"{Meta>}{Enter}{/Meta}\");\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"note_save\", {\n      question_id: 0,\n      has_content: true,\n    });\n    // Modal closed\n    expect(screen.queryByPlaceholderText(\"Write your notes here...\")).not.toBeInTheDocument();\n  });\n\n  it(\"filters questions when typing in search\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const searchInput = screen.getByPlaceholderText(\"Search\");\n    await user.type(searchInput, \"Two Sum\");\n    await waitFor(() => {\n      expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n      expect(screen.queryByText(\"Median of Two Sorted Arrays\")).not.toBeInTheDocument();\n    });\n  });\n\n  it(\"records solved date when completing a question\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const checkboxes = screen.getAllByRole(\"checkbox\", { name: /^Mark .+ as (complete|incomplete)$/ });\n    await user.click(checkboxes[0].closest(\"td\")!);\n    const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-solved-dates\")!);\n    expect(stored).toHaveProperty(\"0\");\n    expect(new Date(stored[\"0\"]).getTime()).not.toBeNaN();\n  });\n\n  it(\"shows relative solved date and set-review placeholder after completing a question\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const checkboxes = screen.getAllByRole(\"checkbox\", { name: /^Mark .+ as (complete|incomplete)$/ });\n    await user.click(checkboxes[0].closest(\"td\")!);\n    await waitFor(() => {\n      expect(screen.getByText(\"Solved today\")).toBeInTheDocument();\n      expect(screen.getByText(\"+ Set review\")).toBeInTheDocument();\n    });\n  });\n\n  it(\"shows relative solved date for imported past dates\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-06T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-09\", interval: 3 } }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(\"Solved 3d ago\")).toBeInTheDocument();\n    expect(screen.getByText(\"Due today\")).toBeInTheDocument();\n    vi.useRealTimers();\n  });\n\n  it(\"shows overdue pill for past review dates\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-12T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-06T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-09\", interval: 3 } }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(\"Overdue 3d\")).toBeInTheDocument();\n    vi.useRealTimers();\n  });\n\n  it(\"shows future review pill for upcoming dates\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(\"Review in 7d\")).toBeInTheDocument();\n    vi.useRealTimers();\n  });\n\n  it(\"review pill tooltip contains the actual date and click hint\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const pill = screen.getByText(\"Review in 7d\");\n    expect(pill.getAttribute(\"title\")).toContain(\"Click to change\");\n    vi.useRealTimers();\n  });\n\n  it(\"shows set review placeholder when solved but no reminder\", () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(\"+ Set review\")).toBeInTheDocument();\n  });\n\n  it(\"opens review modal when clicking set review placeholder\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"+ Set review\"));\n    expect(screen.getByRole(\"dialog\", { name: /review date/i })).toBeInTheDocument();\n  });\n\n  it(\"note column shows full note as tooltip\", () => {\n    const note = \"This is a long test note for tooltip\";\n    localStorage.setItem(\"leetcode-patterns-notes\", JSON.stringify({ 0: note }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const noteBtn = screen.getByText(note);\n    expect(noteBtn.getAttribute(\"title\")).toBe(note);\n  });\n\n  it(\"note column has no tooltip when empty\", () => {\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const noteBtn = screen.getByRole(\"button\", { name: /Add note for Two Sum/ });\n    expect(noteBtn.getAttribute(\"title\")).toBeNull();\n  });\n\n  it(\"clears reminder when clicking completed button on review pill\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(\"Review in 7d\")).toBeInTheDocument();\n    const completedBtn = screen.getByRole(\"button\", { name: /mark review as completed/i });\n    await user.click(completedBtn);\n    await waitFor(() => {\n      expect(screen.queryByText(\"Review in 7d\")).not.toBeInTheDocument();\n      expect(screen.getByText(\"+ Set review\")).toBeInTheDocument();\n    });\n    const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-reminders\")!);\n    expect(stored).not.toHaveProperty(\"0\");\n    vi.useRealTimers();\n  });\n\n  it(\"opens review modal when clicking review pill\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Review in 7d\"));\n    expect(screen.getByRole(\"dialog\", { name: /review date/i })).toBeInTheDocument();\n    vi.useRealTimers();\n  });\n\n  it(\"closes review modal when clicking close button\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Review in 7d\"));\n    expect(screen.getByRole(\"dialog\")).toBeInTheDocument();\n    await user.click(screen.getByRole(\"button\", { name: \"Close\" }));\n    await waitFor(() => expect(screen.queryByRole(\"dialog\")).not.toBeInTheDocument());\n    vi.useRealTimers();\n  });\n\n  it(\"shows clear review date button in modal when reminder exists\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Review in 7d\"));\n    expect(screen.getByRole(\"button\", { name: /clear review date/i })).toBeInTheDocument();\n    vi.useRealTimers();\n  });\n\n  it(\"does not show clear review date button when no reminder exists\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"+ Set review\"));\n    expect(screen.getByRole(\"dialog\")).toBeInTheDocument();\n    expect(screen.queryByRole(\"button\", { name: /clear review date/i })).not.toBeInTheDocument();\n  });\n\n  it(\"clears review date and closes modal when clicking clear button\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-09T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Review in 7d\"));\n    await user.click(screen.getByRole(\"button\", { name: /clear review date/i }));\n    await waitFor(() => {\n      expect(screen.queryByRole(\"dialog\")).not.toBeInTheDocument();\n      expect(screen.getByText(\"+ Set review\")).toBeInTheDocument();\n    });\n    const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-reminders\")!);\n    expect(stored).not.toHaveProperty(\"0\");\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"clear_review_date\", { question_id: 0 });\n    vi.useRealTimers();\n  });\n\n  it(\"solved date pill shows tooltip with actual date\", async () => {\n    vi.useFakeTimers({ shouldAdvanceTime: true });\n    vi.setSystemTime(new Date(\"2026-03-12T12:00:00Z\"));\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-solved-dates\", JSON.stringify({ \"0\": \"2026-03-09T10:00:00.000Z\" }));\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const solvedPill = screen.getByText(\"Solved 3d ago\");\n    expect(solvedPill.getAttribute(\"title\")).toContain(\"Mar\");\n    expect(solvedPill.getAttribute(\"title\")).toContain(\"2026\");\n    vi.useRealTimers();\n  });\n\n  it(\"tracks clear_all_reminders when clearing all reminders\", async () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([0]));\n    localStorage.setItem(\"leetcode-patterns-reminders\", JSON.stringify({ \"0\": { nextReview: \"2026-03-16\", interval: 7 } }));\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByRole(\"button\", { name: /Reminders/ }));\n    await user.click(screen.getByRole(\"button\", { name: \"Clear\" }));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"clear_all_reminders\");\n  });\n\n  it(\"filter checkboxes are visible without opening a dropdown\", () => {\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByLabelText(\"Starred only\")).toBeInTheDocument();\n    expect(screen.getByLabelText(\"Due for review\")).toBeInTheDocument();\n    expect(screen.getByLabelText(\"Hide completed\")).toBeInTheDocument();\n    expect(screen.getByLabelText(\"Hide patterns\")).toBeInTheDocument();\n  });\n\n  it(\"imports starred and solvedDates from file\", async () => {\n    const user = userEvent.setup();\n    render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n    const importContainer = screen.getByRole(\"button\", { name: /Import/ }).closest(\"div\")!;\n    const fileInput = importContainer.querySelector(\"input[type='file']\")! as HTMLInputElement;\n    const importData = JSON.stringify({\n      completed: [0],\n      starred: [1],\n      notes: {},\n      solvedDates: { \"0\": \"2025-06-01T00:00:00.000Z\" },\n    });\n    const file = new File([importData], \"progress.json\", { type: \"application/json\" });\n    await user.upload(fileInput, file);\n    await waitFor(() => {\n      expect(JSON.parse(localStorage.getItem(\"leetcode-patterns-starred\")!)).toEqual([1]);\n      expect(JSON.parse(localStorage.getItem(\"leetcode-patterns-solved-dates\")!)).toEqual({ \"0\": \"2025-06-01T00:00:00.000Z\" });\n    });\n  });\n\n  describe(\"legacy V1 migration\", () => {\n    it(\"migrates old checked array to new completed format via slug matching\", () => {\n      // Old V1 format: boolean array indexed by old 0-based id\n      // two-sum was at legacy index 147, add-two-numbers at 57\n      const checked = new Array(175).fill(false);\n      checked[147] = true; // two-sum\n      checked[57] = true;  // add-two-numbers\n      localStorage.setItem(\"checked\", JSON.stringify(checked));\n      render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n      // Both questions should be checked\n      const checkboxes = screen.getAllByRole(\"checkbox\", { name: /^Mark .+ as (complete|incomplete)$/ });\n      expect(checkboxes[0]).toBeChecked(); // Two Sum (id: 0)\n      expect(checkboxes[1]).toBeChecked(); // Add Two Numbers (id: 1)\n      expect(checkboxes[2]).not.toBeChecked(); // Median of Two Sorted Arrays\n\n      // Old keys should be cleaned up\n      expect(localStorage.getItem(\"checked\")).toBeNull();\n\n      // New format should be saved\n      const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-completed\")!);\n      expect(stored.sort()).toEqual([0, 1]);\n    });\n\n    it(\"shows a toast after migration\", () => {\n      const checked = new Array(175).fill(false);\n      checked[147] = true;\n      localStorage.setItem(\"checked\", JSON.stringify(checked));\n      render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n      expect(screen.getByText(/Migrated 1 completed question from V1/)).toBeInTheDocument();\n    });\n\n    it(\"only runs migration once\", () => {\n      const checked = new Array(175).fill(false);\n      checked[147] = true;\n      localStorage.setItem(\"checked\", JSON.stringify(checked));\n      const { unmount } = render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n      unmount();\n\n      // Re-add old key to simulate a second visit\n      localStorage.setItem(\"checked\", JSON.stringify(checked));\n      render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n      // Migration flag prevents re-running - old key should still be there\n      expect(localStorage.getItem(\"checked\")).not.toBeNull();\n    });\n\n    it(\"merges legacy progress with existing new progress\", () => {\n      localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([2]));\n      const checked = new Array(175).fill(false);\n      checked[147] = true; // two-sum -> id 0\n      localStorage.setItem(\"checked\", JSON.stringify(checked));\n      render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n\n      const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-completed\")!);\n      expect(stored.sort()).toEqual([0, 2]);\n    });\n\n    it(\"does nothing when no legacy data exists\", () => {\n      render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n      expect(screen.queryByText(/Migrated/)).not.toBeInTheDocument();\n      expect(screen.getByText(\"0/3 completed (0%)\")).toBeInTheDocument();\n    });\n\n    it(\"cleans up old showPatterns and hidePatterns keys\", () => {\n      const checked = new Array(175).fill(false);\n      checked[147] = true;\n      localStorage.setItem(\"checked\", JSON.stringify(checked));\n      localStorage.setItem(\"showPatterns\", JSON.stringify([true]));\n      localStorage.setItem(\"hidePatterns\", JSON.stringify([false]));\n      render(<QuestionsTable data={testData} updatedDate=\"2025-01-01\" />);\n      expect(localStorage.getItem(\"showPatterns\")).toBeNull();\n      expect(localStorage.getItem(\"hidePatterns\")).toBeNull();\n    });\n  });\n});\n"
  },
  {
    "path": "src/components/questions/QuestionsTable.tsx",
    "content": "\"use client\";\n\nimport { useState, useMemo, useCallback, useEffect, useRef, useSyncExternalStore } from \"react\";\nimport { useSearchParams } from \"next/navigation\";\nimport {\n  useReactTable,\n  getCoreRowModel,\n  getSortedRowModel,\n  getFilteredRowModel,\n  createColumnHelper,\n  flexRender,\n  type SortingState,\n  type ColumnFiltersState,\n} from \"@tanstack/react-table\";\nimport { Question } from \"@/types/question\";\nimport { ExternalLink, Star, Check } from \"lucide-react\";\nimport { trackEvent } from \"@/lib/analytics\";\nimport { loadCompleted, saveCompleted, loadStarred, saveStarred, loadNotes, saveNotes, loadSolvedDates, saveSolvedDates, loadShuffleOrder, saveShuffleOrder, migrateLegacyProgress, loadReminders, saveReminders } from \"@/lib/storage\";\nimport { useAuth } from \"@/components/layout/AuthContext\";\nimport { type Reminder, isDue, setCustomDate as setCustomReviewDate } from \"@/lib/reminders\";\nimport ProgressBar, { type ProgressStats } from \"./ProgressBar\";\nimport FilterToolbar from \"./FilterToolbar\";\nimport NoteModal, { type EditingNote } from \"./NoteModal\";\nimport ConfirmModal from \"./ConfirmModal\";\nimport GroupHeaderRow from \"./GroupHeaderRow\";\nimport QuestionRow from \"./QuestionRow\";\nimport ReviewDateModal, { type ReviewDateTarget } from \"./ReviewDateModal\";\n\nconst columnHelper = createColumnHelper<Question>();\n\nconst difficultyColor: Record<string, string> = {\n  Easy: \"text-green-700 dark:text-green-400\",\n  Medium: \"text-yellow-700 dark:text-yellow-400\",\n  Hard: \"text-red-700 dark:text-red-400\",\n};\n\nconst difficultyPill: Record<string, string> = {\n  Easy: \"bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400\",\n  Medium: \"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-400\",\n  Hard: \"bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400\",\n};\n\nconst makeColumns = (\n  completed: Set<number>,\n  toggleCompleted: (id: number) => void,\n  starred: Set<number>,\n  toggleStarred: (id: number) => void,\n  notes: Record<number, string>,\n  openNoteModal: (id: number, title: string) => void,\n  hidePatterns: boolean,\n  companyFilter: string[],\n  updatedDate: string,\n  solvedDates: Record<number, string>,\n  reminders: Record<number, Reminder>,\n  openReviewModal: (id: number, title: string) => void,\n  completeReminder: (id: number) => void\n) => [\n  columnHelper.display({\n    id: \"completed\",\n    header: () => <Check className=\"h-4 w-4\" />,\n    size: 40,\n    cell: (info) => (\n      <input\n        type=\"checkbox\"\n        checked={completed.has(info.row.original.id)}\n        onChange={() => toggleCompleted(info.row.original.id)}\n        className=\"h-4 w-4 pointer-events-none accent-blue-600\"\n        aria-label={`Mark ${info.row.original.title} as ${completed.has(info.row.original.id) ? \"incomplete\" : \"complete\"}`}\n      />\n    ),\n    meta: { clickable: true },\n  }),\n  columnHelper.display({\n    id: \"starred\",\n    header: \"★\",\n    size: 40,\n    cell: (info) => (\n      <span\n        role=\"checkbox\"\n        aria-checked={starred.has(info.row.original.id)}\n        aria-label={`Star ${info.row.original.title}`}\n      >\n        <Star\n          className={`h-4 w-4 pointer-events-none ${\n            starred.has(info.row.original.id)\n              ? \"fill-amber-400 text-amber-400\"\n              : \"text-zinc-300 dark:text-zinc-600\"\n          }`}\n        />\n      </span>\n    ),\n    meta: { clickable: true, toggleFn: \"starred\" },\n    enableSorting: false,\n  }),\n  columnHelper.accessor(\"title\", {\n    header: \"Title\",\n    cell: (info) => (\n      <a\n        href={`https://leetcode.com/problems/${info.row.original.slug}/`}\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        className=\"text-blue-600 hover:underline dark:text-blue-400\"\n      >\n        {info.getValue()}\n        {info.row.original.premium && (\n          <span className=\"ml-1 text-xs text-amber-500\">🔒</span>\n        )}\n      </a>\n    ),\n  }),\n  columnHelper.display({\n    id: \"solutions\",\n    header: \"Solutions\",\n    size: 75,\n    cell: (info) => (\n      <a\n        href={`https://leetcode.com/problems/${info.row.original.slug}/solutions/`}\n        target=\"_blank\"\n        rel=\"noopener noreferrer\"\n        title=\"View solutions\"\n        aria-label={`View solutions for ${info.row.original.title}`}\n        className=\"inline-flex items-center text-zinc-400 hover:text-blue-600 dark:text-zinc-500 dark:hover:text-blue-400\"\n      >\n        <ExternalLink className=\"h-4 w-4\" />\n      </a>\n    ),\n    enableSorting: false,\n    meta: { hideOnMobile: true },\n  }),\n  columnHelper.accessor(\"difficulty\", {\n    header: \"Difficulty\",\n    cell: (info) => (\n      <span className={`inline-block rounded-full px-2.5 py-0.5 text-xs font-semibold ${difficultyPill[info.getValue()]} ${completed.has(info.row.original.id) ? \"line-through opacity-60\" : \"\"}`}>\n        {info.getValue()}\n      </span>\n    ),\n    filterFn: (row, _columnId, filterValue: string[]) => {\n      if (!filterValue || filterValue.length === 0) return true;\n      return filterValue.includes(row.original.difficulty);\n    },\n    meta: { hideOnMobile: true },\n  }),\n  columnHelper.accessor(\"pattern\", {\n    header: \"Pattern(s)\",\n    cell: (info) => (\n      <div className=\"flex flex-wrap gap-1\">\n        {info.getValue().map((p) => (\n          <span\n            key={p}\n            className=\"whitespace-nowrap rounded-full bg-zinc-100 px-2 py-0.5 text-xs dark:bg-zinc-800\"\n          >\n            {hidePatterns ? \"•\".repeat(p.length) : p}\n          </span>\n        ))}\n      </div>\n    ),\n    filterFn: (row, _columnId, filterValue: string[]) => {\n      if (!filterValue || filterValue.length === 0) return true;\n      return row.original.pattern.some((p) =>\n        filterValue.some((f) => p.toLowerCase() === f.toLowerCase())\n      );\n    },\n  }),\n  columnHelper.accessor(\"companies\", {\n    header: () => (\n      <div>\n        <span>Companies</span>\n        <div className=\"text-[10px] font-normal text-zinc-400 dark:text-zinc-500\">\n          0–6 months, via{\" \"}\n          <a href=\"https://leetcode.com/subscribe/\" target=\"_blank\" rel=\"noopener noreferrer\" className=\"underline decoration-dotted\">\n            LC Premium\n          </a>\n          {\", \"}\n          {new Date(updatedDate).toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\", year: \"numeric\" })}\n        </div>\n      </div>\n    ),\n    meta: { hideOnMobile: true },\n    cell: (info) => (\n      <div className=\"flex w-[156px] flex-wrap gap-1\">\n        {info.getValue().map((c) => (\n          <span key={c.slug} className=\"group/icon relative\">\n            {/* eslint-disable-next-line @next/next/no-img-element */}\n            <img\n              src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? \"\"}/icons/${c.slug}.png`}\n              alt={c.name}\n              className=\"h-5 w-5 rounded-sm object-contain bg-white/0 p-px dark:bg-white/90 dark:rounded\"\n              onError={(e) => {\n                const img = e.target as HTMLImageElement;\n                const fallback = `https://www.google.com/s2/favicons?sz=64&domain_url=https://${c.slug}.com`;\n                if (!img.dataset.triedFallback) {\n                  img.dataset.triedFallback = \"1\";\n                  img.src = fallback;\n                } else {\n                  img.style.display = \"none\";\n                  img.nextElementSibling?.classList.remove(\"hidden\");\n                }\n              }}\n            />\n            <span className=\"hidden rounded-full bg-zinc-100 px-2 py-0.5 text-xs dark:bg-zinc-800\">\n              {c.name}\n            </span>\n            <span className=\"pointer-events-none absolute bottom-full left-1/2 z-10 mb-1.5 -translate-x-1/2 whitespace-nowrap rounded bg-zinc-800 px-2 py-1 text-xs text-white opacity-0 shadow transition-opacity group-hover/icon:opacity-100 dark:bg-zinc-200 dark:text-zinc-900\">\n              {c.name} - asked {c.frequency} {c.frequency === 1 ? \"time\" : \"times\"} in the last 6 months\n            </span>\n          </span>\n        ))}\n      </div>\n    ),\n    enableSorting: companyFilter.length === 1,\n    sortingFn: (rowA, rowB) => {\n      const slug = companyFilter[0];\n      const freqA = rowA.original.companies.find((c) => c.slug === slug)?.frequency ?? 0;\n      const freqB = rowB.original.companies.find((c) => c.slug === slug)?.frequency ?? 0;\n      return freqA - freqB;\n    },\n    filterFn: (row, _columnId, filterValue: string[]) => {\n      if (!filterValue || filterValue.length === 0) return true;\n      return row.original.companies.some(\n        (c) => filterValue.includes(c.slug)\n      );\n    },\n  }),\n  columnHelper.display({\n    id: \"notes\",\n    header: \"Notes\",\n    size: 100,\n    meta: { hideOnMobile: true, noStrikethrough: true },\n    cell: (info) => {\n      const note = notes[info.row.original.id];\n      return (\n        <button\n          onClick={() =>\n            openNoteModal(info.row.original.id, info.row.original.title)\n          }\n          title={note || undefined}\n          aria-label={`${note ? \"Edit\" : \"Add\"} note for ${info.row.original.title}`}\n          className=\"block max-w-[100px] cursor-pointer truncate text-left text-sm text-zinc-400 hover:text-zinc-600 dark:text-zinc-600 dark:hover:text-zinc-400\"\n        >\n          {note || <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M12 20h9\"/><path d=\"M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4 12.5-12.5z\"/></svg>}\n        </button>\n      );\n    },\n    enableSorting: false,\n  }),\n  columnHelper.display({\n    id: \"review\",\n    header: \"Review\",\n    size: 160,\n    meta: { hideOnMobile: true, noStrikethrough: true },\n    cell: (info) => {\n      const solvedDate = solvedDates[info.row.original.id];\n      const reminder = reminders[info.row.original.id];\n      if (!solvedDate && !reminder) return <span className=\"text-zinc-300 dark:text-zinc-700\">—</span>;\n      const dateFmt = { month: \"short\" as const, day: \"numeric\" as const, year: \"numeric\" as const };\n      return (\n        <div className=\"flex flex-col items-start gap-0.5\">\n          {solvedDate && (\n            <span\n              className=\"whitespace-nowrap rounded-full px-2 py-0.5 text-xs font-semibold ring-1 ring-inset bg-zinc-100 text-zinc-600 ring-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:ring-zinc-700\"\n              title={new Date(solvedDate).toLocaleDateString(\"en-US\", dateFmt)}\n            >\n              Solved {relativeDate(solvedDate, \"past\")}\n            </span>\n          )}\n          {reminder ? (\n            <span className={`inline-flex items-center whitespace-nowrap rounded-full text-xs font-semibold ring-1 ring-inset ${reviewPillStyle(reminder.nextReview)}`}>\n              <button\n                className=\"inline-flex items-center gap-1 cursor-pointer pl-2 pr-1.5 py-0.5 rounded-l-full transition-opacity hover:opacity-80\"\n                title={`${new Date(reminder.nextReview + \"T00:00:00\").toLocaleDateString(\"en-US\", dateFmt)} · Click to change`}\n                onClick={(e) => { e.stopPropagation(); openReviewModal(info.row.original.id, info.row.original.title); }}\n              >\n                {relativeDate(reminder.nextReview, \"future\")}\n              </button>\n              <span className=\"w-px self-stretch bg-current opacity-20\" />\n              <button\n                className=\"inline-flex items-center justify-center cursor-pointer px-1.5 py-0.5 rounded-r-full transition-opacity hover:opacity-80\"\n                title=\"Mark review as completed\"\n                onClick={(e) => { e.stopPropagation(); completeReminder(info.row.original.id); }}\n              >\n                <Check className=\"h-3 w-3\" strokeWidth={2.5} />\n              </button>\n            </span>\n          ) : solvedDate && (\n            <button\n              className=\"inline-flex items-center gap-1 whitespace-nowrap cursor-pointer rounded-full px-2 py-0.5 text-xs ring-1 ring-inset ring-dashed transition-colors text-zinc-400 ring-zinc-300 hover:text-zinc-600 hover:ring-zinc-400 dark:text-zinc-500 dark:ring-zinc-600 dark:hover:text-zinc-400 dark:hover:ring-zinc-500\"\n              title=\"Set a review date\"\n              onClick={(e) => { e.stopPropagation(); openReviewModal(info.row.original.id, info.row.original.title); }}\n            >\n              + Set review\n            </button>\n          )}\n        </div>\n      );\n    },\n    enableSorting: false,\n  }),\n];\n\nfunction daysDiff(isoDate: string): number {\n  const todayStr = new Date().toISOString().slice(0, 10);\n  const target = isoDate.slice(0, 10);\n  const todayMs = new Date(todayStr + \"T00:00:00Z\").getTime();\n  const targetMs = new Date(target + \"T00:00:00Z\").getTime();\n  return Math.round((targetMs - todayMs) / 86_400_000);\n}\n\nfunction reviewPillStyle(isoDate: string): string {\n  const diff = daysDiff(isoDate);\n  if (diff < 0) return \"bg-red-100 text-red-700 ring-red-200 hover:bg-red-200 dark:bg-red-900/40 dark:text-red-400 dark:ring-red-800 dark:hover:bg-red-900/60\";\n  if (diff === 0) return \"bg-orange-100 text-orange-700 ring-orange-200 hover:bg-orange-200 dark:bg-orange-900/40 dark:text-orange-400 dark:ring-orange-800 dark:hover:bg-orange-900/60\";\n  if (diff === 1) return \"bg-amber-100 text-amber-700 ring-amber-200 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-400 dark:ring-amber-800 dark:hover:bg-amber-900/60\";\n  if (diff <= 3) return \"bg-yellow-100 text-yellow-700 ring-yellow-200 hover:bg-yellow-200 dark:bg-yellow-900/40 dark:text-yellow-400 dark:ring-yellow-800 dark:hover:bg-yellow-900/60\";\n  if (diff <= 7) return \"bg-lime-100 text-lime-700 ring-lime-200 hover:bg-lime-200 dark:bg-lime-900/40 dark:text-lime-400 dark:ring-lime-800 dark:hover:bg-lime-900/60\";\n  if (diff <= 14) return \"bg-emerald-100 text-emerald-700 ring-emerald-200 hover:bg-emerald-200 dark:bg-emerald-900/40 dark:text-emerald-400 dark:ring-emerald-800 dark:hover:bg-emerald-900/60\";\n  if (diff <= 30) return \"bg-cyan-100 text-cyan-700 ring-cyan-200 hover:bg-cyan-200 dark:bg-cyan-900/40 dark:text-cyan-400 dark:ring-cyan-800 dark:hover:bg-cyan-900/60\";\n  return \"bg-zinc-100 text-zinc-600 ring-zinc-200 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:ring-zinc-700 dark:hover:bg-zinc-700\";\n}\n\nfunction relativeDate(isoDate: string, mode: \"past\" | \"future\"): string {\n  const diffDays = daysDiff(isoDate);\n  if (mode === \"past\") {\n    const ago = -diffDays;\n    if (ago <= 0) return \"today\";\n    if (ago === 1) return \"yesterday\";\n    return `${ago}d ago`;\n  }\n  if (diffDays < 0) return `Overdue ${-diffDays}d`;\n  if (diffDays === 0) return \"Due today\";\n  if (diffDays === 1) return \"Review tomorrow\";\n  return `Review in ${diffDays}d`;\n}\n\nconst mobileQuery = \"(max-width: 639px)\";\n\nfunction subscribeMobile(callback: () => void) {\n  const mq = window.matchMedia(mobileQuery);\n  mq.addEventListener(\"change\", callback);\n  return () => mq.removeEventListener(\"change\", callback);\n}\n\nfunction getMobileSnapshot() {\n  return window.matchMedia(mobileQuery).matches;\n}\n\nfunction getMobileServerSnapshot() {\n  return false;\n}\n\nfunction useIsMobile() {\n  return useSyncExternalStore(subscribeMobile, getMobileSnapshot, getMobileServerSnapshot);\n}\n\nfunction parseInitialFilters(searchParams: URLSearchParams) {\n  const filters: ColumnFiltersState = [];\n  const difficulty = searchParams.get(\"difficulty\");\n  if (difficulty) filters.push({ id: \"difficulty\", value: difficulty.split(\",\") });\n  const pattern = searchParams.get(\"pattern\");\n  if (pattern) filters.push({ id: \"pattern\", value: pattern.split(\",\") });\n  const companies = searchParams.get(\"companies\");\n  if (companies) filters.push({ id: \"companies\", value: companies.split(\",\") });\n  return filters;\n}\n\nexport default function QuestionsTable({ data, updatedDate }: { data: Question[]; updatedDate: string }) {\n  const isMobile = useIsMobile();\n  const searchParams = useSearchParams();\n  const { syncNow, syncVersion } = useAuth();\n\n  const [sorting, setSorting] = useState<SortingState>([]);\n  const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>(() =>\n    parseInitialFilters(searchParams)\n  );\n  const [globalFilter, setGlobalFilter] = useState(\n    () => searchParams.get(\"q\") ?? \"\"\n  );\n  const [completed, setCompleted] = useState<Set<number>>(new Set());\n  const [starred, setStarred] = useState<Set<number>>(new Set());\n  const [shuffleOrder, setShuffleOrder] = useState<number[] | null>(null);\n  const [notes, setNotes] = useState<Record<number, string>>({});\n  const [solvedDates, setSolvedDates] = useState<Record<number, string>>({});\n  const [reminders, setReminders] = useState<Record<number, Reminder>>({});\n  const [migrationToast, setMigrationToast] = useState<string | null>(null);\n  const [toastFading, setToastFading] = useState(false);\n  const [hydrated, setHydrated] = useState(false);\n\n  useEffect(() => {\n    const migrated = migrateLegacyProgress(data);\n    setCompleted(migrated ?? loadCompleted());\n    if (migrated) {\n      setMigrationToast(`Migrated ${migrated.size} completed question${migrated.size === 1 ? \"\" : \"s\"} from V1`);\n    }\n    setStarred(loadStarred());\n    setShuffleOrder(loadShuffleOrder());\n    setNotes(loadNotes());\n    setSolvedDates(loadSolvedDates());\n    setReminders(loadReminders());\n    setHydrated(true);\n  }, [data]);\n\n  // Reload from localStorage when remote sync arrives\n  useEffect(() => {\n    if (!hydrated || syncVersion === 0) return;\n    setCompleted(loadCompleted());\n    setStarred(loadStarred());\n    setNotes(loadNotes());\n    setSolvedDates(loadSolvedDates());\n    setReminders(loadReminders());\n  }, [syncVersion, hydrated]);\n\n  useEffect(() => {\n    if (!migrationToast) return;\n    const fadeTimer = setTimeout(() => setToastFading(true), 2500);\n    const removeTimer = setTimeout(() => { setMigrationToast(null); setToastFading(false); }, 3200);\n    return () => { clearTimeout(fadeTimer); clearTimeout(removeTimer); };\n  }, [migrationToast]);\n\n  useEffect(() => {\n    const params = new URLSearchParams();\n    if (globalFilter) params.set(\"q\", globalFilter);\n    const difficulty = columnFilters.find((f) => f.id === \"difficulty\")?.value as string[] | undefined;\n    if (difficulty?.length) params.set(\"difficulty\", difficulty.join(\",\"));\n    const pattern = columnFilters.find((f) => f.id === \"pattern\")?.value as string[] | undefined;\n    if (pattern?.length) params.set(\"pattern\", pattern.join(\",\"));\n    const companies = columnFilters.find((f) => f.id === \"companies\")?.value as string[] | undefined;\n    if (companies?.length) params.set(\"companies\", companies.join(\",\"));\n    const qs = params.toString();\n    window.history.replaceState(null, \"\", qs ? `?${qs}` : window.location.pathname);\n  }, [globalFilter, columnFilters]);\n\n  const toggleCompleted = useCallback((id: number) => {\n    let completing = false;\n    setCompleted((prev) => {\n      const next = new Set(prev);\n      completing = !next.has(id);\n      if (completing) next.add(id);\n      else next.delete(id);\n      saveCompleted(next);\n      trackEvent(\"question_toggle\", { question_id: id, completed: completing });\n      return next;\n    });\n    setSolvedDates((prev) => {\n      const next = { ...prev };\n      next[id] = new Date().toISOString();\n      saveSolvedDates(next);\n      return next;\n    });\n    if (!completing) {\n      setReminders((prev) => {\n        const next = { ...prev };\n        delete next[id];\n        saveReminders(next);\n        return next;\n      });\n    }\n    syncNow();\n  }, [syncNow]);\n\n  const [reviewTarget, setReviewTarget] = useState<ReviewDateTarget | null>(null);\n\n  const openReviewModal = useCallback((id: number, title: string) => {\n    setReviewTarget({ id, title });\n  }, []);\n\n  const onReviewDateChange = useCallback((id: number, date: string) => {\n    setReminders((prev) => {\n      const existing = prev[id];\n      const updated = existing\n        ? setCustomReviewDate(existing, date)\n        : { nextReview: date.slice(0, 10), interval: 1 };\n      const next = { ...prev, [id]: updated };\n      saveReminders(next);\n      trackEvent(\"custom_review_date\", { question_id: id });\n      return next;\n    });\n    setReviewTarget(null);\n    syncNow();\n  }, [syncNow]);\n\n  const onReviewDateClear = useCallback((id: number) => {\n    setReminders((prev) => {\n      const next = { ...prev };\n      delete next[id];\n      saveReminders(next);\n      trackEvent(\"clear_review_date\", { question_id: id });\n      return next;\n    });\n    setReviewTarget(null);\n    syncNow();\n  }, [syncNow]);\n\n  const toggleStarred = useCallback((id: number) => {\n    setStarred((prev) => {\n      const next = new Set(prev);\n      const starring = !next.has(id);\n      if (starring) next.add(id);\n      else next.delete(id);\n      saveStarred(next);\n      trackEvent(\"star_toggle\", { question_id: id, starred: starring });\n      return next;\n    });\n    syncNow();\n  }, [syncNow]);\n\n  const patterns = useMemo(() => {\n    const set = new Set<string>();\n    data.forEach((q) => q.pattern.forEach((p) => set.add(p)));\n    return Array.from(set).sort();\n  }, [data]);\n\n  const companies = useMemo(() => {\n    const map = new Map<string, string>();\n    data.forEach((q) =>\n      q.companies.forEach((c) => {\n        if (!map.has(c.slug)) map.set(c.slug, c.name);\n      })\n    );\n    return Array.from(map.entries())\n      .sort(([, a], [, b]) => a.localeCompare(b));\n  }, [data]);\n\n  const updateNote = useCallback((id: number, value: string) => {\n    setNotes((prev) => {\n      const next = { ...prev };\n      if (value) next[id] = value;\n      else delete next[id];\n      saveNotes(next);\n      trackEvent(\"note_save\", { question_id: id, has_content: !!value });\n      return next;\n    });\n    syncNow();\n  }, [syncNow]);\n\n  const [editingNote, setEditingNote] = useState<EditingNote | null>(null);\n\n  const openNoteModal = useCallback((id: number, title: string) => {\n    setEditingNote({ id, title, draft: notes[id] ?? \"\", confirmDiscard: false });\n  }, [notes]);\n\n  const [hideCompleted, setHideCompleted] = useState(false);\n  const [showStarredOnly, setShowStarredOnly] = useState(false);\n  const [hidePatterns, setHidePatterns] = useState(false);\n  const [showDueOnly, setShowDueOnly] = useState(false);\n\n  useEffect(() => {\n    setHideCompleted(localStorage.getItem(\"leetcode-patterns-hide-completed\") === \"true\");\n    setShowStarredOnly(localStorage.getItem(\"leetcode-patterns-starred-only\") === \"true\");\n    setHidePatterns(localStorage.getItem(\"leetcode-patterns-hide-patterns\") === \"true\");\n    setShowDueOnly(localStorage.getItem(\"leetcode-patterns-due-only\") === \"true\");\n  }, []);\n\n  useEffect(() => { if (hydrated) localStorage.setItem(\"leetcode-patterns-hide-completed\", String(hideCompleted)); }, [hideCompleted, hydrated]);\n  useEffect(() => { if (hydrated) localStorage.setItem(\"leetcode-patterns-starred-only\", String(showStarredOnly)); }, [showStarredOnly, hydrated]);\n  useEffect(() => { if (hydrated) localStorage.setItem(\"leetcode-patterns-hide-patterns\", String(hidePatterns)); }, [hidePatterns, hydrated]);\n  useEffect(() => { if (hydrated) localStorage.setItem(\"leetcode-patterns-due-only\", String(showDueOnly)); }, [showDueOnly, hydrated]);\n\n  const activeCompanyFilter = useMemo(\n    () => (columnFilters.find((f) => f.id === \"companies\")?.value as string[]) ?? [],\n    [columnFilters]\n  );\n\n  const columns = useMemo(\n    () => makeColumns(completed, toggleCompleted, starred, toggleStarred, notes, openNoteModal, hidePatterns, activeCompanyFilter, updatedDate, solvedDates, reminders, openReviewModal, onReviewDateClear),\n    [completed, toggleCompleted, starred, toggleStarred, notes, openNoteModal, hidePatterns, activeCompanyFilter, updatedDate, solvedDates, reminders, openReviewModal, onReviewDateClear]\n  );\n\n  useEffect(() => {\n    if (activeCompanyFilter.length === 1) {\n      setSorting([{ id: \"companies\", desc: true }]);\n    } else {\n      setSorting((prev) =>\n        prev.some((s) => s.id === \"companies\")\n          ? prev.filter((s) => s.id !== \"companies\")\n          : prev\n      );\n    }\n  }, [activeCompanyFilter]);\n\n  const filteredData = useMemo(() => {\n    let result = data;\n    if (hideCompleted) result = result.filter((q) => !completed.has(q.id));\n    if (showStarredOnly) result = result.filter((q) => starred.has(q.id));\n    if (showDueOnly) result = result.filter((q) => reminders[q.id] && isDue(reminders[q.id]));\n    return result;\n  }, [data, completed, starred, hideCompleted, showStarredOnly, showDueOnly, reminders]);\n\n  const columnVisibility = useMemo(() => {\n    if (!isMobile) return {};\n    const vis: Record<string, boolean> = {};\n    columns.forEach((col) => {\n      const id = \"accessorKey\" in col ? (col.accessorKey as string) : (col as { id?: string }).id;\n      if (id && (col.meta as { hideOnMobile?: boolean })?.hideOnMobile) {\n        vis[id] = false;\n      }\n    });\n    return vis;\n  }, [isMobile, columns]);\n\n  // eslint-disable-next-line react-hooks/incompatible-library\n  const table = useReactTable({\n    data: filteredData,\n    columns,\n    state: { sorting, columnFilters, globalFilter, columnVisibility },\n    globalFilterFn: (row, _columnId, filterValue: string) => {\n      const q = row.original;\n      const search = filterValue.toLowerCase();\n      return (\n        q.title.toLowerCase().includes(search) ||\n        q.difficulty.toLowerCase().includes(search) ||\n        q.pattern.some((p) => p.toLowerCase().includes(search)) ||\n        q.companies.some((c) => c.name.toLowerCase().includes(search))\n      );\n    },\n    onSortingChange: setSorting,\n    onColumnFiltersChange: setColumnFilters,\n    onGlobalFilterChange: setGlobalFilter,\n    getCoreRowModel: getCoreRowModel(),\n    getSortedRowModel: getSortedRowModel(),\n    getFilteredRowModel: getFilteredRowModel(),\n  });\n\n  const stats = useMemo<ProgressStats>(() => {\n    const totals = { Easy: 0, Medium: 0, Hard: 0 };\n    const done = { Easy: 0, Medium: 0, Hard: 0 };\n    const unfilteredData = showStarredOnly ? data.filter((q) => starred.has(q.id)) : data;\n    const filteredRows = table.getFilteredRowModel().rows;\n    const visibleIds = new Set(filteredRows.map((r) => r.original.id));\n    const baseRows = unfilteredData.filter(\n      (q) => visibleIds.has(q.id) || (hideCompleted && completed.has(q.id) && (() => {\n        const patternFilter = columnFilters.find((f) => f.id === \"pattern\");\n        const diffFilter = columnFilters.find((f) => f.id === \"difficulty\");\n        const companyFilter = columnFilters.find((f) => f.id === \"companies\");\n        const patternOk = !patternFilter || q.pattern.some((p) =>\n          (patternFilter.value as string[]).some((f) => p.toLowerCase() === f.toLowerCase())\n        );\n        const diffOk = !diffFilter || (diffFilter.value as string[]).includes(q.difficulty);\n        const companyOk = !companyFilter || q.companies.some((c) =>\n          (companyFilter.value as string[]).includes(c.slug)\n        );\n        const searchOk = !globalFilter || q.title.toLowerCase().includes(globalFilter.toLowerCase()) ||\n          q.difficulty.toLowerCase().includes(globalFilter.toLowerCase()) ||\n          q.pattern.some((p) => p.toLowerCase().includes(globalFilter.toLowerCase())) ||\n          q.companies.some((c) => c.name.toLowerCase().includes(globalFilter.toLowerCase()));\n        return patternOk && diffOk && companyOk && searchOk;\n      })())\n    );\n    baseRows.forEach((q) => {\n      totals[q.difficulty]++;\n      if (completed.has(q.id)) done[q.difficulty]++;\n    });\n    const total = baseRows.length;\n    const totalDone = done.Easy + done.Medium + done.Hard;\n    return { totals, done, total, totalDone };\n  }, [table, data, completed, starred, columnFilters, globalFilter, hideCompleted, showStarredOnly]);\n\n  const pickRandom = useCallback(() => {\n    const unsolved = table\n      .getFilteredRowModel()\n      .rows.filter((r) => !completed.has(r.original.id));\n    if (unsolved.length === 0) return;\n    const pick = unsolved[Math.floor(Math.random() * unsolved.length)];\n    trackEvent(\"random_question\", { question_id: pick.original.id, slug: pick.original.slug });\n    window.open(\n      `https://leetcode.com/problems/${pick.original.slug}/description/`,\n      \"_blank\"\n    );\n  }, [table, completed]);\n\n  const toggleShuffle = useCallback(() => {\n    if (shuffleOrder) {\n      setShuffleOrder(null);\n      saveShuffleOrder(null);\n      trackEvent(\"restore_order\");\n    } else {\n      const rows = table.getFilteredRowModel().rows;\n      const ids = rows.map((r) => r.original.id);\n      for (let i = ids.length - 1; i > 0; i--) {\n        const j = Math.floor(Math.random() * (i + 1));\n        [ids[i], ids[j]] = [ids[j], ids[i]];\n      }\n      setShuffleOrder(ids);\n      saveShuffleOrder(ids);\n      trackEvent(\"shuffle_questions\");\n    }\n  }, [table, shuffleOrder]);\n\n  const [resetConfirmGroup, setResetConfirmGroup] = useState<string | null>(null);\n  const [clearConfirm, setClearConfirm] = useState<\"notes\" | \"questions\" | \"starred\" | \"reminders\" | null>(null);\n  const [collapsedGroups, setCollapsedGroups] = useState<Set<string>>(new Set());\n\n  const toggleGroup = useCallback((group: string) => {\n    setCollapsedGroups((prev) => {\n      const next = new Set(prev);\n      if (next.has(group)) next.delete(group);\n      else next.add(group);\n      return next;\n    });\n  }, []);\n\n  const resetGroupProgress = useCallback((difficulty: string) => {\n    const ids = data.filter((q) => q.difficulty === difficulty).map((q) => q.id);\n    setCompleted((prev) => {\n      const next = new Set(prev);\n      ids.forEach((id) => next.delete(id));\n      saveCompleted(next);\n      return next;\n    });\n    setNotes((prev) => {\n      const next = { ...prev };\n      ids.forEach((id) => delete next[id]);\n      saveNotes(next);\n      return next;\n    });\n    setSolvedDates((prev) => {\n      const next = { ...prev };\n      ids.forEach((id) => delete next[id]);\n      saveSolvedDates(next);\n      return next;\n    });\n    setReminders((prev) => {\n      const next = { ...prev };\n      ids.forEach((id) => delete next[id]);\n      saveReminders(next);\n      return next;\n    });\n    trackEvent(\"reset_group\", { difficulty });\n    setResetConfirmGroup(null);\n    syncNow();\n  }, [data, syncNow]);\n\n  const clearAllNotes = useCallback(() => {\n    setNotes({});\n    saveNotes({});\n    trackEvent(\"clear_all_notes\");\n    setClearConfirm(null);\n    syncNow();\n  }, [syncNow]);\n\n  const clearAllQuestions = useCallback(() => {\n    setCompleted(new Set());\n    saveCompleted(new Set());\n    setSolvedDates({});\n    saveSolvedDates({});\n    setReminders({});\n    saveReminders({});\n    trackEvent(\"clear_all_progress\");\n    setClearConfirm(null);\n    syncNow();\n  }, [syncNow]);\n\n  const clearAllStarred = useCallback(() => {\n    setStarred(new Set());\n    saveStarred(new Set());\n    trackEvent(\"clear_all_starred\");\n    setClearConfirm(null);\n    syncNow();\n  }, [syncNow]);\n\n  const clearAllReminders = useCallback(() => {\n    setReminders({});\n    saveReminders({});\n    trackEvent(\"clear_all_reminders\");\n    setClearConfirm(null);\n    syncNow();\n  }, [syncNow]);\n\n  const exportProgress = useCallback(() => {\n    const payload = { completed: [...completed], starred: [...starred], notes, solvedDates, reminders };\n    const blob = new Blob([JSON.stringify(payload, null, 2)], { type: \"application/json\" });\n    const url = URL.createObjectURL(blob);\n    const a = document.createElement(\"a\");\n    a.href = url;\n    a.download = `leetcode-patterns-progress-${new Date().toISOString().slice(0, 10)}.json`;\n    a.click();\n    URL.revokeObjectURL(url);\n    trackEvent(\"export_progress\", { completed_count: completed.size, notes_count: Object.keys(notes).length });\n  }, [completed, starred, notes, solvedDates, reminders]);\n\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  const importProgress = useCallback((file: File) => {\n    const reader = new FileReader();\n    reader.onload = (e) => {\n      try {\n        const parsed = JSON.parse(e.target?.result as string);\n        if (Array.isArray(parsed.completed)) {\n          const imported = new Set<number>(parsed.completed);\n          setCompleted(imported);\n          saveCompleted(imported);\n        }\n        if (Array.isArray(parsed.starred)) {\n          const imported = new Set<number>(parsed.starred);\n          setStarred(imported);\n          saveStarred(imported);\n        }\n        if (parsed.notes && typeof parsed.notes === \"object\") {\n          setNotes(parsed.notes);\n          saveNotes(parsed.notes);\n        }\n        if (parsed.solvedDates && typeof parsed.solvedDates === \"object\") {\n          setSolvedDates(parsed.solvedDates);\n          saveSolvedDates(parsed.solvedDates);\n        }\n        if (parsed.reminders && typeof parsed.reminders === \"object\") {\n          setReminders(parsed.reminders);\n          saveReminders(parsed.reminders);\n        }\n        trackEvent(\"import_progress\", { completed_count: parsed.completed?.length ?? 0, notes_count: parsed.notes ? Object.keys(parsed.notes).length : 0 });\n        syncNow();\n      } catch {}\n    };\n    reader.readAsText(file);\n  }, [syncNow]);\n\n  const searchRef = useRef<HTMLInputElement>(null);\n\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key === \"Escape\") {\n        if (reviewTarget) { setReviewTarget(null); return; }\n        if (clearConfirm) { setClearConfirm(null); return; }\n        if (resetConfirmGroup) { setResetConfirmGroup(null); return; }\n        if (editingNote) {\n          const saved = notes[editingNote.id] ?? \"\";\n          if (editingNote.draft !== saved) {\n            setEditingNote({ ...editingNote, confirmDiscard: true });\n          } else {\n            setEditingNote(null);\n          }\n          return;\n        }\n      }\n      if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) return;\n      if (e.metaKey || e.ctrlKey || e.altKey) return;\n      if (e.key === \"/\" ) {\n        e.preventDefault();\n        searchRef.current?.focus();\n      } else if (e.key === \"r\") {\n        pickRandom();\n      }\n    };\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [pickRandom, editingNote, notes, clearConfirm, resetConfirmGroup, reviewTarget]);\n\n  // Track filter changes\n  const activeDifficultyFilter = useMemo(\n    () => (columnFilters.find((f) => f.id === \"difficulty\")?.value as string[])?.join(\",\") ?? \"\",\n    [columnFilters]\n  );\n  const activePatternFilter = useMemo(\n    () => (columnFilters.find((f) => f.id === \"pattern\")?.value as string[])?.join(\",\") ?? \"\",\n    [columnFilters]\n  );\n  const activeCompanyFilterStr = useMemo(\n    () => activeCompanyFilter.join(\",\"),\n    [activeCompanyFilter]\n  );\n\n  useEffect(() => {\n    if (activeDifficultyFilter) trackEvent(\"filter_difficulty\", { values: activeDifficultyFilter });\n  }, [activeDifficultyFilter]);\n\n  useEffect(() => {\n    if (activePatternFilter) trackEvent(\"filter_pattern\", { values: activePatternFilter });\n  }, [activePatternFilter]);\n\n  useEffect(() => {\n    if (activeCompanyFilterStr) trackEvent(\"filter_company\", { values: activeCompanyFilterStr });\n  }, [activeCompanyFilterStr]);\n\n  // Track search with debounce\n  useEffect(() => {\n    if (!globalFilter) return;\n    const timer = setTimeout(() => {\n      trackEvent(\"search\", { query: globalFilter });\n    }, 500);\n    return () => clearTimeout(timer);\n  }, [globalFilter]);\n\n  // Track sorting\n  useEffect(() => {\n    if (sorting.length > 0) {\n      trackEvent(\"sort_column\", { column: sorting[0].id, direction: sorting[0].desc ? \"desc\" : \"asc\" });\n    }\n  }, [sorting]);\n\n  const pct = stats.total > 0 ? Math.round((stats.totalDone / stats.total) * 100) : 0;\n\n  const companySortActive = sorting.some((s) => s.id === \"companies\");\n  const companySortDesc = sorting.find((s) => s.id === \"companies\")?.desc ?? true;\n\n  const tableRows = table.getRowModel().rows;\n\n  const groupedRows = useMemo(() => {\n    if (shuffleOrder) {\n      const orderMap = new Map(shuffleOrder.map((id, i) => [id, i]));\n      const sorted = [...tableRows].sort((a, b) => {\n        const ai = orderMap.get(a.original.id) ?? Infinity;\n        const bi = orderMap.get(b.original.id) ?? Infinity;\n        return ai - bi;\n      });\n      return [{ key: null as string | null, rows: sorted }];\n    }\n\n    if (companySortActive && activeCompanyFilter.length === 1) {\n      const slug = activeCompanyFilter[0];\n      const sorted = [...tableRows].sort((a, b) => {\n        const freqA = a.original.companies.find((c) => c.slug === slug)?.frequency ?? 0;\n        const freqB = b.original.companies.find((c) => c.slug === slug)?.frequency ?? 0;\n        return companySortDesc ? freqB - freqA : freqA - freqB;\n      });\n      return [{ key: null as string | null, rows: sorted }];\n    }\n\n    const groupMap = new Map<string, typeof tableRows>();\n    for (const row of tableRows) {\n      const key = row.original.difficulty;\n      if (!groupMap.has(key)) groupMap.set(key, []);\n      groupMap.get(key)!.push(row);\n    }\n\n    return [\"Easy\", \"Medium\", \"Hard\"]\n      .filter((k) => groupMap.has(k))\n      .map((key) => ({ key: key as string | null, rows: groupMap.get(key)! }));\n  }, [tableRows, shuffleOrder, companySortActive, companySortDesc, activeCompanyFilter]);\n\n  type FlatItem =\n    | { type: \"header\"; key: string; groupDone: number; total: number }\n    | { type: \"row\"; row: (typeof tableRows)[number] };\n\n  const flatItems = useMemo<FlatItem[]>(() => {\n    const items: FlatItem[] = [];\n    for (const group of groupedRows) {\n      const isCollapsed = group.key !== null && collapsedGroups.has(group.key);\n      const groupDone = group.rows.filter((r) => completed.has(r.original.id)).length;\n      if (group.key !== null) {\n        items.push({ type: \"header\", key: group.key, groupDone, total: group.rows.length });\n      }\n      if (!isCollapsed) {\n        for (const row of group.rows) {\n          items.push({ type: \"row\", row });\n        }\n      }\n    }\n    return items;\n  }, [groupedRows, collapsedGroups, completed]);\n\n  return (\n    <div className=\"space-y-4\">\n      {/* Sticky: Progress + Filters */}\n      <div className=\"sticky top-0 z-20 -mx-4 space-y-4 bg-background px-4 pb-4\">\n      {/* Progress */}\n      <ProgressBar stats={stats} pct={pct} />\n\n      {/* Filters */}\n      <FilterToolbar\n        table={table}\n        globalFilter={globalFilter}\n        setGlobalFilter={setGlobalFilter}\n        patterns={patterns}\n        companies={companies}\n        showStarredOnly={showStarredOnly}\n        setShowStarredOnly={setShowStarredOnly}\n        hideCompleted={hideCompleted}\n        setHideCompleted={setHideCompleted}\n        hidePatterns={hidePatterns}\n        setHidePatterns={setHidePatterns}\n        pickRandom={pickRandom}\n        shuffleOrder={shuffleOrder}\n        toggleShuffle={toggleShuffle}\n        exportProgress={exportProgress}\n        fileInputRef={fileInputRef}\n        importProgress={importProgress}\n        starred={starred}\n        notes={notes}\n        completed={completed}\n        reminders={reminders}\n        setClearConfirm={setClearConfirm}\n        searchRef={searchRef}\n        columnFilters={columnFilters}\n        showDueOnly={showDueOnly}\n        setShowDueOnly={setShowDueOnly}\n      />\n      </div>\n\n      {/* Table */}\n      <div className=\"overflow-x-auto rounded-lg border border-zinc-200 dark:border-zinc-800\">\n        <table className=\"w-full text-left text-sm\">\n          <thead className=\"border-b border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900\">\n            {table.getHeaderGroups().map((headerGroup) => (\n              <tr key={headerGroup.id}>\n                {headerGroup.headers.map((header) => (\n                  <th\n                    key={header.id}\n                    className=\"px-2 py-2 font-semibold whitespace-nowrap select-none sm:px-4 sm:py-3\"\n                    style={{ width: header.getSize() !== 150 ? header.getSize() : undefined }}\n                    onClick={header.column.getToggleSortingHandler()}\n                    onKeyDown={header.column.getCanSort() ? (e) => { if (e.key === \"Enter\" || e.key === \" \") { e.preventDefault(); header.column.getToggleSortingHandler()?.(e); } } : undefined}\n                    tabIndex={header.column.getCanSort() ? 0 : undefined}\n                    role={header.column.getCanSort() ? \"button\" : undefined}\n                    aria-sort={header.column.getIsSorted() === \"asc\" ? \"ascending\" : header.column.getIsSorted() === \"desc\" ? \"descending\" : undefined}\n                  >\n                    <span className=\"flex items-center gap-1\">\n                      {flexRender(\n                        header.column.columnDef.header,\n                        header.getContext()\n                      )}\n                      {{ asc: \" ▲\", desc: \" ▼\" }[\n                        header.column.getIsSorted() as string\n                      ] ?? null}\n                    </span>\n                  </th>\n                ))}\n              </tr>\n            ))}\n          </thead>\n          <tbody className=\"divide-y divide-zinc-200 dark:divide-zinc-800\">\n            {flatItems.map((item, idx) => {\n              if (item.type === \"header\") {\n                const isCollapsed = collapsedGroups.has(item.key);\n                return (\n                  <GroupHeaderRow\n                    key={`header-${item.key}`}\n                    groupKey={item.key}\n                    groupDone={item.groupDone}\n                    total={item.total}\n                    isCollapsed={isCollapsed}\n                    toggleGroup={toggleGroup}\n                    setResetConfirmGroup={setResetConfirmGroup}\n                    colSpan={columns.length}\n                    dataIndex={idx}\n                  />\n                );\n              }\n              const row = item.row;\n              return (\n                <QuestionRow\n                  key={row.id}\n                  row={row}\n                  completed={completed}\n                  toggleCompleted={toggleCompleted}\n                  toggleStarred={toggleStarred}\n                  dataIndex={idx}\n                />\n              );\n            })}\n          </tbody>\n        </table>\n      </div>\n\n      {/* Note Modal */}\n      {editingNote && (\n        <NoteModal\n          editingNote={editingNote}\n          setEditingNote={setEditingNote}\n          updateNote={updateNote}\n          notes={notes}\n        />\n      )}\n\n      {/* Review Date Modal */}\n      {reviewTarget && (\n        <ReviewDateModal\n          target={reviewTarget}\n          onSelect={onReviewDateChange}\n          onClear={reminders[reviewTarget.id] ? onReviewDateClear : undefined}\n          onCancel={() => setReviewTarget(null)}\n        />\n      )}\n\n      {/* Reset Confirmation Modal */}\n      {resetConfirmGroup && (() => {\n        const groupIds = data.filter((q) => q.difficulty === resetConfirmGroup).map((q) => q.id);\n        const doneCount = groupIds.filter((id) => completed.has(id)).length;\n        const noteCount = groupIds.filter((id) => notes[id]).length;\n        return (\n          <ConfirmModal\n            title={`Reset ${resetConfirmGroup} progress`}\n            message={\n              <>\n                This will clear {doneCount} completed question(s)\n                {noteCount > 0 && ` and ${noteCount} note(s)`} in the{\" \"}\n                <span className={`font-medium ${difficultyColor[resetConfirmGroup] ?? \"\"}`}>\n                  {resetConfirmGroup}\n                </span>{\" \"}\n                group. This action cannot be undone.\n              </>\n            }\n            confirmLabel=\"Reset\"\n            onConfirm={() => resetGroupProgress(resetConfirmGroup)}\n            onCancel={() => setResetConfirmGroup(null)}\n          />\n        );\n      })()}\n\n      {/* Clear All Confirmation Modal */}\n      {clearConfirm && (\n        <ConfirmModal\n          title={clearConfirm === \"notes\" ? \"Clear all notes\" : clearConfirm === \"starred\" ? \"Clear all stars\" : clearConfirm === \"reminders\" ? \"Clear all reminders\" : \"Clear all progress\"}\n          message={\n            clearConfirm === \"notes\"\n              ? `This will delete ${Object.keys(notes).length} note(s). This action cannot be undone.`\n              : clearConfirm === \"starred\"\n                ? `This will unstar ${starred.size} question(s). This action cannot be undone.`\n                : clearConfirm === \"reminders\"\n                  ? `This will remove ${Object.keys(reminders).length} review reminder(s). This action cannot be undone.`\n                  : `This will clear ${completed.size} completed question(s). This action cannot be undone.`\n          }\n          confirmLabel=\"Clear\"\n          onConfirm={clearConfirm === \"notes\" ? clearAllNotes : clearConfirm === \"starred\" ? clearAllStarred : clearConfirm === \"reminders\" ? clearAllReminders : clearAllQuestions}\n          onCancel={() => setClearConfirm(null)}\n        />\n      )}\n\n      {/* Migration Toast */}\n      {migrationToast && (\n        <div\n          className={`fixed inset-x-0 bottom-6 z-50 mx-auto w-fit animate-[fadeInUp_0.3s_ease-out] rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-800 shadow-lg transition-opacity duration-700 ease-in-out dark:border-green-800 dark:bg-green-950 dark:text-green-200 ${toastFading ? \"opacity-0\" : \"opacity-100\"}`}\n        >\n          ✓ {migrationToast}\n        </div>\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/questions/ReviewDateModal.tsx",
    "content": "import { useState } from \"react\";\nimport { today } from \"@/lib/reminders\";\n\nconst PRESETS = [\n  { label: \"Tomorrow\", days: 1 },\n  { label: \"3 days\", days: 3 },\n  { label: \"1 week\", days: 7 },\n  { label: \"2 weeks\", days: 14 },\n  { label: \"1 month\", days: 30 },\n];\n\nfunction addDays(dateStr: string, days: number): string {\n  const d = new Date(dateStr + \"T00:00:00Z\");\n  d.setUTCDate(d.getUTCDate() + days);\n  return d.toISOString().slice(0, 10);\n}\n\nexport interface ReviewDateTarget {\n  id: number;\n  title: string;\n}\n\nexport default function ReviewDateModal({\n  target,\n  onSelect,\n  onClear,\n  onCancel,\n}: {\n  target: ReviewDateTarget;\n  onSelect: (id: number, date: string) => void;\n  onClear?: (id: number) => void;\n  onCancel: () => void;\n}) {\n  const todayStr = today();\n  const [customDate, setCustomDate] = useState(\"\");\n\n  return (\n    <div\n      className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n      onClick={onCancel}\n      role=\"dialog\"\n      aria-modal=\"true\"\n      aria-label={`Set review date for ${target.title}`}\n    >\n      <div\n        className=\"mx-4 w-full max-w-[240px] rounded-lg border border-zinc-200 bg-white p-4 shadow-xl dark:border-zinc-700 dark:bg-zinc-900\"\n        onClick={(e) => e.stopPropagation()}\n      >\n        <div className=\"mb-3 flex items-center justify-between\">\n          <h2 className=\"text-sm font-semibold\">Review Date</h2>\n          <button\n            onClick={onCancel}\n            className=\"rounded p-0.5 text-zinc-400 transition-colors hover:bg-zinc-100 hover:text-zinc-600 dark:text-zinc-500 dark:hover:bg-zinc-800 dark:hover:text-zinc-300\"\n            aria-label=\"Close\"\n          >\n            <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\"><path d=\"M18 6 6 18\"/><path d=\"m6 6 12 12\"/></svg>\n          </button>\n        </div>\n\n        <div className=\"mb-3 flex flex-col gap-1\">\n          {PRESETS.map((p) => {\n            const date = addDays(todayStr, p.days);\n            return (\n              <button\n                key={p.days}\n                onClick={() => onSelect(target.id, date)}\n                className=\"flex items-center justify-between rounded px-2 py-1.5 text-xs transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n              >\n                <span>{p.label}</span>\n                <span className=\"text-[10px] text-zinc-400 dark:text-zinc-500\">\n                  {new Date(date + \"T00:00:00\").toLocaleDateString(\"en-US\", { month: \"short\", day: \"numeric\" })}\n                </span>\n              </button>\n            );\n          })}\n        </div>\n\n        <div className=\"border-t border-zinc-200 pt-2 dark:border-zinc-700\">\n          <div className=\"flex gap-1.5\">\n            <input\n              type=\"date\"\n              min={todayStr}\n              value={customDate}\n              onChange={(e) => setCustomDate(e.target.value)}\n              className=\"min-w-0 flex-1 rounded border border-zinc-300 bg-transparent px-2 py-1 text-xs focus:border-blue-500 focus:outline-none dark:border-zinc-700 dark:text-zinc-200 dark:[color-scheme:dark]\"\n            />\n            <button\n              onClick={() => { if (customDate) onSelect(target.id, customDate); }}\n              disabled={!customDate}\n              className=\"rounded bg-blue-600 px-2.5 py-1 text-xs font-medium text-white hover:bg-blue-700 disabled:opacity-40\"\n            >\n              Set\n            </button>\n          </div>\n        </div>\n\n        {onClear && (\n          <div className=\"mt-2 border-t border-zinc-200 pt-2 dark:border-zinc-700\">\n            <button\n              onClick={() => onClear(target.id)}\n              className=\"w-full rounded px-2 py-1.5 text-xs text-red-600 transition-colors hover:bg-red-50 dark:text-red-400 dark:hover:bg-red-900/30\"\n            >\n              Clear review date\n            </button>\n          </div>\n        )}\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/components/roadmaps/RoadmapView.test.tsx",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { render, screen, cleanup } from \"@testing-library/react\";\nimport userEvent from \"@testing-library/user-event\";\nimport type { Question } from \"@/types/question\";\n\nconst { mockTrackEvent } = vi.hoisted(() => ({\n  mockTrackEvent: vi.fn(),\n}));\n\nvi.mock(\"@/lib/analytics\", () => ({\n  trackEvent: mockTrackEvent,\n}));\n\nconst { mockSearchParams } = vi.hoisted(() => ({\n  mockSearchParams: { current: new URLSearchParams() },\n}));\n\nvi.mock(\"next/navigation\", () => ({\n  useSearchParams: () => mockSearchParams.current,\n}));\n\nvi.mock(\"@/components/layout/AuthContext\", () => ({\n  useAuth: () => ({ user: null, loading: false, signIn: vi.fn(), signOut: vi.fn(), syncNow: vi.fn(), syncVersion: 0 }),\n}));\n\nimport ViewSwitcher from \"@/components/layout/ViewSwitcher\";\nimport RoadmapView from \"@/components/roadmaps/RoadmapView\";\nimport { beginnerRoadmap, experiencedRoadmap } from \"@/data/roadmaps\";\n\nconst testData: Question[] = [\n  {\n    id: 0,\n    title: \"Two Sum\",\n    slug: \"two-sum\",\n    pattern: [\"Array\", \"Hash Table\"],\n    difficulty: \"Easy\",\n    premium: false,\n    companies: [],\n  },\n  {\n    id: 1,\n    title: \"Contains Duplicate\",\n    slug: \"contains-duplicate\",\n    pattern: [\"Array\", \"Hash Table\", \"Sorting\"],\n    difficulty: \"Easy\",\n    premium: false,\n    companies: [],\n  },\n  {\n    id: 2,\n    title: \"3Sum\",\n    slug: \"3sum\",\n    pattern: [\"Array\", \"Two Pointers\", \"Sorting\"],\n    difficulty: \"Medium\",\n    premium: false,\n    companies: [],\n  },\n];\n\ndescribe(\"ViewSwitcher\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    mockSearchParams.current = new URLSearchParams();\n    localStorage.clear();\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"renders all three view tabs\", () => {\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(\"All Questions\")).toBeInTheDocument();\n    expect(screen.getByText(\"Beginner Roadmap\")).toBeInTheDocument();\n    expect(screen.getByText(\"Experienced Roadmap\")).toBeInTheDocument();\n  });\n\n  it(\"defaults to All Questions view\", () => {\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    // QuestionsTable renders search, which is unique to the table view\n    expect(screen.getByPlaceholderText(\"Search\")).toBeInTheDocument();\n  });\n\n  it(\"switches to beginner roadmap view\", async () => {\n    const user = userEvent.setup();\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Beginner Roadmap\"));\n    expect(await screen.findByText(/structured path for those new/)).toBeInTheDocument();\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"switch_view\", { view: \"beginner\" });\n  });\n\n  it(\"switches to experienced roadmap view\", async () => {\n    const user = userEvent.setup();\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Experienced Roadmap\"));\n    expect(await screen.findByText(/Originally shared on/)).toBeInTheDocument();\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"switch_view\", { view: \"experienced\" });\n  });\n\n  it(\"persists view selection to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Beginner Roadmap\"));\n    expect(localStorage.getItem(\"leetcode-patterns-view\")).toBe(\"beginner\");\n  });\n\n  it(\"persists experienced view to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Experienced Roadmap\"));\n    expect(localStorage.getItem(\"leetcode-patterns-view\")).toBe(\"experienced\");\n  });\n\n  it(\"restores view selection from localStorage\", () => {\n    localStorage.setItem(\"leetcode-patterns-view\", \"experienced\");\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(/Originally shared on/)).toBeInTheDocument();\n  });\n\n  it(\"deep links to beginner roadmap via ?view=beginner\", () => {\n    mockSearchParams.current = new URLSearchParams(\"view=beginner\");\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(/structured path for those new/)).toBeInTheDocument();\n  });\n\n  it(\"deep links to Experienced Roadmap via ?view=experienced\", () => {\n    mockSearchParams.current = new URLSearchParams(\"view=experienced\");\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(/Originally shared on/)).toBeInTheDocument();\n  });\n\n  it(\"URL param takes priority over localStorage\", () => {\n    localStorage.setItem(\"leetcode-patterns-view\", \"experienced\");\n    mockSearchParams.current = new URLSearchParams(\"view=beginner\");\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByText(/structured path for those new/)).toBeInTheDocument();\n  });\n});\n\ndescribe(\"RoadmapView\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    localStorage.clear();\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"renders roadmap header and progress\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    expect(screen.getByText(beginnerRoadmap.name)).toBeInTheDocument();\n    expect(screen.getByText(/structured path for those new/)).toBeInTheDocument();\n    // Should show progress with matching questions\n    const completedElements = screen.getAllByText(/completed/);\n    expect(completedElements.length).toBeGreaterThanOrEqual(1);\n  });\n\n  it(\"renders phase headers\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    expect(screen.getByText(beginnerRoadmap.phases[0].title)).toBeInTheDocument();\n  });\n\n  it(\"renders questions that exist in the data\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    // Two Sum and Contains Duplicate are in both testData and the beginner roadmap\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n    expect(screen.getByText(\"Contains Duplicate\")).toBeInTheDocument();\n  });\n\n  it(\"shows pre-populated notes with lightbulb\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    // The beginner roadmap has notes behind a \"Show hint\" toggle\n    const containsDupNote = beginnerRoadmap.phases[0].questions.find(\n      (q) => q.slug === \"contains-duplicate\"\n    )!.note;\n    // Hint should be hidden initially\n    expect(screen.queryByText(containsDupNote)).not.toBeInTheDocument();\n    // Click the first \"Show hint\" button (contains-duplicate appears first)\n    const hintButtons = screen.getAllByText(\"Show hint\");\n    await user.click(hintButtons[0]);\n    expect(screen.getByText(containsDupNote)).toBeInTheDocument();\n  });\n\n  it(\"toggles question completion\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const checkboxes = screen.getAllByRole(\"checkbox\");\n    await user.click(checkboxes[0]);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"question_toggle\", expect.objectContaining({\n      completed: true,\n    }));\n  });\n\n  it(\"collapses and expands phases\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const phaseHeader = screen.getByText(beginnerRoadmap.phases[0].title);\n    const phaseButton = phaseHeader.closest(\"button\")!;\n\n    // Clicking should collapse\n    await user.click(phaseButton);\n    // Two Sum should no longer be visible (it's in phase 1)\n    expect(screen.queryByText(\"Two Sum\")).not.toBeInTheDocument();\n\n    // Click again to expand\n    await user.click(phaseButton);\n    expect(screen.getByText(\"Two Sum\")).toBeInTheDocument();\n  });\n\n  it(\"shows difficulty pills for each question\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    // Both Two Sum and Contains Duplicate are Easy\n    const easyPills = screen.getAllByText(\"Easy\");\n    expect(easyPills.length).toBeGreaterThanOrEqual(1);\n  });\n\n  it(\"renders experienced roadmap with its phases\", () => {\n    render(<RoadmapView roadmap={experiencedRoadmap} questions={testData} />);\n    expect(screen.getByText(experiencedRoadmap.name)).toBeInTheDocument();\n    expect(screen.getByText(experiencedRoadmap.phases[0].title)).toBeInTheDocument();\n  });\n\n  it(\"shows user notes when they exist\", () => {\n    localStorage.setItem(\"leetcode-patterns-notes\", JSON.stringify({ 0: \"My personal note\" }));\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    expect(screen.getByText(/My personal note/)).toBeInTheDocument();\n  });\n\n  it(\"toggles hint closed after opening\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const hintButtons = screen.getAllByText(\"Show hint\");\n    await user.click(hintButtons[0]);\n    const containsDupNote = beginnerRoadmap.phases[0].questions.find(\n      (q) => q.slug === \"contains-duplicate\"\n    )!.note;\n    expect(screen.getByText(containsDupNote)).toBeInTheDocument();\n    // Click the hint area again to collapse\n    await user.click(screen.getByText(containsDupNote));\n    expect(screen.queryByText(containsDupNote)).not.toBeInTheDocument();\n  });\n\n  it(\"un-completing a question fires event with completed: false\", async () => {\n    const user = userEvent.setup();\n    // Pre-mark question 1 (Contains Duplicate) as completed - it appears first in the Easy group\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([1]));\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    // Wait for the useEffect to load completed state\n    const checkbox = await screen.findByRole(\"checkbox\", { checked: true });\n    await user.click(checkbox);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"question_toggle\", expect.objectContaining({\n      completed: false,\n    }));\n  });\n\n  it(\"persists completion to localStorage\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const checkboxes = screen.getAllByRole(\"checkbox\");\n    await user.click(checkboxes[0]);\n    const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-completed\")!);\n    expect(stored).toContainEqual(expect.any(Number));\n  });\n\n  it(\"toggles star and tracks event\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const starButtons = screen.getAllByTitle(\"Star\");\n    await user.click(starButtons[0]);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"star_toggle\", expect.objectContaining({\n      starred: true,\n    }));\n    // Persists to localStorage\n    const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-starred\")!);\n    expect(stored.length).toBeGreaterThan(0);\n  });\n\n  it(\"unstars a starred question\", async () => {\n    const user = userEvent.setup();\n    localStorage.setItem(\"leetcode-patterns-starred\", JSON.stringify([0]));\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const unstarBtn = screen.getAllByTitle(\"Unstar\");\n    await user.click(unstarBtn[0]);\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"star_toggle\", expect.objectContaining({\n      starred: false,\n    }));\n  });\n\n  it(\"opens note modal and saves a note\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const noteButtons = screen.getAllByTitle(\"Add note\");\n    await user.click(noteButtons[0]);\n    // Modal should open\n    expect(screen.getByText(\"Add your notes below\")).toBeInTheDocument();\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"My study note\");\n    await user.click(screen.getByText(\"Done\"));\n    expect(mockTrackEvent).toHaveBeenCalledWith(\"note_save\", expect.objectContaining({\n      has_content: true,\n    }));\n  });\n\n  it(\"note modal shows unsaved changes warning on cancel\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const noteButtons = screen.getAllByTitle(\"Add note\");\n    await user.click(noteButtons[0]);\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"unsaved text\");\n    await user.click(screen.getByText(\"Cancel\"));\n    expect(screen.getByText(\"Unsaved changes\")).toBeInTheDocument();\n  });\n\n  it(\"note modal discard button closes without saving\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const noteButtons = screen.getAllByTitle(\"Add note\");\n    await user.click(noteButtons[0]);\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"will discard\");\n    await user.click(screen.getByText(\"Cancel\"));\n    // Should show the confirm dialog\n    await user.click(screen.getByText(\"Discard\"));\n    // Modal should be closed\n    expect(screen.queryByText(\"Add your notes below\")).not.toBeInTheDocument();\n  });\n\n  it(\"note modal keep editing returns to editor\", async () => {\n    const user = userEvent.setup();\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const noteButtons = screen.getAllByTitle(\"Add note\");\n    await user.click(noteButtons[0]);\n    const textarea = screen.getByPlaceholderText(\"Write your notes here...\");\n    await user.type(textarea, \"keep this\");\n    await user.click(screen.getByText(\"Cancel\"));\n    await user.click(screen.getByText(\"Keep editing\"));\n    // Should be back in the editor with text preserved\n    expect(screen.getByPlaceholderText(\"Write your notes here...\")).toHaveValue(\"keep this\");\n  });\n\n  it(\"renders difficulty group headers for beginner roadmap\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    // testData has Easy and Medium questions, both matched in beginner roadmap\n    expect(screen.getByText(\"Easy\")).toBeInTheDocument();\n  });\n\n  it(\"renders difficulty group headers for experienced roadmap\", () => {\n    render(<RoadmapView roadmap={experiencedRoadmap} questions={testData} />);\n    expect(screen.getByText(\"Easy\")).toBeInTheDocument();\n  });\n\n  it(\"beginner roadmap shows group descriptions\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    expect(screen.getByText(/Build your foundation/)).toBeInTheDocument();\n  });\n\n  it(\"experienced roadmap hides group descriptions\", () => {\n    render(<RoadmapView roadmap={experiencedRoadmap} questions={testData} />);\n    expect(screen.queryByText(/Build your foundation/)).not.toBeInTheDocument();\n  });\n});\n\ndescribe(\"RoadmapView next steps\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    localStorage.clear();\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"renders next steps section for beginner roadmap\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    expect(screen.getByText(\"Next Steps\")).toBeInTheDocument();\n    expect(screen.getByText(\"What to do after completing this roadmap\")).toBeInTheDocument();\n  });\n\n  it(\"renders next steps section for experienced roadmap\", () => {\n    render(<RoadmapView roadmap={experiencedRoadmap} questions={testData} />);\n    expect(screen.getByText(\"Next Steps\")).toBeInTheDocument();\n  });\n\n  it(\"renders the correct number of steps\", () => {\n    render(<RoadmapView roadmap={beginnerRoadmap} questions={testData} />);\n    const nextStepsContainer = screen.getByText(\"Next Steps\").closest(\".border-blue-300\")!;\n    const stepNumbers = nextStepsContainer.querySelectorAll(\".bg-blue-100\");\n    // pill + numbered circles\n    expect(stepNumbers.length).toBe(beginnerRoadmap.nextSteps!.length + 1);\n  });\n\n  it(\"renders external links with target=_blank\", () => {\n    render(<RoadmapView roadmap={experiencedRoadmap} questions={testData} />);\n    const prampLinks = screen.getAllByText(\"Pramp\");\n    const nextStepsPramp = prampLinks.find((el) => el.closest(\".border-blue-300\"));\n    expect(nextStepsPramp?.closest(\"a\")).toHaveAttribute(\"target\", \"_blank\");\n  });\n\n  it(\"does not render next steps when none are defined\", () => {\n    const roadmapWithoutSteps = { ...beginnerRoadmap, nextSteps: undefined };\n    render(<RoadmapView roadmap={roadmapWithoutSteps} questions={testData} />);\n    expect(screen.queryByText(\"Next Steps\")).not.toBeInTheDocument();\n  });\n});\n\ndescribe(\"Roadmap data integrity\", () => {\n  it(\"beginner roadmap has no duplicate question slugs\", () => {\n    const slugs = beginnerRoadmap.phases.flatMap((p) => p.questions.map((q) => q.slug));\n    expect(new Set(slugs).size).toBe(slugs.length);\n  });\n\n  it(\"experienced roadmap has no duplicate question slugs\", () => {\n    const slugs = experiencedRoadmap.phases.flatMap((p) => p.questions.map((q) => q.slug));\n    expect(new Set(slugs).size).toBe(slugs.length);\n  });\n\n  it(\"experienced roadmap contains exactly 75 questions\", () => {\n    const count = experiencedRoadmap.phases.reduce(\n      (sum, p) => sum + p.questions.length,\n      0\n    );\n    expect(count).toBe(75);\n  });\n\n  it(\"both roadmaps have nextSteps defined\", () => {\n    expect(beginnerRoadmap.nextSteps).toBeDefined();\n    expect(beginnerRoadmap.nextSteps!.length).toBeGreaterThan(0);\n    expect(experiencedRoadmap.nextSteps).toBeDefined();\n    expect(experiencedRoadmap.nextSteps!.length).toBeGreaterThan(0);\n  });\n\n  it(\"every roadmap question has a non-empty note\", () => {\n    const allQuestions = [\n      ...beginnerRoadmap.phases.flatMap((p) => p.questions),\n      ...experiencedRoadmap.phases.flatMap((p) => p.questions),\n    ];\n    for (const q of allQuestions) {\n      expect(q.note.length, `${q.slug} has empty note`).toBeGreaterThan(0);\n    }\n  });\n});\n\ndescribe(\"ViewSwitcher edge cases\", () => {\n  beforeEach(() => {\n    mockTrackEvent.mockClear();\n    mockSearchParams.current = new URLSearchParams();\n    localStorage.clear();\n  });\n\n  afterEach(() => {\n    cleanup();\n    vi.restoreAllMocks();\n  });\n\n  it(\"ignores invalid ?view param and defaults to table\", () => {\n    mockSearchParams.current = new URLSearchParams(\"view=nonexistent\");\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    expect(screen.getByPlaceholderText(\"Search\")).toBeInTheDocument();\n  });\n\n  it(\"switching back to table removes view param\", async () => {\n    const user = userEvent.setup();\n    render(<ViewSwitcher questions={testData} updatedDate=\"2025-01-01\" />);\n    await user.click(screen.getByText(\"Beginner Roadmap\"));\n    await user.click(screen.getByText(\"All Questions\"));\n    expect(localStorage.getItem(\"leetcode-patterns-view\")).toBe(\"table\");\n  });\n});\n"
  },
  {
    "path": "src/components/roadmaps/RoadmapView.tsx",
    "content": "\"use client\";\n\nimport { useState, useMemo, useCallback, useEffect, Fragment } from \"react\";\nimport {\n  ChevronDown,\n  ChevronRight,\n  ExternalLink,\n  NotebookPen,\n  Check,\n  Star,\n  Lock,\n} from \"lucide-react\";\nimport { Question } from \"@/types/question\";\nimport { Roadmap } from \"@/data/roadmaps\";\nimport { trackEvent } from \"@/lib/analytics\";\nimport { loadCompleted, saveCompleted, loadStarred, saveStarred, loadNotes, saveNotes, loadSolvedDates, saveSolvedDates, loadReminders, saveReminders, MAX_NOTE_LENGTH } from \"@/lib/storage\";\nimport { useAuth } from \"@/components/layout/AuthContext\";\nimport { type Reminder, initReminder } from \"@/lib/reminders\";\n\nfunction InlineMarkdown({ text }: { text: string }) {\n  const lines = text.split(\"\\n\");\n  return (\n    <>\n      {lines.map((line, li) => (\n        <Fragment key={li}>\n          {li > 0 && <br />}\n          {line.split(/(\\*\\*\\*[^*]+\\*\\*\\*|\\*\\*[^*]+\\*\\*|\\*[^*]+\\*|`[^`]+`|\\[[^\\]]+\\]\\([^)]+\\))/g).map((part, pi) => {\n            if (/^\\*\\*\\*(.+)\\*\\*\\*$/.test(part))\n              return <strong key={pi}><em>{part.slice(3, -3)}</em></strong>;\n            if (/^\\*\\*(.+)\\*\\*$/.test(part))\n              return <strong key={pi}>{part.slice(2, -2)}</strong>;\n            if (/^\\*(.+)\\*$/.test(part))\n              return <em key={pi}>{part.slice(1, -1)}</em>;\n            if (/^`(.+)`$/.test(part))\n              return <code key={pi} className=\"rounded bg-zinc-100 px-1 py-0.5 font-mono text-xs dark:bg-zinc-800\">{part.slice(1, -1)}</code>;\n            const linkMatch = part.match(/^\\[([^\\]]+)\\]\\(([^)]+)\\)$/);\n            if (linkMatch) {\n              const isExternal = /^https?:\\/\\//.test(linkMatch[2]);\n              return <a key={pi} href={linkMatch[2]} {...(isExternal ? { target: \"_blank\", rel: \"noopener noreferrer\" } : {})} className=\"text-blue-600 underline decoration-dotted hover:decoration-solid dark:text-blue-400\">{linkMatch[1]}</a>;\n            }\n            return part;\n          })}\n        </Fragment>\n      ))}\n    </>\n  );\n}\n\nconst difficultyPill: Record<string, string> = {\n  Easy: \"bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400\",\n  Medium:\n    \"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/40 dark:text-yellow-400\",\n  Hard: \"bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400\",\n};\n\ninterface Props {\n  roadmap: Roadmap;\n  questions: Question[];\n}\n\nexport default function RoadmapView({ roadmap, questions }: Props) {\n  const { syncNow, syncVersion } = useAuth();\n  const slugToQuestion = useMemo(() => {\n    const map = new Map<string, Question>();\n    questions.forEach((q) => map.set(q.slug, q));\n    return map;\n  }, [questions]);\n\n  const [completed, setCompleted] = useState<Set<number>>(new Set());\n  const [starred, setStarred] = useState<Set<number>>(new Set());\n  const [notes, setNotes] = useState<Record<number, string>>({});\n  const [, setSolvedDates] = useState<Record<number, string>>({});\n  const [, setReminders] = useState<Record<number, Reminder>>({});\n  const [collapsedPhases, setCollapsedPhases] = useState<Set<string | number>>(\n    new Set()\n  );\n  const [expandedHints, setExpandedHints] = useState<Set<string>>(new Set());\n  const [editingNote, setEditingNote] = useState<{\n    id: number;\n    title: string;\n    draft: string;\n    confirmDiscard: boolean;\n  } | null>(null);\n\n  useEffect(() => {\n    setCompleted(loadCompleted());\n    setStarred(loadStarred());\n    setNotes(loadNotes());\n    setSolvedDates(loadSolvedDates());\n    setReminders(loadReminders());\n  }, []);\n\n  // Reload from localStorage when remote sync arrives\n  useEffect(() => {\n    if (syncVersion === 0) return;\n    setCompleted(loadCompleted());\n    setStarred(loadStarred());\n    setNotes(loadNotes());\n    setSolvedDates(loadSolvedDates());\n    setReminders(loadReminders());\n  }, [syncVersion]);\n\n  const toggleCompleted = useCallback((id: number) => {\n    let completing = false;\n    setCompleted((prev) => {\n      const next = new Set(prev);\n      completing = !next.has(id);\n      if (completing) next.add(id);\n      else next.delete(id);\n      saveCompleted(next);\n      trackEvent(\"question_toggle\", {\n        question_id: id,\n        completed: completing,\n      });\n      return next;\n    });\n    setSolvedDates((prev) => {\n      const next = { ...prev };\n      next[id] = new Date().toISOString();\n      saveSolvedDates(next);\n      return next;\n    });\n    setReminders((prev) => {\n      const next = { ...prev };\n      if (completing) {\n        next[id] = initReminder(new Date().toISOString());\n      } else {\n        delete next[id];\n      }\n      saveReminders(next);\n      return next;\n    });\n    syncNow();\n  }, [syncNow]);\n\n  const toggleStarred = useCallback((id: number) => {\n    setStarred((prev) => {\n      const next = new Set(prev);\n      const starring = !next.has(id);\n      if (starring) next.add(id);\n      else next.delete(id);\n      saveStarred(next);\n      trackEvent(\"star_toggle\", { question_id: id, starred: starring });\n      return next;\n    });\n    syncNow();\n  }, [syncNow]);\n\n  const updateNote = useCallback((id: number, value: string) => {\n    setNotes((prev) => {\n      const next = { ...prev };\n      if (value) next[id] = value;\n      else delete next[id];\n      saveNotes(next);\n      trackEvent(\"note_save\", { question_id: id, has_content: !!value });\n      return next;\n    });\n    syncNow();\n  }, [syncNow]);\n\n  const openNoteModal = useCallback(\n    (id: number, title: string) => {\n      setEditingNote({\n        id,\n        title,\n        draft: notes[id] ?? \"\",\n        confirmDiscard: false,\n      });\n    },\n    [notes]\n  );\n\n  const togglePhase = useCallback((index: string | number) => {\n    setCollapsedPhases((prev) => {\n      const next = new Set(prev);\n      if (next.has(index)) next.delete(index);\n      else next.add(index);\n      return next;\n    });\n  }, []);\n\n  // Escape key handler\n  useEffect(() => {\n    const handleKeyDown = (e: KeyboardEvent) => {\n      if (e.key !== \"Escape\") return;\n      if (editingNote) {\n        const saved = notes[editingNote.id] ?? \"\";\n        if (editingNote.draft !== saved) {\n          setEditingNote({ ...editingNote, confirmDiscard: true });\n        } else {\n          setEditingNote(null);\n        }\n      }\n    };\n    window.addEventListener(\"keydown\", handleKeyDown);\n    return () => window.removeEventListener(\"keydown\", handleKeyDown);\n  }, [editingNote, notes]);\n\n  // Compute stats\n  const stats = useMemo(() => {\n    let total = 0;\n    let done = 0;\n    const doneByDiff = { Easy: 0, Medium: 0, Hard: 0 };\n    const totalByDiff = { Easy: 0, Medium: 0, Hard: 0 };\n    roadmap.phases.forEach((phase) => {\n      phase.questions.forEach((rq) => {\n        const q = slugToQuestion.get(rq.slug);\n        if (q) {\n          total++;\n          if (q.difficulty in totalByDiff) totalByDiff[q.difficulty as keyof typeof totalByDiff]++;\n          if (completed.has(q.id)) {\n            done++;\n            if (q.difficulty in doneByDiff) doneByDiff[q.difficulty as keyof typeof doneByDiff]++;\n          }\n        }\n      });\n    });\n    return { total, done, pct: total > 0 ? Math.round((done / total) * 100) : 0, doneByDiff, totalByDiff };\n  }, [roadmap, slugToQuestion, completed]);\n\n  const phaseStats = useMemo(() => {\n    return roadmap.phases.map((phase) => {\n      let total = 0;\n      let done = 0;\n      phase.questions.forEach((rq) => {\n        const q = slugToQuestion.get(rq.slug);\n        if (q) {\n          total++;\n          if (completed.has(q.id)) done++;\n        }\n      });\n      return { total, done };\n    });\n  }, [roadmap, slugToQuestion, completed]);\n\n  // For beginner roadmap: group by difficulty, then phases within each\n  const difficultyGroups = useMemo(() => {\n    if (![\"beginner\", \"experienced\"].includes(roadmap.id)) return null;\n    const difficulties = [\"Easy\", \"Medium\", \"Hard\"] as const;\n    const groups = difficulties\n      .map((diff) => {\n        const phases = roadmap.phases\n          .map((phase, phaseIndex) => {\n            const qs = phase.questions.filter((rq) => {\n              const q = slugToQuestion.get(rq.slug);\n              return q?.difficulty === diff;\n            });\n            if (qs.length === 0) return null;\n            return { phase, phaseIndex, questions: qs };\n          })\n          .filter(Boolean) as {\n          phase: (typeof roadmap.phases)[number];\n          phaseIndex: number;\n          questions: (typeof roadmap.phases)[number][\"questions\"];\n        }[];\n        if (phases.length === 0) return null;\n        const total = phases.reduce((sum, p) => sum + p.questions.length, 0);\n        const done = phases.reduce(\n          (sum, p) =>\n            sum +\n            p.questions.filter((rq) => {\n              const q = slugToQuestion.get(rq.slug);\n              return q && completed.has(q.id);\n            }).length,\n          0\n        );\n        return { difficulty: diff, phases, total, done, phaseOffset: 0 };\n      })\n      .filter(Boolean) as {\n      difficulty: string;\n      phases: {\n        phase: (typeof roadmap.phases)[number];\n        phaseIndex: number;\n        questions: (typeof roadmap.phases)[number][\"questions\"];\n      }[];\n      total: number;\n      done: number;\n      phaseOffset: number;\n    }[];\n\n    let offset = 0;\n    for (const group of groups) {\n      group.phaseOffset = offset;\n      offset += group.phases.length;\n    }\n    return groups;\n  }, [roadmap, slugToQuestion, completed]);\n\n  const renderQuestionRow = (q: Question, rq: { slug: string; note: string }, { showDifficulty = true } = {}) => {\n    const isDone = completed.has(q.id);\n    const isStarred = starred.has(q.id);\n    const userNote = notes[q.id];\n\n    return (\n      <div\n        key={rq.slug}\n        className={`flex items-start gap-3 border-b border-zinc-100 px-4 py-3 last:border-b-0 dark:border-zinc-800/50 ${\n          isDone ? \"bg-green-50/30 dark:bg-green-950/20\" : \"\"\n        }`}\n      >\n        <input\n          type=\"checkbox\"\n          checked={isDone}\n          onChange={() => toggleCompleted(q.id)}\n          className=\"mt-1 h-4 w-4 shrink-0 accent-blue-600\"\n        />\n        <div className=\"min-w-0 flex-1\">\n          <div className=\"flex flex-wrap items-center gap-2\">\n            <a\n              href={`https://leetcode.com/problems/${q.slug}/`}\n              target=\"_blank\"\n              rel=\"noopener noreferrer\"\n              className={`font-medium hover:underline ${\n                isDone\n                  ? \"text-zinc-400 line-through dark:text-zinc-500\"\n                  : \"text-blue-600 dark:text-blue-400\"\n              }`}\n            >\n              {q.title}\n              {q.premium && (\n                <Lock className=\"ml-1 inline h-3 w-3 text-amber-500\" />\n              )}\n            </a>\n            {showDifficulty && (\n              <span\n                className={`inline-block rounded-full px-2 py-0.5 text-xs font-semibold ${difficultyPill[q.difficulty]}`}\n              >\n                {q.difficulty}\n              </span>\n            )}\n            <div className=\"flex flex-wrap gap-1\">\n              {q.pattern.map((p) => (\n                <span\n                  key={p}\n                  className=\"rounded-full bg-zinc-100 px-2 py-0.5 text-xs dark:bg-zinc-800\"\n                >\n                  {p}\n                </span>\n              ))}\n            </div>\n            {q.companies.length > 0 && (\n              <div className=\"flex flex-wrap gap-1\">\n                {q.companies.map((c) => (\n                  <span key={c.slug} className=\"group/icon relative\">\n                    {/* eslint-disable-next-line @next/next/no-img-element */}\n                    <img\n                      src={`${process.env.NEXT_PUBLIC_BASE_PATH ?? \"\"}/icons/${c.slug}.png`}\n                      alt={c.name}\n                      className=\"h-4 w-4 rounded-sm object-contain bg-white/0 p-px dark:bg-white/90 dark:rounded\"\n                      onError={(e) => {\n                        const img = e.target as HTMLImageElement;\n                        const fallback = `https://www.google.com/s2/favicons?sz=64&domain_url=https://${c.slug}.com`;\n                        if (!img.dataset.triedFallback) {\n                          img.dataset.triedFallback = \"1\";\n                          img.src = fallback;\n                        } else {\n                          img.style.display = \"none\";\n                          img.nextElementSibling?.classList.remove(\"hidden\");\n                        }\n                      }}\n                    />\n                    <span className=\"hidden rounded-full bg-zinc-100 px-1.5 py-0.5 text-[10px] dark:bg-zinc-800\">\n                      {c.name}\n                    </span>\n                    <span className=\"pointer-events-none absolute bottom-full left-1/2 z-10 mb-1.5 -translate-x-1/2 whitespace-nowrap rounded bg-zinc-800 px-2 py-1 text-xs text-white opacity-0 shadow transition-opacity group-hover/icon:opacity-100 dark:bg-zinc-200 dark:text-zinc-900\">\n                      {c.name} - asked {c.frequency} {c.frequency === 1 ? \"time\" : \"times\"} in the last 6 months\n                    </span>\n                  </span>\n                ))}\n              </div>\n            )}\n          </div>\n          <button\n            type=\"button\"\n            onClick={() =>\n              setExpandedHints((prev) => {\n                const next = new Set(prev);\n                if (next.has(rq.slug)) next.delete(rq.slug);\n                else next.add(rq.slug);\n                return next;\n              })\n            }\n            className={`mt-1.5 inline-flex items-center gap-1.5 rounded-md px-2 py-1 text-sm transition-colors ${\n              expandedHints.has(rq.slug)\n                ? \"bg-amber-50 text-zinc-600 dark:bg-amber-950/30 dark:text-zinc-300\"\n                : \"bg-amber-50 text-amber-700 hover:bg-amber-100 dark:bg-amber-950/30 dark:text-amber-400 dark:hover:bg-amber-950/50\"\n            }`}\n          >\n            💡\n            {expandedHints.has(rq.slug) ? (\n              <span className=\"break-words text-left\">{rq.note}</span>\n            ) : (\n              <span>Show hint</span>\n            )}\n          </button>\n          {userNote && (\n            <p className=\"mt-1 break-words text-sm text-zinc-600 dark:text-zinc-300\">\n              <NotebookPen className=\"inline h-3.5 w-3.5 shrink-0 text-blue-500\" /> {userNote}\n            </p>\n          )}\n        </div>\n        <div className=\"flex shrink-0 items-center gap-1\">\n          <button\n            onClick={() => toggleStarred(q.id)}\n            className=\"rounded p-1 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n            title={isStarred ? \"Unstar\" : \"Star\"}\n          >\n            <Star\n              className={`h-4 w-4 ${\n                isStarred\n                  ? \"fill-amber-400 text-amber-400\"\n                  : \"text-zinc-300 dark:text-zinc-600\"\n              }`}\n            />\n          </button>\n          <button\n            onClick={() => openNoteModal(q.id, q.title)}\n            className=\"rounded p-1 transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800\"\n            title=\"Add note\"\n          >\n            <NotebookPen\n              className={`h-4 w-4 ${\n                userNote\n                  ? \"text-blue-500\"\n                  : \"text-zinc-300 dark:text-zinc-600\"\n              }`}\n            />\n          </button>\n          <a\n            href={`https://leetcode.com/problems/${q.slug}/solutions/`}\n            target=\"_blank\"\n            rel=\"noopener noreferrer\"\n            className=\"rounded p-1 text-zinc-300 transition-colors hover:bg-zinc-100 hover:text-blue-600 dark:text-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-blue-400\"\n            title=\"View solutions\"\n          >\n            <ExternalLink className=\"h-4 w-4\" />\n          </a>\n        </div>\n      </div>\n    );\n  };\n\n  return (\n    <div className=\"space-y-6\">\n      {/* Header */}\n      <div className=\"group rounded-lg border border-zinc-200 bg-zinc-50 p-4 dark:border-zinc-800 dark:bg-zinc-900\">\n        <h2 className=\"text-lg font-bold\">{roadmap.name}</h2>\n        <p className=\"mt-1 text-sm text-zinc-500\"><InlineMarkdown text={roadmap.description} /></p>\n        <div className=\"mt-3\">\n          <div className=\"mb-1 flex flex-wrap items-center gap-x-4 gap-y-1 text-sm font-medium\">\n            <span>{stats.done}/{stats.total} completed ({stats.pct}%)</span>\n            <div className=\"flex gap-4 sm:opacity-0 sm:transition-opacity sm:duration-500 sm:ease-in-out sm:group-hover:opacity-100\">\n              {stats.totalByDiff.Easy > 0 && (\n                <span className=\"text-green-700 dark:text-green-400\">\n                  Easy: {stats.doneByDiff.Easy}/{stats.totalByDiff.Easy}\n                </span>\n              )}\n              {stats.totalByDiff.Medium > 0 && (\n                <span className=\"text-yellow-700 dark:text-yellow-400\">\n                  Medium: {stats.doneByDiff.Medium}/{stats.totalByDiff.Medium}\n                </span>\n              )}\n              {stats.totalByDiff.Hard > 0 && (\n                <span className=\"text-red-700 dark:text-red-400\">\n                  Hard: {stats.doneByDiff.Hard}/{stats.totalByDiff.Hard}\n                </span>\n              )}\n            </div>\n          </div>\n          <div className=\"relative h-2 overflow-hidden rounded-full bg-zinc-200 dark:bg-zinc-700\">\n            {/* Default: solid blue bar */}\n            <div\n              className=\"absolute inset-0 h-full bg-blue-500 transition-opacity duration-500 ease-in-out sm:group-hover:opacity-0 max-sm:hidden\"\n              style={{ width: `${stats.pct}%` }}\n            />\n            {/* Hover: blended difficulty gradient */}\n            <div\n              className=\"absolute inset-0 h-full transition-opacity duration-500 ease-in-out max-sm:opacity-100 sm:opacity-0 sm:group-hover:opacity-100\"\n              style={{\n                width: `${stats.pct}%`,\n                background: (() => {\n                  if (!stats.done) return \"var(--color-green-500)\";\n                  const easyPct = (stats.doneByDiff.Easy / stats.done) * 100;\n                  const medPct = ((stats.doneByDiff.Easy + stats.doneByDiff.Medium) / stats.done) * 100;\n                  return `linear-gradient(90deg, var(--color-green-500) ${Math.max(easyPct - 3, 0)}%, var(--color-yellow-500) ${Math.min(easyPct + 3, medPct - 3)}%, var(--color-yellow-500) ${Math.max(medPct - 3, easyPct + 3)}%, var(--color-red-500) ${medPct + 3}%)`;\n                })(),\n              }}\n            />\n          </div>\n        </div>\n      </div>\n\n      {/* Beginner: group by difficulty → phases */}\n      {difficultyGroups ? (\n        <div className=\"space-y-8\">\n          {difficultyGroups.map((group) => {\n            const diffBorder: Record<string, string> = {\n              Easy: \"border-green-300 dark:border-green-800\",\n              Medium: \"border-yellow-300 dark:border-yellow-800\",\n              Hard: \"border-red-300 dark:border-red-800\",\n            };\n            const diffHeaderBg: Record<string, string> = {\n              Easy: \"bg-green-50 dark:bg-green-950/30\",\n              Medium: \"bg-yellow-50 dark:bg-yellow-950/30\",\n              Hard: \"bg-red-50 dark:bg-red-950/30\",\n            };\n            const diffPill: Record<string, string> = {\n              Easy: \"bg-green-100 text-green-700 dark:bg-green-900/50 dark:text-green-400\",\n              Medium: \"bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-400\",\n              Hard: \"bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-400\",\n            };\n            const diffSubtitle: Record<string, string> = {\n              Easy: \"Build your foundation with these introductory problems\",\n              Medium: \"Medium difficulty problems begin introducing new techniques and algorithms - most of the time you will need to study the optimal solution\",\n              Hard: \"Avoid these unless you have a lot of time - 99.9999% of the time you will have to study the optimal solution\",\n            };\n            const groupPct = group.total > 0 ? Math.round((group.done / group.total) * 100) : 0;\n\n            return (\n              <div key={group.difficulty} className={`overflow-hidden rounded-xl border ${diffBorder[group.difficulty]}`}>\n                {/* Difficulty header */}\n                <div className={`px-4 py-3 ${diffHeaderBg[group.difficulty]}`}>\n                  <div className=\"flex items-center gap-3\">\n                    <span className={`rounded-full px-3 py-1 text-sm font-bold ${diffPill[group.difficulty]}`}>\n                      {group.difficulty}\n                    </span>\n                    <span className=\"text-sm font-medium text-zinc-600 dark:text-zinc-300\">\n                      {group.done}/{group.total} completed ({groupPct}%)\n                    </span>\n                  </div>\n                  {roadmap.id === \"beginner\" && (\n                    <p className=\"mt-1.5 text-sm text-zinc-500 dark:text-zinc-400\">\n                      {diffSubtitle[group.difficulty]}\n                    </p>\n                  )}\n                </div>\n\n                {/* Phases within this difficulty */}\n                <div className=\"relative p-4\">\n                  {group.phases.map((entry, idx) => {\n                    const collapseKey = `${group.difficulty}-${entry.phaseIndex}`;\n                    const isCollapsed = collapsedPhases.has(collapseKey);\n                    const phaseDone = entry.questions.filter((rq) => {\n                      const q = slugToQuestion.get(rq.slug);\n                      return q && completed.has(q.id);\n                    }).length;\n                    const phaseComplete = phaseDone === entry.questions.length;\n\n                    return (\n                      <Fragment key={collapseKey}>\n                        {idx > 0 && (\n                          <div className=\"flex justify-center py-1\">\n                            <div className={`h-6 w-0.5 ${\n                              (() => {\n                                const prevEntry = group.phases[idx - 1];\n                                const prevDone = prevEntry.questions.filter((rq) => {\n                                  const q = slugToQuestion.get(rq.slug);\n                                  return q && completed.has(q.id);\n                                }).length;\n                                return prevDone === prevEntry.questions.length\n                                  ? \"bg-green-400 dark:bg-green-600\"\n                                  : \"bg-zinc-300 dark:bg-zinc-700\";\n                              })()\n                            }`} />\n                          </div>\n                        )}\n\n                        <div className={`rounded-lg border transition-colors ${\n                          phaseComplete\n                            ? \"border-green-300 bg-green-50/50 dark:border-green-800 dark:bg-green-950/30\"\n                            : \"border-zinc-200 dark:border-zinc-800\"\n                        }`}>\n                          <button\n                            onClick={() => togglePhase(collapseKey)}\n                            className={`flex w-full items-center gap-3 px-4 py-3 text-left transition-colors ${\n                              phaseComplete\n                                ? \"hover:bg-green-100/50 dark:hover:bg-green-900/20\"\n                                : \"hover:bg-zinc-50 dark:hover:bg-zinc-900/50\"\n                            }`}\n                          >\n                            <div className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-bold ${\n                              phaseComplete\n                                ? \"bg-green-500 text-white\"\n                                : phaseDone > 0\n                                  ? \"bg-blue-500 text-white\"\n                                  : \"bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300\"\n                            }`}>\n                              {phaseComplete ? <Check className=\"h-4 w-4\" /> : entry.phaseIndex + 1}\n                            </div>\n                            <div className=\"min-w-0 flex-1\">\n                              <div className=\"flex items-center gap-2\">\n                                <span className=\"font-semibold\">{entry.phase.title}</span>\n                                <span className=\"text-xs text-zinc-500\">\n                                  {phaseDone}/{entry.questions.length}\n                                </span>\n                              </div>\n                              {roadmap.id === \"beginner\" && (\n                                <p className=\"mt-0.5 text-xs text-zinc-500\">\n                                  <InlineMarkdown text={\n                                    group.difficulty !== \"Easy\" && entry.phase.mediumDescription\n                                      ? entry.phase.mediumDescription\n                                      : entry.phase.description\n                                  } />\n                                </p>\n                              )}\n                            </div>\n                            {isCollapsed ? (\n                              <ChevronRight className=\"h-4 w-4 shrink-0 text-zinc-400\" />\n                            ) : (\n                              <ChevronDown className=\"h-4 w-4 shrink-0 text-zinc-400\" />\n                            )}\n                          </button>\n\n                          {!isCollapsed && (\n                            <div className=\"border-t border-zinc-200 dark:border-zinc-800\">\n                              {entry.questions.map((rq) => {\n                                const q = slugToQuestion.get(rq.slug);\n                                if (!q) return null;\n                                return renderQuestionRow(q, rq, { showDifficulty: false });\n                              })}\n                            </div>\n                          )}\n                        </div>\n                      </Fragment>\n                    );\n                  })}\n                </div>\n              </div>\n            );\n          })}\n        </div>\n      ) : (\n        /* Experienced / default: flat phases */\n        <div className=\"relative\">\n          {roadmap.phases.map((phase, phaseIndex) => {\n            const isCollapsed = collapsedPhases.has(phaseIndex);\n            const ps = phaseStats[phaseIndex];\n            const phaseComplete = ps.done === ps.total && ps.total > 0;\n\n            return (\n              <Fragment key={phaseIndex}>\n                {phaseIndex > 0 && (\n                  <div className=\"flex justify-center py-1\">\n                    <div\n                      className={`h-6 w-0.5 ${\n                        phaseStats[phaseIndex - 1].done ===\n                          phaseStats[phaseIndex - 1].total &&\n                        phaseStats[phaseIndex - 1].total > 0\n                          ? \"bg-green-400 dark:bg-green-600\"\n                          : \"bg-zinc-300 dark:bg-zinc-700\"\n                      }`}\n                    />\n                  </div>\n                )}\n\n                <div\n                  className={`rounded-lg border transition-colors ${\n                    phaseComplete\n                      ? \"border-green-300 bg-green-50/50 dark:border-green-800 dark:bg-green-950/30\"\n                      : \"border-zinc-200 dark:border-zinc-800\"\n                  }`}\n                >\n                  <button\n                    onClick={() => togglePhase(phaseIndex)}\n                    className={`flex w-full items-center gap-3 px-4 py-3 text-left transition-colors ${\n                      phaseComplete\n                        ? \"hover:bg-green-100/50 dark:hover:bg-green-900/20\"\n                        : \"hover:bg-zinc-50 dark:hover:bg-zinc-900/50\"\n                    }`}\n                  >\n                    <div\n                      className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-bold ${\n                        phaseComplete\n                          ? \"bg-green-500 text-white\"\n                          : ps.done > 0\n                            ? \"bg-blue-500 text-white\"\n                            : \"bg-zinc-200 text-zinc-600 dark:bg-zinc-700 dark:text-zinc-300\"\n                      }`}\n                    >\n                      {phaseComplete ? <Check className=\"h-4 w-4\" /> : phaseIndex + 1}\n                    </div>\n                    <div className=\"min-w-0 flex-1\">\n                      <div className=\"flex items-center gap-2\">\n                        <span className=\"font-semibold\">{phase.title}</span>\n                        <span className=\"text-xs text-zinc-500\">\n                          {ps.done}/{ps.total}\n                        </span>\n                      </div>\n                      <p className=\"mt-0.5 text-xs text-zinc-500\">\n                        <InlineMarkdown text={phase.description} />\n                      </p>\n                    </div>\n                    {isCollapsed ? (\n                      <ChevronRight className=\"h-4 w-4 shrink-0 text-zinc-400\" />\n                    ) : (\n                      <ChevronDown className=\"h-4 w-4 shrink-0 text-zinc-400\" />\n                    )}\n                  </button>\n\n                  {!isCollapsed && (\n                    <div className=\"border-t border-zinc-200 dark:border-zinc-800\">\n                      {phase.questions.map((rq) => {\n                        const q = slugToQuestion.get(rq.slug);\n                        if (!q) return null;\n                        return renderQuestionRow(q, rq);\n                      })}\n                    </div>\n                  )}\n                </div>\n              </Fragment>\n            );\n          })}\n        </div>\n      )}\n\n      {/* Next Steps */}\n      {roadmap.nextSteps && roadmap.nextSteps.length > 0 && (\n        <div className=\"mt-8 overflow-hidden rounded-xl border border-blue-300 dark:border-blue-800\">\n          <div className=\"bg-blue-50 px-4 py-3 dark:bg-blue-950/30\">\n            <div className=\"flex items-center gap-3\">\n              <span className=\"rounded-full bg-blue-100 px-3 py-1 text-sm font-bold text-blue-700 dark:bg-blue-900/50 dark:text-blue-400\">\n                Next Steps\n              </span>\n            </div>\n            <p className=\"mt-1.5 text-sm text-zinc-500 dark:text-zinc-400\">\n              What to do after completing this roadmap\n            </p>\n          </div>\n          <div className=\"space-y-0 divide-y divide-zinc-100 px-4 dark:divide-zinc-800/50\">\n            {roadmap.nextSteps.map((step, i) => (\n              <div key={i} className=\"flex items-start gap-3 py-3 text-sm text-zinc-600 dark:text-zinc-400\">\n                <span className=\"mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-bold text-blue-700 dark:bg-blue-900/50 dark:text-blue-400\">\n                  {i + 1}\n                </span>\n                <span><InlineMarkdown text={step} /></span>\n              </div>\n            ))}\n          </div>\n        </div>\n      )}\n\n      {/* Note Modal - same as QuestionsTable */}\n      {editingNote &&\n        (() => {\n          const saved = notes[editingNote.id] ?? \"\";\n          const hasChanges = editingNote.draft !== saved;\n          const tryDismiss = () => {\n            if (hasChanges) {\n              setEditingNote({ ...editingNote, confirmDiscard: true });\n            } else {\n              setEditingNote(null);\n            }\n          };\n          return (\n            <div\n              className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n              onClick={tryDismiss}\n            >\n              <div\n                className=\"mx-4 w-full max-w-lg rounded-xl border border-zinc-200 bg-white p-6 shadow-xl dark:border-zinc-700 dark:bg-zinc-900\"\n                onClick={(e) => e.stopPropagation()}\n              >\n                {editingNote.confirmDiscard ? (\n                  <>\n                    <h2 className=\"mb-2 text-lg font-semibold\">\n                      Unsaved changes\n                    </h2>\n                    <p className=\"mb-3 text-sm text-zinc-500\">\n                      Your note for{\" \"}\n                      <span className=\"font-medium text-foreground\">\n                        {editingNote.title}\n                      </span>{\" \"}\n                      has been modified but not saved. Would you like to go back\n                      and save your changes, or discard them?\n                    </p>\n                    <div className=\"mb-4 rounded-lg border border-zinc-200 bg-zinc-50 p-3 text-sm dark:border-zinc-700 dark:bg-zinc-800\">\n                      <p className=\"mb-1 text-xs font-medium text-zinc-400\">\n                        Your unsaved note:\n                      </p>\n                      <p className=\"whitespace-pre-wrap break-words text-zinc-600 dark:text-zinc-300\">\n                        {editingNote.draft || (\n                          <span className=\"italic text-zinc-400\">(empty)</span>\n                        )}\n                      </p>\n                    </div>\n                    <div className=\"flex justify-end gap-2\">\n                      <button\n                        onClick={() =>\n                          setEditingNote({\n                            ...editingNote,\n                            confirmDiscard: false,\n                          })\n                        }\n                        className=\"rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n                      >\n                        Keep editing\n                      </button>\n                      <button\n                        onClick={() => setEditingNote(null)}\n                        className=\"rounded-lg bg-red-600 px-4 py-2 text-sm font-medium text-white hover:bg-red-700\"\n                      >\n                        Discard\n                      </button>\n                    </div>\n                  </>\n                ) : (\n                  <>\n                    <h2 className=\"mb-1 text-lg font-semibold\">\n                      {editingNote.title}\n                    </h2>\n                    <p className=\"mb-4 text-sm text-zinc-500\">\n                      Add your notes below\n                    </p>\n                    <textarea\n                      autoFocus\n                      rows={4}\n                      value={editingNote.draft}\n                      onChange={(e) =>\n                        setEditingNote({\n                          ...editingNote,\n                          draft: e.target.value,\n                        })\n                      }\n                      onKeyDown={(e) => {\n                        if (\n                          e.key === \"Enter\" &&\n                          (e.metaKey || e.ctrlKey)\n                        ) {\n                          e.preventDefault();\n                          updateNote(editingNote.id, editingNote.draft);\n                          setEditingNote(null);\n                        }\n                      }}\n                      maxLength={MAX_NOTE_LENGTH}\n                      placeholder=\"Write your notes here...\"\n                      className=\"w-full resize-y rounded-lg border border-zinc-300 bg-transparent px-3 py-2 text-sm break-words focus:border-blue-500 focus:outline-none dark:border-zinc-700\"\n                    />\n                    <div className=\"mt-2 flex items-center justify-between\">\n                      <span className={`text-xs ${editingNote.draft.length >= MAX_NOTE_LENGTH ? \"text-red-500\" : \"text-zinc-400\"}`}>\n                        {editingNote.draft.length.toLocaleString()} / {MAX_NOTE_LENGTH.toLocaleString()} characters\n                      </span>\n                      {hasChanges ? (\n                        <span className=\"text-xs text-amber-600 dark:text-amber-400\">\n                          ⚠ Unsaved changes ·{\" \"}\n                          {navigator.platform?.includes(\"Mac\") ? \"⌘\" : \"Ctrl\"}\n                          +Enter to save\n                        </span>\n                      ) : (\n                        <span className=\"text-xs text-zinc-400\">\n                          {navigator.platform?.includes(\"Mac\") ? \"⌘\" : \"Ctrl\"}\n                          +Enter to save\n                        </span>\n                      )}\n                    </div>\n                    <div className=\"mt-4 flex justify-end gap-2\">\n                      <button\n                        onClick={tryDismiss}\n                        className=\"rounded-lg border border-zinc-300 px-4 py-2 text-sm font-medium hover:bg-zinc-100 dark:border-zinc-700 dark:hover:bg-zinc-800\"\n                      >\n                        Cancel\n                      </button>\n                      <button\n                        onClick={() => {\n                          updateNote(editingNote.id, editingNote.draft);\n                          setEditingNote(null);\n                        }}\n                        className=\"rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700\"\n                      >\n                        Done\n                      </button>\n                    </div>\n                  </>\n                )}\n              </div>\n            </div>\n          );\n        })()}\n    </div>\n  );\n}\n"
  },
  {
    "path": "src/data/questions.json",
    "content": "{\n  \"updated\": \"2026-03-15T14:07:51.507813\",\n  \"data\": [\n    {\n      \"id\": 1,\n      \"title\": \"Two Sum\",\n      \"slug\": \"two-sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 212\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 73\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 43\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 25\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 23\n        },\n        {\n          \"name\": \"J.P. Morgan\",\n          \"slug\": \"jpmorgan\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Accenture\",\n          \"slug\": \"accenture\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Capgemini\",\n          \"slug\": \"capgemini\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Deloitte\",\n          \"slug\": \"deloitte\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Morgan Stanley\",\n          \"slug\": \"morgan-stanley\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"EPAM Systems\",\n          \"slug\": \"epam-systems\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"eBay\",\n          \"slug\": \"ebay\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 2,\n      \"title\": \"Add Two Numbers\",\n      \"slug\": \"add-two-numbers\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Math\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 53\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 21\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 21\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Cisco\",\n          \"slug\": \"cisco\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 3,\n      \"title\": \"Longest Substring Without Repeating Characters\",\n      \"slug\": \"longest-substring-without-repeating-characters\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 37\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 33\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Netflix\",\n          \"slug\": \"netflix\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"AT&T\",\n          \"slug\": \"at-t\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Accolite\",\n          \"slug\": \"accolite\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Morgan Stanley\",\n          \"slug\": \"morgan-stanley\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 4,\n      \"title\": \"Median of Two Sorted Arrays\",\n      \"slug\": \"median-of-two-sorted-arrays\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Divide and Conquer\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 37\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 16\n        },\n        {\n          \"name\": \"Udemy\",\n          \"slug\": \"udemy\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Rippling\",\n          \"slug\": \"rippling\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 5,\n      \"title\": \"Longest Palindromic Substring\",\n      \"slug\": \"longest-palindromic-substring\",\n      \"pattern\": [\n        \"Two Pointers\",\n        \"String\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 27\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Cognizant\",\n          \"slug\": \"cognizant\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ZS Associates\",\n          \"slug\": \"zs-associates\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"HashedIn\",\n          \"slug\": \"hashedin\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 11,\n      \"title\": \"Container With Most Water\",\n      \"slug\": \"container-with-most-water\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Greedy\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 31\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 30\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Flipkart\",\n          \"slug\": \"flipkart\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Capital One\",\n          \"slug\": \"capital-one\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"PayPal\",\n          \"slug\": \"paypal\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 15,\n      \"title\": \"3Sum\",\n      \"slug\": \"3sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 25\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 20\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Qualcomm\",\n          \"slug\": \"qualcomm\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 16,\n      \"title\": \"3Sum Closest\",\n      \"slug\": \"3sum-closest\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Harness\",\n          \"slug\": \"harness\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 17,\n      \"title\": \"Letter Combinations of a Phone Number\",\n      \"slug\": \"letter-combinations-of-a-phone-number\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 19,\n      \"title\": \"Remove Nth Node From End of List\",\n      \"slug\": \"remove-nth-node-from-end-of-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Two Pointers\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Symantec\",\n          \"slug\": \"symantec\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Morgan Stanley\",\n          \"slug\": \"morgan-stanley\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 21,\n      \"title\": \"Merge Two Sorted Lists\",\n      \"slug\": \"merge-two-sorted-lists\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Udemy\",\n          \"slug\": \"udemy\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ServiceNow\",\n          \"slug\": \"servicenow\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Rippling\",\n          \"slug\": \"rippling\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 22,\n      \"title\": \"Generate Parentheses\",\n      \"slug\": \"generate-parentheses\",\n      \"pattern\": [\n        \"String\",\n        \"Dynamic Programming\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 19\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 23,\n      \"title\": \"Merge k Sorted Lists\",\n      \"slug\": \"merge-k-sorted-lists\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Divide and Conquer\",\n        \"Heap (Priority Queue)\",\n        \"Merge Sort\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Rippling\",\n          \"slug\": \"rippling\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Cloudflare\",\n          \"slug\": \"cloudflare\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Deloitte\",\n          \"slug\": \"deloitte\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Flipkart\",\n          \"slug\": \"flipkart\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snowflake\",\n          \"slug\": \"snowflake\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"MongoDB\",\n          \"slug\": \"mongodb\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 24,\n      \"title\": \"Swap Nodes in Pairs\",\n      \"slug\": \"swap-nodes-in-pairs\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 25,\n      \"title\": \"Reverse Nodes in k-Group\",\n      \"slug\": \"reverse-nodes-in-k-group\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 30,\n      \"title\": \"Substring with Concatenation of All Words\",\n      \"slug\": \"substring-with-concatenation-of-all-words\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 33,\n      \"title\": \"Search in Rotated Sorted Array\",\n      \"slug\": \"search-in-rotated-sorted-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"PayPal\",\n          \"slug\": \"paypal\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Grammarly\",\n          \"slug\": \"grammarly\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 37,\n      \"title\": \"Sudoku Solver\",\n      \"slug\": \"sudoku-solver\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Backtracking\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Confluent\",\n          \"slug\": \"confluent\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"eBay\",\n          \"slug\": \"ebay\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 39,\n      \"title\": \"Combination Sum\",\n      \"slug\": \"combination-sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"NetApp\",\n          \"slug\": \"netapp\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 40,\n      \"title\": \"Combination Sum II\",\n      \"slug\": \"combination-sum-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 41,\n      \"title\": \"First Missing Positive\",\n      \"slug\": \"first-missing-positive\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 42,\n      \"title\": \"Trapping Rain Water\",\n      \"slug\": \"trapping-rain-water\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Dynamic Programming\",\n        \"Stack\",\n        \"Monotonic Stack\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 34\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 28\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Snowflake\",\n          \"slug\": \"snowflake\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"ServiceNow\",\n          \"slug\": \"servicenow\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Hive\",\n          \"slug\": \"hive\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Flipkart\",\n          \"slug\": \"flipkart\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"DE Shaw\",\n          \"slug\": \"de-shaw\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Samsung\",\n          \"slug\": \"samsung\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 46,\n      \"title\": \"Permutations\",\n      \"slug\": \"permutations\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 47,\n      \"title\": \"Permutations II\",\n      \"slug\": \"permutations-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 48,\n      \"title\": \"Rotate Image\",\n      \"slug\": \"rotate-image\",\n      \"pattern\": [\n        \"Array\",\n        \"Math\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Roblox\",\n          \"slug\": \"roblox\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nutanix\",\n          \"slug\": \"nutanix\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"SIG\",\n          \"slug\": \"sig\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 51,\n      \"title\": \"N-Queens\",\n      \"slug\": \"n-queens\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 53,\n      \"title\": \"Maximum Subarray\",\n      \"slug\": \"maximum-subarray\",\n      \"pattern\": [\n        \"Array\",\n        \"Divide and Conquer\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 18\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Upstart\",\n          \"slug\": \"upstart\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meesho\",\n          \"slug\": \"meesho\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 54,\n      \"title\": \"Spiral Matrix\",\n      \"slug\": \"spiral-matrix\",\n      \"pattern\": [\n        \"Array\",\n        \"Matrix\",\n        \"Simulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Epic Systems\",\n          \"slug\": \"epic-systems\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"PhonePe\",\n          \"slug\": \"phonepe\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 55,\n      \"title\": \"Jump Game\",\n      \"slug\": \"jump-game\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\",\n        \"Greedy\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TomTom\",\n          \"slug\": \"tomtom\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Agoda\",\n          \"slug\": \"agoda\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 56,\n      \"title\": \"Merge Intervals\",\n      \"slug\": \"merge-intervals\",\n      \"pattern\": [\n        \"Array\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 22\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 20\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 18\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Atlassian\",\n          \"slug\": \"atlassian\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"CleverTap\",\n          \"slug\": \"clevertap\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Grammarly\",\n          \"slug\": \"grammarly\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Okta\",\n          \"slug\": \"okta\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 57,\n      \"title\": \"Insert Interval\",\n      \"slug\": \"insert-interval\",\n      \"pattern\": [\n        \"Array\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 61,\n      \"title\": \"Rotate List\",\n      \"slug\": \"rotate-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Two Pointers\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 62,\n      \"title\": \"Unique Paths\",\n      \"slug\": \"unique-paths\",\n      \"pattern\": [\n        \"Math\",\n        \"Dynamic Programming\",\n        \"Combinatorics\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 70,\n      \"title\": \"Climbing Stairs\",\n      \"slug\": \"climbing-stairs\",\n      \"pattern\": [\n        \"Math\",\n        \"Dynamic Programming\",\n        \"Memoization\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Media.net\",\n          \"slug\": \"medianet\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Accenture\",\n          \"slug\": \"accenture\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Grammarly\",\n          \"slug\": \"grammarly\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 73,\n      \"title\": \"Set Matrix Zeroes\",\n      \"slug\": \"set-matrix-zeroes\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nykaa\",\n          \"slug\": \"nykaa\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ZScaler\",\n          \"slug\": \"zscaler\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 74,\n      \"title\": \"Search a 2D Matrix\",\n      \"slug\": \"search-a-2d-matrix\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 75,\n      \"title\": \"Sort Colors\",\n      \"slug\": \"sort-colors\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Agoda\",\n          \"slug\": \"agoda\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 76,\n      \"title\": \"Minimum Window Substring\",\n      \"slug\": \"minimum-window-substring\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Lyft\",\n          \"slug\": \"lyft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snap\",\n          \"slug\": \"snapchat\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 77,\n      \"title\": \"Combinations\",\n      \"slug\": \"combinations\",\n      \"pattern\": [\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 78,\n      \"title\": \"Subsets\",\n      \"slug\": \"subsets\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 79,\n      \"title\": \"Word Search\",\n      \"slug\": \"word-search\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Backtracking\",\n        \"Depth-First Search\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 16\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Faire\",\n          \"slug\": \"faire\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Atlassian\",\n          \"slug\": \"atlassian\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 81,\n      \"title\": \"Search in Rotated Sorted Array II\",\n      \"slug\": \"search-in-rotated-sorted-array-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 83,\n      \"title\": \"Remove Duplicates from Sorted List\",\n      \"slug\": \"remove-duplicates-from-sorted-list\",\n      \"pattern\": [\n        \"Linked List\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 90,\n      \"title\": \"Subsets II\",\n      \"slug\": \"subsets-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 91,\n      \"title\": \"Decode Ways\",\n      \"slug\": \"decode-ways\",\n      \"pattern\": [\n        \"String\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oscar Health\",\n          \"slug\": \"oscar-health\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 92,\n      \"title\": \"Reverse Linked List II\",\n      \"slug\": \"reverse-linked-list-ii\",\n      \"pattern\": [\n        \"Linked List\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 98,\n      \"title\": \"Validate Binary Search Tree\",\n      \"slug\": \"validate-binary-search-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Search Tree\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Millennium\",\n          \"slug\": \"millennium\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 100,\n      \"title\": \"Same Tree\",\n      \"slug\": \"same-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 102,\n      \"title\": \"Binary Tree Level Order Traversal\",\n      \"slug\": \"binary-tree-level-order-traversal\",\n      \"pattern\": [\n        \"Tree\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 103,\n      \"title\": \"Binary Tree Zigzag Level Order Traversal\",\n      \"slug\": \"binary-tree-zigzag-level-order-traversal\",\n      \"pattern\": [\n        \"Tree\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Sigmoid\",\n          \"slug\": \"sigmoid\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 104,\n      \"title\": \"Maximum Depth of Binary Tree\",\n      \"slug\": \"maximum-depth-of-binary-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Accenture\",\n          \"slug\": \"accenture\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Avito\",\n          \"slug\": \"avito\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 105,\n      \"title\": \"Construct Binary Tree from Preorder and Inorder Traversal\",\n      \"slug\": \"construct-binary-tree-from-preorder-and-inorder-traversal\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Divide and Conquer\",\n        \"Tree\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 107,\n      \"title\": \"Binary Tree Level Order Traversal II\",\n      \"slug\": \"binary-tree-level-order-traversal-ii\",\n      \"pattern\": [\n        \"Tree\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 111,\n      \"title\": \"Minimum Depth of Binary Tree\",\n      \"slug\": \"minimum-depth-of-binary-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 112,\n      \"title\": \"Path Sum\",\n      \"slug\": \"path-sum\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 113,\n      \"title\": \"Path Sum II\",\n      \"slug\": \"path-sum-ii\",\n      \"pattern\": [\n        \"Backtracking\",\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 121,\n      \"title\": \"Best Time to Buy and Sell Stock\",\n      \"slug\": \"best-time-to-buy-and-sell-stock\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 37\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 25\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 18\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Agoda\",\n          \"slug\": \"agoda\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Comcast\",\n          \"slug\": \"comcast\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Cognizant\",\n          \"slug\": \"cognizant\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"J.P. Morgan\",\n          \"slug\": \"jpmorgan\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Sigmoid\",\n          \"slug\": \"sigmoid\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Citadel\",\n          \"slug\": \"citadel\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 124,\n      \"title\": \"Binary Tree Maximum Path Sum\",\n      \"slug\": \"binary-tree-maximum-path-sum\",\n      \"pattern\": [\n        \"Dynamic Programming\",\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"DoorDash\",\n          \"slug\": \"doordash\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 128,\n      \"title\": \"Longest Consecutive Sequence\",\n      \"slug\": \"longest-consecutive-sequence\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Union-Find\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 24\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 131,\n      \"title\": \"Palindrome Partitioning\",\n      \"slug\": \"palindrome-partitioning\",\n      \"pattern\": [\n        \"String\",\n        \"Dynamic Programming\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 134,\n      \"title\": \"Gas Station\",\n      \"slug\": \"gas-station\",\n      \"pattern\": [\n        \"Array\",\n        \"Greedy\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"CME Group\",\n          \"slug\": \"cme-group\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Accolite\",\n          \"slug\": \"accolite\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 136,\n      \"title\": \"Single Number\",\n      \"slug\": \"single-number\",\n      \"pattern\": [\n        \"Array\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Cognizant\",\n          \"slug\": \"cognizant\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 139,\n      \"title\": \"Word Break\",\n      \"slug\": \"word-break\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"String\",\n        \"Dynamic Programming\",\n        \"Trie\",\n        \"Memoization\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zeta\",\n          \"slug\": \"zeta\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"MongoDB\",\n          \"slug\": \"mongodb\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 141,\n      \"title\": \"Linked List Cycle\",\n      \"slug\": \"linked-list-cycle\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"Linked List\",\n        \"Two Pointers\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Palo Alto Networks\",\n          \"slug\": \"palo-alto-networks\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 142,\n      \"title\": \"Linked List Cycle II\",\n      \"slug\": \"linked-list-cycle-ii\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"Linked List\",\n        \"Two Pointers\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 143,\n      \"title\": \"Reorder List\",\n      \"slug\": \"reorder-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Two Pointers\",\n        \"Stack\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Morgan Stanley\",\n          \"slug\": \"morgan-stanley\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 148,\n      \"title\": \"Sort List\",\n      \"slug\": \"sort-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Two Pointers\",\n        \"Divide and Conquer\",\n        \"Sorting\",\n        \"Merge Sort\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 152,\n      \"title\": \"Maximum Product Subarray\",\n      \"slug\": \"maximum-product-subarray\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 153,\n      \"title\": \"Find Minimum in Rotated Sorted Array\",\n      \"slug\": \"find-minimum-in-rotated-sorted-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 162,\n      \"title\": \"Find Peak Element\",\n      \"slug\": \"find-peak-element\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IXL\",\n          \"slug\": \"ixl\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 169,\n      \"title\": \"Majority Element\",\n      \"slug\": \"majority-element\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Divide and Conquer\",\n        \"Sorting\",\n        \"Counting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 25\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 198,\n      \"title\": \"House Robber\",\n      \"slug\": \"house-robber\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Expedia\",\n          \"slug\": \"expedia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ByteDance\",\n          \"slug\": \"bytedance\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Gusto\",\n          \"slug\": \"gusto\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 199,\n      \"title\": \"Binary Tree Right Side View\",\n      \"slug\": \"binary-tree-right-side-view\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 200,\n      \"title\": \"Number of Islands\",\n      \"slug\": \"number-of-islands\",\n      \"pattern\": [\n        \"Array\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Union-Find\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Anduril\",\n          \"slug\": \"anduril\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Snap\",\n          \"slug\": \"snapchat\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"CrowdStrike\",\n          \"slug\": \"crowdstrike\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Capital One\",\n          \"slug\": \"capital-one\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ByteDance\",\n          \"slug\": \"bytedance\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Autodesk\",\n          \"slug\": \"autodesk\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 203,\n      \"title\": \"Remove Linked List Elements\",\n      \"slug\": \"remove-linked-list-elements\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 206,\n      \"title\": \"Reverse Linked List\",\n      \"slug\": \"reverse-linked-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 17\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Qualcomm\",\n          \"slug\": \"qualcomm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Paytm\",\n          \"slug\": \"paytm\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 207,\n      \"title\": \"Course Schedule\",\n      \"slug\": \"course-schedule\",\n      \"pattern\": [\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Graph Theory\",\n        \"Topological Sort\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Roblox\",\n          \"slug\": \"roblox\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"ByteDance\",\n          \"slug\": \"bytedance\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Cloudflare\",\n          \"slug\": \"cloudflare\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 208,\n      \"title\": \"Implement Trie (Prefix Tree)\",\n      \"slug\": \"implement-trie-prefix-tree\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Design\",\n        \"Trie\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"MongoDB\",\n          \"slug\": \"mongodb\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 209,\n      \"title\": \"Minimum Size Subarray Sum\",\n      \"slug\": \"minimum-size-subarray-sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Sliding Window\",\n        \"Prefix Sum\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 210,\n      \"title\": \"Course Schedule II\",\n      \"slug\": \"course-schedule-ii\",\n      \"pattern\": [\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Graph Theory\",\n        \"Topological Sort\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Roblox\",\n          \"slug\": \"roblox\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Netflix\",\n          \"slug\": \"netflix\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Snowflake\",\n          \"slug\": \"snowflake\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Anduril\",\n          \"slug\": \"anduril\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Moloco\",\n          \"slug\": \"moloco\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Arista Networks\",\n          \"slug\": \"arista-networks\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Citadel\",\n          \"slug\": \"citadel\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snap\",\n          \"slug\": \"snapchat\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 212,\n      \"title\": \"Word Search II\",\n      \"slug\": \"word-search-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Backtracking\",\n        \"Trie\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Zoom\",\n          \"slug\": \"zoom\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snowflake\",\n          \"slug\": \"snowflake\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Cisco\",\n          \"slug\": \"cisco\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 213,\n      \"title\": \"House Robber II\",\n      \"slug\": \"house-robber-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 215,\n      \"title\": \"Kth Largest Element in an Array\",\n      \"slug\": \"kth-largest-element-in-an-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Divide and Conquer\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Quickselect\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 19\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Cerner\",\n          \"slug\": \"cerner\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 216,\n      \"title\": \"Combination Sum III\",\n      \"slug\": \"combination-sum-iii\",\n      \"pattern\": [\n        \"Array\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 217,\n      \"title\": \"Contains Duplicate\",\n      \"slug\": \"contains-duplicate\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 18\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Capgemini\",\n          \"slug\": \"capgemini\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 226,\n      \"title\": \"Invert Binary Tree\",\n      \"slug\": \"invert-binary-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 230,\n      \"title\": \"Kth Smallest Element in a BST\",\n      \"slug\": \"kth-smallest-element-in-a-bst\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Search Tree\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 234,\n      \"title\": \"Palindrome Linked List\",\n      \"slug\": \"palindrome-linked-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Two Pointers\",\n        \"Stack\",\n        \"Recursion\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"eBay\",\n          \"slug\": \"ebay\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"SAP\",\n          \"slug\": \"sap\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"AMD\",\n          \"slug\": \"amd\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 235,\n      \"title\": \"Lowest Common Ancestor of a Binary Search Tree\",\n      \"slug\": \"lowest-common-ancestor-of-a-binary-search-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Search Tree\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 236,\n      \"title\": \"Lowest Common Ancestor of a Binary Tree\",\n      \"slug\": \"lowest-common-ancestor-of-a-binary-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 13\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Atlassian\",\n          \"slug\": \"atlassian\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"SAP\",\n          \"slug\": \"sap\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 238,\n      \"title\": \"Product of Array Except Self\",\n      \"slug\": \"product-of-array-except-self\",\n      \"pattern\": [\n        \"Array\",\n        \"Prefix Sum\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Asana\",\n          \"slug\": \"asana\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 239,\n      \"title\": \"Sliding Window Maximum\",\n      \"slug\": \"sliding-window-maximum\",\n      \"pattern\": [\n        \"Array\",\n        \"Queue\",\n        \"Sliding Window\",\n        \"Heap (Priority Queue)\",\n        \"Monotonic Queue\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"J.P. Morgan\",\n          \"slug\": \"jpmorgan\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Aurora\",\n          \"slug\": \"aurora\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Expedia\",\n          \"slug\": \"expedia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Citadel\",\n          \"slug\": \"citadel\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ServiceNow\",\n          \"slug\": \"servicenow\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Booking.com\",\n          \"slug\": \"bookingcom\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 240,\n      \"title\": \"Search a 2D Matrix II\",\n      \"slug\": \"search-a-2d-matrix-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Divide and Conquer\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zomato\",\n          \"slug\": \"zomato\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 252,\n      \"title\": \"Meeting Rooms\",\n      \"slug\": \"meeting-rooms\",\n      \"pattern\": [\n        \"Array\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 253,\n      \"title\": \"Meeting Rooms II\",\n      \"slug\": \"meeting-rooms-ii\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Greedy\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Prefix Sum\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"J.P. Morgan\",\n          \"slug\": \"jpmorgan\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Atlassian\",\n          \"slug\": \"atlassian\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snap\",\n          \"slug\": \"snapchat\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Compass\",\n          \"slug\": \"compass\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Whatnot\",\n          \"slug\": \"whatnot\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 254,\n      \"title\": \"Factor Combinations\",\n      \"slug\": \"factor-combinations\",\n      \"pattern\": [\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": true,\n      \"companies\": []\n    },\n    {\n      \"id\": 257,\n      \"title\": \"Binary Tree Paths\",\n      \"slug\": \"binary-tree-paths\",\n      \"pattern\": [\n        \"String\",\n        \"Backtracking\",\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Capital One\",\n          \"slug\": \"capital-one\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 261,\n      \"title\": \"Graph Valid Tree\",\n      \"slug\": \"graph-valid-tree\",\n      \"pattern\": [\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Union-Find\",\n        \"Graph Theory\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Snowflake\",\n          \"slug\": \"snowflake\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 268,\n      \"title\": \"Missing Number\",\n      \"slug\": \"missing-number\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Math\",\n        \"Binary Search\",\n        \"Bit Manipulation\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Arista Networks\",\n          \"slug\": \"arista-networks\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 269,\n      \"title\": \"Alien Dictionary\",\n      \"slug\": \"alien-dictionary\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Graph Theory\",\n        \"Topological Sort\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Airbnb\",\n          \"slug\": \"airbnb\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nuro\",\n          \"slug\": \"nuro\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 283,\n      \"title\": \"Move Zeroes\",\n      \"slug\": \"move-zeroes\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"EPAM Systems\",\n          \"slug\": \"epam-systems\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Qualcomm\",\n          \"slug\": \"qualcomm\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Sigmoid\",\n          \"slug\": \"sigmoid\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LTI\",\n          \"slug\": \"lti\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Accenture\",\n          \"slug\": \"accenture\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"AMD\",\n          \"slug\": \"amd\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 287,\n      \"title\": \"Find the Duplicate Number\",\n      \"slug\": \"find-the-duplicate-number\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Binary Search\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nike\",\n          \"slug\": \"nike\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 295,\n      \"title\": \"Find Median from Data Stream\",\n      \"slug\": \"find-median-from-data-stream\",\n      \"pattern\": [\n        \"Two Pointers\",\n        \"Design\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Data Stream\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Citadel\",\n          \"slug\": \"citadel\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Anduril\",\n          \"slug\": \"anduril\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"PayPal\",\n          \"slug\": \"paypal\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IXL\",\n          \"slug\": \"ixl\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Intuit\",\n          \"slug\": \"intuit\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Spotify\",\n          \"slug\": \"spotify\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 297,\n      \"title\": \"Serialize and Deserialize Binary Tree\",\n      \"slug\": \"serialize-and-deserialize-binary-tree\",\n      \"pattern\": [\n        \"String\",\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Design\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Hive\",\n          \"slug\": \"hive\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 300,\n      \"title\": \"Longest Increasing Subsequence\",\n      \"slug\": \"longest-increasing-subsequence\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Block\",\n          \"slug\": \"square\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"PayPal\",\n          \"slug\": \"paypal\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Huawei\",\n          \"slug\": \"huawei\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 303,\n      \"title\": \"Range Sum Query - Immutable\",\n      \"slug\": \"range-sum-query-immutable\",\n      \"pattern\": [\n        \"Array\",\n        \"Design\",\n        \"Prefix Sum\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 309,\n      \"title\": \"Best Time to Buy and Sell Stock with Cooldown\",\n      \"slug\": \"best-time-to-buy-and-sell-stock-with-cooldown\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"AQR Capital Management\",\n          \"slug\": \"aqr-capital-management-llc\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 310,\n      \"title\": \"Minimum Height Trees\",\n      \"slug\": \"minimum-height-trees\",\n      \"pattern\": [\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Graph Theory\",\n        \"Topological Sort\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 320,\n      \"title\": \"Generalized Abbreviation\",\n      \"slug\": \"generalized-abbreviation\",\n      \"pattern\": [\n        \"String\",\n        \"Backtracking\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": true,\n      \"companies\": []\n    },\n    {\n      \"id\": 322,\n      \"title\": \"Coin Change\",\n      \"slug\": \"coin-change\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\",\n        \"Breadth-First Search\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Pinterest\",\n          \"slug\": \"pinterest\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Datadog\",\n          \"slug\": \"datadog\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Accolite\",\n          \"slug\": \"accolite\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Mastercard\",\n          \"slug\": \"mastercard\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 323,\n      \"title\": \"Number of Connected Components in an Undirected Graph\",\n      \"slug\": \"number-of-connected-components-in-an-undirected-graph\",\n      \"pattern\": [\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Union-Find\",\n        \"Graph Theory\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"General Motors\",\n          \"slug\": \"general-motors\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 327,\n      \"title\": \"Count of Range Sum\",\n      \"slug\": \"count-of-range-sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Divide and Conquer\",\n        \"Binary Indexed Tree\",\n        \"Segment Tree\",\n        \"Merge Sort\",\n        \"Ordered Set\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 328,\n      \"title\": \"Odd Even Linked List\",\n      \"slug\": \"odd-even-linked-list\",\n      \"pattern\": [\n        \"Linked List\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 338,\n      \"title\": \"Counting Bits\",\n      \"slug\": \"counting-bits\",\n      \"pattern\": [\n        \"Dynamic Programming\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 347,\n      \"title\": \"Top K Frequent Elements\",\n      \"slug\": \"top-k-frequent-elements\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Divide and Conquer\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Bucket Sort\",\n        \"Counting\",\n        \"Quickselect\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Snap\",\n          \"slug\": \"snapchat\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"eBay\",\n          \"slug\": \"ebay\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"PayPal\",\n          \"slug\": \"paypal\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snowflake\",\n          \"slug\": \"snowflake\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Rippling\",\n          \"slug\": \"rippling\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Disney\",\n          \"slug\": \"disney\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 358,\n      \"title\": \"Rearrange String k Distance Apart\",\n      \"slug\": \"rearrange-string-k-distance-apart\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Greedy\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Counting\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": true,\n      \"companies\": []\n    },\n    {\n      \"id\": 373,\n      \"title\": \"Find K Pairs with Smallest Sums\",\n      \"slug\": \"find-k-pairs-with-smallest-sums\",\n      \"pattern\": [\n        \"Array\",\n        \"Heap (Priority Queue)\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 377,\n      \"title\": \"Combination Sum IV\",\n      \"slug\": \"combination-sum-iv\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 378,\n      \"title\": \"Kth Smallest Element in a Sorted Matrix\",\n      \"slug\": \"kth-smallest-element-in-a-sorted-matrix\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 392,\n      \"title\": \"Is Subsequence\",\n      \"slug\": \"is-subsequence\",\n      \"pattern\": [\n        \"Two Pointers\",\n        \"String\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 416,\n      \"title\": \"Partition Equal Subset Sum\",\n      \"slug\": \"partition-equal-subset-sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 417,\n      \"title\": \"Pacific Atlantic Water Flow\",\n      \"slug\": \"pacific-atlantic-water-flow\",\n      \"pattern\": [\n        \"Array\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Matrix\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Flipkart\",\n          \"slug\": \"flipkart\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 424,\n      \"title\": \"Longest Repeating Character Replacement\",\n      \"slug\": \"longest-repeating-character-replacement\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"ServiceNow\",\n          \"slug\": \"servicenow\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 425,\n      \"title\": \"Word Squares\",\n      \"slug\": \"word-squares\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Backtracking\",\n        \"Trie\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": true,\n      \"companies\": []\n    },\n    {\n      \"id\": 435,\n      \"title\": \"Non-overlapping Intervals\",\n      \"slug\": \"non-overlapping-intervals\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\",\n        \"Greedy\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Verkada\",\n          \"slug\": \"verkada\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Grammarly\",\n          \"slug\": \"grammarly\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 437,\n      \"title\": \"Path Sum III\",\n      \"slug\": \"path-sum-iii\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 442,\n      \"title\": \"Find All Duplicates in an Array\",\n      \"slug\": \"find-all-duplicates-in-an-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"CleverTap\",\n          \"slug\": \"clevertap\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 448,\n      \"title\": \"Find All Numbers Disappeared in an Array\",\n      \"slug\": \"find-all-numbers-disappeared-in-an-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 451,\n      \"title\": \"Sort Characters By Frequency\",\n      \"slug\": \"sort-characters-by-frequency\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Bucket Sort\",\n        \"Counting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 452,\n      \"title\": \"Minimum Number of Arrows to Burst Balloons\",\n      \"slug\": \"minimum-number-of-arrows-to-burst-balloons\",\n      \"pattern\": [\n        \"Array\",\n        \"Greedy\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 472,\n      \"title\": \"Concatenated Words\",\n      \"slug\": \"concatenated-words\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Dynamic Programming\",\n        \"Depth-First Search\",\n        \"Trie\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 480,\n      \"title\": \"Sliding Window Median\",\n      \"slug\": \"sliding-window-median\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Sliding Window\",\n        \"Heap (Priority Queue)\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Snap\",\n          \"slug\": \"snapchat\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 494,\n      \"title\": \"Target Sum\",\n      \"slug\": \"target-sum\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\",\n        \"Backtracking\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 567,\n      \"title\": \"Permutation in String\",\n      \"slug\": \"permutation-in-string\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"Two Pointers\",\n        \"String\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Databricks\",\n          \"slug\": \"databricks\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 572,\n      \"title\": \"Subtree of Another Tree\",\n      \"slug\": \"subtree-of-another-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"String Matching\",\n        \"Binary Tree\",\n        \"Hash Function\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 617,\n      \"title\": \"Merge Two Binary Trees\",\n      \"slug\": \"merge-two-binary-trees\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 621,\n      \"title\": \"Task Scheduler\",\n      \"slug\": \"task-scheduler\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Greedy\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Counting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"micro1\",\n          \"slug\": \"micro1\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 632,\n      \"title\": \"Smallest Range Covering Elements from K Lists\",\n      \"slug\": \"smallest-range-covering-elements-from-k-lists\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Greedy\",\n        \"Sliding Window\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 637,\n      \"title\": \"Average of Levels in Binary Tree\",\n      \"slug\": \"average-of-levels-in-binary-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 642,\n      \"title\": \"Design Search Autocomplete System\",\n      \"slug\": \"design-search-autocomplete-system\",\n      \"pattern\": [\n        \"String\",\n        \"Depth-First Search\",\n        \"Design\",\n        \"Trie\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Data Stream\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Pinterest\",\n          \"slug\": \"pinterest\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Roblox\",\n          \"slug\": \"roblox\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 643,\n      \"title\": \"Maximum Average Subarray I\",\n      \"slug\": \"maximum-average-subarray-i\",\n      \"pattern\": [\n        \"Array\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 647,\n      \"title\": \"Palindromic Substrings\",\n      \"slug\": \"palindromic-substrings\",\n      \"pattern\": [\n        \"Two Pointers\",\n        \"String\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Pure Storage\",\n          \"slug\": \"pure-storage\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Capital One\",\n          \"slug\": \"capital-one\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Intuit\",\n          \"slug\": \"intuit\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"HCL\",\n          \"slug\": \"hcl\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 654,\n      \"title\": \"Maximum Binary Tree\",\n      \"slug\": \"maximum-binary-tree\",\n      \"pattern\": [\n        \"Array\",\n        \"Divide and Conquer\",\n        \"Stack\",\n        \"Tree\",\n        \"Monotonic Stack\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 658,\n      \"title\": \"Find K Closest Elements\",\n      \"slug\": \"find-k-closest-elements\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Binary Search\",\n        \"Sliding Window\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 662,\n      \"title\": \"Maximum Width of Binary Tree\",\n      \"slug\": \"maximum-width-of-binary-tree\",\n      \"pattern\": [\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 673,\n      \"title\": \"Number of Longest Increasing Subsequence\",\n      \"slug\": \"number-of-longest-increasing-subsequence\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\",\n        \"Binary Indexed Tree\",\n        \"Segment Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 698,\n      \"title\": \"Partition to K Equal Sum Subsets\",\n      \"slug\": \"partition-to-k-equal-sum-subsets\",\n      \"pattern\": [\n        \"Array\",\n        \"Dynamic Programming\",\n        \"Backtracking\",\n        \"Bit Manipulation\",\n        \"Memoization\",\n        \"Bitmask\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 713,\n      \"title\": \"Subarray Product Less Than K\",\n      \"slug\": \"subarray-product-less-than-k\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\",\n        \"Sliding Window\",\n        \"Prefix Sum\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 720,\n      \"title\": \"Longest Word in Dictionary\",\n      \"slug\": \"longest-word-in-dictionary\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"String\",\n        \"Trie\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 745,\n      \"title\": \"Find Smallest Letter Greater Than Target\",\n      \"slug\": \"find-smallest-letter-greater-than-target\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 746,\n      \"title\": \"Prefix and Suffix Search\",\n      \"slug\": \"prefix-and-suffix-search\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"String\",\n        \"Design\",\n        \"Trie\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 761,\n      \"title\": \"Employee Free Time\",\n      \"slug\": \"employee-free-time\",\n      \"pattern\": [\n        \"Array\",\n        \"Sweep Line\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 778,\n      \"title\": \"Reorganize String\",\n      \"slug\": \"reorganize-string\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Greedy\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Counting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 792,\n      \"title\": \"Binary Search\",\n      \"slug\": \"binary-search\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 12\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 800,\n      \"title\": \"Letter Case Permutation\",\n      \"slug\": \"letter-case-permutation\",\n      \"pattern\": [\n        \"String\",\n        \"Backtracking\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 855,\n      \"title\": \"Count Unique Characters of All Substrings of a Given String\",\n      \"slug\": \"count-unique-characters-of-all-substrings-of-a-given-string\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 874,\n      \"title\": \"Backspace String Compare\",\n      \"slug\": \"backspace-string-compare\",\n      \"pattern\": [\n        \"Two Pointers\",\n        \"String\",\n        \"Stack\",\n        \"Simulation\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 882,\n      \"title\": \"Peak Index in a Mountain Array\",\n      \"slug\": \"peak-index-in-a-mountain-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Binary Search\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 893,\n      \"title\": \"All Nodes Distance K in Binary Tree\",\n      \"slug\": \"all-nodes-distance-k-in-binary-tree\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"Tree\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Binary Tree\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Salesforce\",\n          \"slug\": \"salesforce\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 908,\n      \"title\": \"Middle of the Linked List\",\n      \"slug\": \"middle-of-the-linked-list\",\n      \"pattern\": [\n        \"Linked List\",\n        \"Two Pointers\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 931,\n      \"title\": \"Maximum Frequency Stack\",\n      \"slug\": \"maximum-frequency-stack\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"Stack\",\n        \"Design\",\n        \"Ordered Set\"\n      ],\n      \"difficulty\": \"Hard\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 940,\n      \"title\": \"Fruit Into Baskets\",\n      \"slug\": \"fruit-into-baskets\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"Sliding Window\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        }\n      ]\n    },\n    {\n      \"id\": 1014,\n      \"title\": \"K Closest Points to Origin\",\n      \"slug\": \"k-closest-points-to-origin\",\n      \"pattern\": [\n        \"Array\",\n        \"Math\",\n        \"Divide and Conquer\",\n        \"Geometry\",\n        \"Sorting\",\n        \"Heap (Priority Queue)\",\n        \"Quickselect\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Asana\",\n          \"slug\": \"asana\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 1019,\n      \"title\": \"Squares of a Sorted Array\",\n      \"slug\": \"squares-of-a-sorted-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Uber\",\n          \"slug\": \"uber\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 1028,\n      \"title\": \"Interval List Intersections\",\n      \"slug\": \"interval-list-intersections\",\n      \"pattern\": [\n        \"Array\",\n        \"Two Pointers\",\n        \"Sweep Line\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Verkada\",\n          \"slug\": \"verkada\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 1075,\n      \"title\": \"Index Pairs of a String\",\n      \"slug\": \"index-pairs-of-a-string\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Trie\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": true,\n      \"companies\": []\n    },\n    {\n      \"id\": 2132,\n      \"title\": \"Convert 1D Array Into 2D Array\",\n      \"slug\": \"convert-1d-array-into-2d-array\",\n      \"pattern\": [\n        \"Array\",\n        \"Matrix\",\n        \"Simulation\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": []\n    },\n    {\n      \"id\": 242,\n      \"title\": \"Valid Anagram\",\n      \"slug\": \"valid-anagram\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"String\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 14\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"EPAM Systems\",\n          \"slug\": \"epam-systems\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Nokia\",\n          \"slug\": \"nokia\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 49,\n      \"title\": \"Group Anagrams\",\n      \"slug\": \"group-anagrams\",\n      \"pattern\": [\n        \"Array\",\n        \"Hash Table\",\n        \"String\",\n        \"Sorting\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 19\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 7\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Okta\",\n          \"slug\": \"okta\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"J.P. Morgan\",\n          \"slug\": \"jpmorgan\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Adobe\",\n          \"slug\": \"adobe\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Goldman Sachs\",\n          \"slug\": \"goldman-sachs\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Walmart Labs\",\n          \"slug\": \"walmart-labs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"EPAM Systems\",\n          \"slug\": \"epam-systems\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Visa\",\n          \"slug\": \"visa\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Anduril\",\n          \"slug\": \"anduril\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Palo Alto Networks\",\n          \"slug\": \"palo-alto-networks\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 271,\n      \"title\": \"Encode and Decode Strings\",\n      \"slug\": \"encode-and-decode-strings\",\n      \"pattern\": [\n        \"Array\",\n        \"String\",\n        \"Design\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": true,\n      \"companies\": [\n        {\n          \"name\": \"Udemy\",\n          \"slug\": \"udemy\",\n          \"frequency\": 9\n        },\n        {\n          \"name\": \"OpenAI\",\n          \"slug\": \"openai\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"CrowdStrike\",\n          \"slug\": \"crowdstrike\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 125,\n      \"title\": \"Valid Palindrome\",\n      \"slug\": \"valid-palindrome\",\n      \"pattern\": [\n        \"Two Pointers\",\n        \"String\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"CleverTap\",\n          \"slug\": \"clevertap\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Yandex\",\n          \"slug\": \"yandex\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"HCL\",\n          \"slug\": \"hcl\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"tcs\",\n          \"slug\": \"tcs\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 20,\n      \"title\": \"Valid Parentheses\",\n      \"slug\": \"valid-parentheses\",\n      \"pattern\": [\n        \"String\",\n        \"Stack\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 22\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 21\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 15\n        },\n        {\n          \"name\": \"Oracle\",\n          \"slug\": \"oracle\",\n          \"frequency\": 11\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Cerner\",\n          \"slug\": \"cerner\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Zulily\",\n          \"slug\": \"zulily\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 10\n        },\n        {\n          \"name\": \"Udemy\",\n          \"slug\": \"udemy\",\n          \"frequency\": 8\n        },\n        {\n          \"name\": \"LinkedIn\",\n          \"slug\": \"linkedin\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Infosys\",\n          \"slug\": \"infosys\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Tripadvisor\",\n          \"slug\": \"tripadvisor\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Nvidia\",\n          \"slug\": \"nvidia\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Tesla\",\n          \"slug\": \"tesla\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"TikTok\",\n          \"slug\": \"tiktok\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Zoho\",\n          \"slug\": \"zoho\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Epic Systems\",\n          \"slug\": \"epic-systems\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Intuit\",\n          \"slug\": \"intuit\",\n          \"frequency\": 3\n        }\n      ]\n    },\n    {\n      \"id\": 211,\n      \"title\": \"Design Add and Search Words Data Structure\",\n      \"slug\": \"design-add-and-search-words-data-structure\",\n      \"pattern\": [\n        \"String\",\n        \"Depth-First Search\",\n        \"Design\",\n        \"Trie\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Datadog\",\n          \"slug\": \"datadog\",\n          \"frequency\": 6\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Apple\",\n          \"slug\": \"apple\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 133,\n      \"title\": \"Clone Graph\",\n      \"slug\": \"clone-graph\",\n      \"pattern\": [\n        \"Hash Table\",\n        \"Depth-First Search\",\n        \"Breadth-First Search\",\n        \"Graph Theory\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 2\n        },\n        {\n          \"name\": \"CrowdStrike\",\n          \"slug\": \"crowdstrike\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 191,\n      \"title\": \"Number of 1 Bits\",\n      \"slug\": \"number-of-1-bits\",\n      \"pattern\": [\n        \"Divide and Conquer\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 190,\n      \"title\": \"Reverse Bits\",\n      \"slug\": \"reverse-bits\",\n      \"pattern\": [\n        \"Divide and Conquer\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Easy\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Bloomberg\",\n          \"slug\": \"bloomberg\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 371,\n      \"title\": \"Sum of Two Integers\",\n      \"slug\": \"sum-of-two-integers\",\n      \"pattern\": [\n        \"Math\",\n        \"Bit Manipulation\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Microsoft\",\n          \"slug\": \"microsoft\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"IBM\",\n          \"slug\": \"ibm\",\n          \"frequency\": 2\n        }\n      ]\n    },\n    {\n      \"id\": 1250,\n      \"title\": \"Longest Common Subsequence\",\n      \"slug\": \"longest-common-subsequence\",\n      \"pattern\": [\n        \"String\",\n        \"Dynamic Programming\"\n      ],\n      \"difficulty\": \"Medium\",\n      \"premium\": false,\n      \"companies\": [\n        {\n          \"name\": \"Google\",\n          \"slug\": \"google\",\n          \"frequency\": 5\n        },\n        {\n          \"name\": \"Meta\",\n          \"slug\": \"facebook\",\n          \"frequency\": 4\n        },\n        {\n          \"name\": \"Amazon\",\n          \"slug\": \"amazon\",\n          \"frequency\": 3\n        },\n        {\n          \"name\": \"micro1\",\n          \"slug\": \"micro1\",\n          \"frequency\": 2\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/data/roadmaps.ts",
    "content": "export interface RoadmapQuestion {\n  slug: string;\n  note: string;\n}\n\nexport interface RoadmapPhase {\n  title: string;\n  description: string;\n  mediumDescription?: string;\n  questions: RoadmapQuestion[];\n}\n\nexport interface Roadmap {\n  id: string;\n  name: string;\n  description: string;\n  phases: RoadmapPhase[];\n  nextSteps?: string[];\n}\n\nexport const beginnerRoadmap: Roadmap = {\n  id: \"beginner\",\n  name: \"Beginner Roadmap\",\n  description:\n    \"A structured path for those new to coding interviews.\\n\\n**Sean's Guidance:**\\n\" +\n    \"1. Choose a programming language that you **feel most comfortable with** - for me that is Java. If you're new to programming, I would recommend **Python**.\\n\" +\n    \"2. **Spend no more than 30 minutes** trying to think of *any* solution, even if it's brute force. You want to try applying what you know, and if stuck, then expand your skillset by **studying the solution until you're able to fully explain it to another individual**.\\n\" +\n    \"3. **Expect to struggle at first** (*for my first time, it took at least a month to begin improving*).\\n\" + \n    \"4. **Do not memorize solutions** - this does not work in interviews when asked follow-up questions (*I speak from experience*).\\n\" +\n    \"5. Don't be afraid to **ask an AI agent to explain the solution** to you - *it's a great alternative to watching a video solution*.\\n\" +\n    \"6. Reference the **Helpful Tips** tab for which tools to consider pulling out of your toolkit when solving a problem - *don't use a screwdriver when you need a hammer!*\",\n  phases: [\n    {\n      title: \"Phase 1: Arrays & Hash Tables\",\n      description:\n        \"Learn the two most fundamental data structures. Arrays teach you iteration and indexing; hash tables give you O(1) lookups.\",\n      mediumDescription:\n        \"Apply array and hash table skills to trickier problems that require multi-pass strategies or in-place techniques.\",\n      questions: [\n        {\n          slug: \"contains-duplicate\",\n          note: \"Add each number to a Set as you go. If it's already in the Set, you found a duplicate.\",\n        },\n        {\n          slug: \"two-sum\",\n          note: \"For each number, check if (target - number) is already in a hash map. If yes, you found the pair. If not, store the current number.\",\n        },\n        {\n          slug: \"find-all-numbers-disappeared-in-an-array\",\n          note: \"Since values are 1 to n, use each value as an index to mark which numbers are present. The unmarked positions are the missing numbers.\",\n        },\n        {\n          slug: \"missing-number\",\n          note: \"Calculate the expected sum of 0 to n, then subtract the actual array sum. The difference is the missing number. Or use a Set.\",\n        },\n        {\n          slug: \"majority-element\",\n          note: \"Track a candidate and a counter. Add 1 when you see the candidate, subtract 1 otherwise. When the counter hits 0, pick a new candidate. The majority element always wins.\",\n        },\n        {\n          slug: \"product-of-array-except-self\",\n          note: \"Two-pass approach: build left products, then multiply by right products in reverse. No division needed.\",\n        },\n        {\n          slug: \"find-all-duplicates-in-an-array\",\n          note: \"Similar to disappeared numbers - negate the value at index abs(num)-1. If already negative, it's a duplicate.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 2: Two Pointers\",\n      description:\n        \"Two pointers let you solve problems in O(n) by scanning from both ends or using a slow/fast pointer.\",\n      mediumDescription:\n        \"Combine sorting with two-pointer scans to handle duplicates, multi-element sums, and greedy boundary decisions.\",\n      questions: [\n        {\n          slug: \"move-zeroes\",\n          note: \"Keep one pointer where the next non-zero should go. Walk through the array and swap each non-zero element to that position.\",\n        },\n        {\n          slug: \"squares-of-a-sorted-array\",\n          note: \"The largest squares are at either end of the array. Compare both ends, place the bigger square at the back of the result, and move that pointer inward.\",\n        },\n        {\n          slug: \"backspace-string-compare\",\n          note: \"Process each string using a stack: push letters, pop on '#'. Then compare the two final stacks. Or walk backwards through both strings, skipping characters after '#'.\",\n        },\n        {\n          slug: \"3sum\",\n          note: \"Sort first, then for each element use two pointers on the remainder. Skip duplicates to avoid repeats.\",\n        },\n        {\n          slug: \"3sum-closest\",\n          note: \"Same two-pointer approach as 3Sum but track the closest sum. Update when |sum - target| improves.\",\n        },\n        {\n          slug: \"container-with-most-water\",\n          note: \"Start pointers at both ends. The shorter line limits height, so move that pointer inward.\",\n        },\n        {\n          slug: \"sort-colors\",\n          note: \"Dutch national flag: three pointers (low, mid, high). Swap 0s left and 2s right.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 3: Sliding Window\",\n      description:\n        \"Sliding window optimizes brute-force subarray/substring problems from O(n²) to O(n) by maintaining a moving window.\",\n      mediumDescription:\n        \"Handle variable-width windows with shrink conditions, frequency maps, and constraints like \\\"at most k\\\" distinct elements.\",\n      questions: [\n        {\n          slug: \"maximum-average-subarray-i\",\n          note: \"Slide a window of size k across the array. As you move right, add the new element and remove the leftmost one. Track the best sum.\",\n        },\n        {\n          slug: \"is-subsequence\",\n          note: \"Walk through the longer string with a pointer for each string. When characters match, advance both pointers. Otherwise only advance the longer string's pointer.\",\n        },\n        {\n          slug: \"longest-substring-without-repeating-characters\",\n          note: \"Expand right pointer, shrink left when duplicates found. Use a Set or map to track window contents.\",\n        },\n        {\n          slug: \"minimum-size-subarray-sum\",\n          note: \"Expand window until sum ≥ target, then shrink from the left while still valid. Track minimum length.\",\n        },\n        {\n          slug: \"longest-repeating-character-replacement\",\n          note: \"Window is valid when length - maxFreq ≤ k. Track character frequencies in the window.\",\n        },\n        {\n          slug: \"permutation-in-string\",\n          note: \"Fixed-size sliding window matching s1's length. Compare character frequency maps of window and s1.\",\n        },\n        {\n          slug: \"fruit-into-baskets\",\n          note: \"Sliding window with at most 2 distinct elements. Shrink window when you exceed 2 fruit types.\",\n        },\n        {\n          slug: \"subarray-product-less-than-k\",\n          note: \"Expand right, shrink left while product ≥ k. Each valid window of size w contributes w subarrays.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 4: Linked Lists\",\n      description:\n        \"Master pointer manipulation with linked lists. These problems build intuition for in-place data structure operations.\",\n      mediumDescription:\n        \"Chain together multiple linked list techniques (find middle, reverse, merge) within a single problem.\",\n      questions: [\n        {\n          slug: \"reverse-linked-list\",\n          note: \"Walk through the list, and at each node point it backward instead of forward. Keep track of the previous node so you can redirect the link.\",\n        },\n        {\n          slug: \"middle-of-the-linked-list\",\n          note: \"Use two pointers: one moves 1 step at a time, the other moves 2 steps. When the fast one reaches the end, the slow one is at the middle.\",\n        },\n        {\n          slug: \"linked-list-cycle\",\n          note: \"Use two pointers moving at different speeds. If there's a loop, the fast one will eventually catch up to the slow one. If not, the fast one reaches the end.\",\n        },\n        {\n          slug: \"merge-two-sorted-lists\",\n          note: \"Create a placeholder node to start building the result. Compare the fronts of both lists, attach the smaller one, and keep going. Attach whatever's left at the end.\",\n        },\n        {\n          slug: \"remove-linked-list-elements\",\n          note: \"Add a placeholder node before the head (handles the case where the head itself needs removal). Walk through and skip any node whose value matches the target.\",\n        },\n        {\n          slug: \"remove-duplicates-from-sorted-list\",\n          note: \"Because the list is sorted, duplicates sit next to each other. Walk through and whenever the next node has the same value, skip over it.\",\n        },\n        {\n          slug: \"palindrome-linked-list\",\n          note: \"Find the middle of the list, reverse the second half, then compare the first half with the reversed second half node by node.\",\n        },\n        {\n          slug: \"remove-nth-node-from-end-of-list\",\n          note: \"Two pointers with n-gap: advance fast n steps first, then move both. When fast ends, slow is at target.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 5: Binary Search\",\n      description:\n        \"Binary search halves the search space each iteration for O(log n) time. Master the template: left, right, mid boundaries.\",\n      mediumDescription:\n        \"Apply binary search to modified arrays - rotated, 2D, or peak-finding - where you must decide which half to discard.\",\n      questions: [\n        {\n          slug: \"binary-search\",\n          note: \"Start with the full range. Check the middle element: if it matches, you're done. If the target is smaller, search the left half. If larger, search the right half.\",\n        },\n        {\n          slug: \"find-smallest-letter-greater-than-target\",\n          note: \"Binary search for the first letter that comes after the target. Since the letters wrap around, if nothing is greater, return the very first letter.\",\n        },\n        {\n          slug: \"peak-index-in-a-mountain-array\",\n          note: \"Binary search on the slope: if arr[mid] < arr[mid+1], peak is to the right. Otherwise, to the left.\",\n        },\n        {\n          slug: \"search-in-rotated-sorted-array\",\n          note: \"Determine which half is sorted, then check if target lies in the sorted half. Adjust boundaries accordingly.\",\n        },\n        {\n          slug: \"find-minimum-in-rotated-sorted-array\",\n          note: \"Compare mid with right boundary. If arr[mid] > arr[right], minimum is in the right half.\",\n        },\n        {\n          slug: \"search-a-2d-matrix\",\n          note: \"Treat the 2D matrix as a flat sorted array. Convert 1D index to (row, col) with division and modulo.\",\n        },\n        {\n          slug: \"find-peak-element\",\n          note: \"Binary search: move toward the side where the neighbor is larger. A peak must exist on that side.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 6: Trees - DFS & BFS\",\n      description:\n        \"Trees combine recursion with data structures. DFS (preorder, inorder, postorder) and BFS (level-order) are the core traversals.\",\n      mediumDescription:\n        \"Tackle tree problems that require maintaining global state, enforcing BST constraints, or processing nodes level by level.\",\n      questions: [\n        {\n          slug: \"maximum-depth-of-binary-tree\",\n          note: \"Recursively ask each subtree for its depth. The answer is 1 + the larger of the two. An empty tree has depth 0.\",\n        },\n        {\n          slug: \"minimum-depth-of-binary-tree\",\n          note: \"Similar to max depth, but find the shortest path to a leaf. Watch out: if a node only has one child, you must follow that child (a null child isn't a leaf).\",\n        },\n        {\n          slug: \"same-tree\",\n          note: \"Compare the two trees node by node. If both are empty, they match. If one is empty or values differ, they don't. Then check left and right children the same way.\",\n        },\n        {\n          slug: \"invert-binary-tree\",\n          note: \"At every node, swap its left and right children. Then do the same for each child. Think of it as mirroring the tree.\",\n        },\n        {\n          slug: \"path-sum\",\n          note: \"Subtract each node's value from the target as you walk down. When you reach a leaf, check if the remaining target is exactly the leaf's value.\",\n        },\n        {\n          slug: \"subtree-of-another-tree\",\n          note: \"Visit every node in the main tree. At each one, check if the tree starting there looks exactly like the target tree (reuse the same-tree logic).\",\n        },\n        {\n          slug: \"binary-tree-paths\",\n          note: \"Build a path string as you travel from root toward leaves. Each time you reach a leaf, save that path to your results.\",\n        },\n        {\n          slug: \"merge-two-binary-trees\",\n          note: \"Walk both trees together. When both nodes exist, add their values. When only one exists, use that node as-is.\",\n        },\n        {\n          slug: \"average-of-levels-in-binary-tree\",\n          note: \"Process the tree level by level using a queue. At each level, add up all the values and divide by how many nodes are on that level.\",\n        },\n        {\n          slug: \"binary-tree-level-order-traversal\",\n          note: \"BFS with a queue. Process all nodes at the current level (queue length) before moving to the next level.\",\n        },\n        {\n          slug: \"validate-binary-search-tree\",\n          note: \"DFS with min/max bounds. Each node must be within (min, max). Left child gets (min, node), right gets (node, max).\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 7: Sorting & Intervals\",\n      description:\n        \"Sorting unlocks many techniques. Interval problems almost always start with sorting by start time.\",\n      mediumDescription:\n        \"Merge, insert, and remove overlapping intervals. These problems layer greedy decisions on top of sorted input.\",\n      questions: [\n        {\n          slug: \"meeting-rooms\",\n          note: \"Sort the meetings by start time. Then check each pair of consecutive meetings - if one starts before the previous one ends, you have a conflict.\",\n        },\n        {\n          slug: \"merge-intervals\",\n          note: \"Sort by start time. Iterate and merge overlapping intervals by extending the end time.\",\n        },\n        {\n          slug: \"insert-interval\",\n          note: \"Add all intervals before the new one, merge overlapping ones, then add the remaining intervals.\",\n        },\n        {\n          slug: \"non-overlapping-intervals\",\n          note: \"Sort by end time (greedy). Count intervals that overlap with the last kept interval - those must be removed.\",\n        },\n        {\n          slug: \"interval-list-intersections\",\n          note: \"Two pointers on two sorted lists. The intersection is [max of starts, min of ends]. Advance the one that ends first.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 9: Graphs - BFS & DFS\",\n      description:\n        \"Build on tree traversals. Graphs add cycles and multiple paths, requiring visited tracking.\",\n      questions: [\n        {\n          slug: \"number-of-islands\",\n          note: \"Iterate grid cells. When you find '1', BFS/DFS to mark the entire island as visited. Count the number of BFS/DFS calls.\",\n        },\n        {\n          slug: \"pacific-atlantic-water-flow\",\n          note: \"BFS/DFS from ocean borders inward (reverse flow). Cells reachable from both oceans are in the answer.\",\n        },\n        {\n          slug: \"graph-valid-tree\",\n          note: \"A valid tree has exactly n-1 edges and is fully connected. Use BFS/DFS or Union-Find to verify.\",\n        },\n        {\n          slug: \"number-of-connected-components-in-an-undirected-graph\",\n          note: \"Count connected components with BFS/DFS or Union-Find. Each unvisited node starts a new component.\",\n        },\n        {\n          slug: \"course-schedule\",\n          note: \"Detect cycles in a directed graph. Use DFS with 3 states (unvisited, in-progress, done) or BFS topological sort.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 10: Heaps & Priority Queues\",\n      description:\n        \"Heaps efficiently maintain the min or max element. Essential for 'top K' and streaming problems.\",\n      questions: [\n        {\n          slug: \"kth-largest-element-in-an-array\",\n          note: \"Use a min-heap of size k. After processing all elements, the top of the heap is the kth largest.\",\n        },\n        {\n          slug: \"top-k-frequent-elements\",\n          note: \"Count frequencies with a map, then use a min-heap of size k to keep the top k frequent elements.\",\n        },\n        {\n          slug: \"k-closest-points-to-origin\",\n          note: \"Use a max-heap of size k. Compare squared distances (no need for sqrt). Replace when closer point found.\",\n        },\n        {\n          slug: \"meeting-rooms-ii\",\n          note: \"Sort by start time. Use a min-heap of end times. If earliest ending room is free, reuse it; otherwise add a new room.\",\n        },\n        {\n          slug: \"kth-smallest-element-in-a-sorted-matrix\",\n          note: \"Min-heap approach: start with first column, expand right. Or use binary search on the value range.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 10: Matrix Traversal\",\n      description:\n        \"Apply array and graph techniques to 2D grids. Row/column math and directional traversal are key skills.\",\n      mediumDescription:\n        \"Navigate matrices with boundary tracking, in-place marking, and coordinate transformations like transpose and spiral order.\",\n      questions: [\n        {\n          slug: \"convert-1d-array-into-2d-array\",\n          note: \"First check if the total elements fit into m×n. Then fill the 2D array row by row, pulling elements from the 1D array in order.\",\n        },\n        {\n          slug: \"set-matrix-zeroes\",\n          note: \"Use first row/column as markers. Two passes: mark which rows/cols need zeroing, then apply.\",\n        },\n        {\n          slug: \"spiral-matrix\",\n          note: \"Track four boundaries (top, bottom, left, right). Traverse right→down→left→up, shrinking boundaries each loop.\",\n        },\n        {\n          slug: \"rotate-image\",\n          note: \"Transpose the matrix (swap [i][j] with [j][i]), then reverse each row. This gives a 90° clockwise rotation.\",\n        },\n      ],\n    },\n    {\n      title: \"Phase 11: Prefix Sums\",\n      description:\n        \"Prefix sums let you compute range sums in O(1) after O(n) preprocessing. A powerful technique for subarray problems.\",\n      mediumDescription:\n        \"Extend the prefix idea to products. Build left and right prefix arrays to compute results without division.\",\n      questions: [\n        {\n          slug: \"range-sum-query-immutable\",\n          note: \"Pre-compute a running total array. To get the sum of any range, subtract the running total at the start from the running total at the end.\",\n        },\n      ],\n    },\n  ],\n  nextSteps: [\n    \"**Re-solve problems that gave you trouble** - if a question stumped you, redo it until the approach clicks. Repetition is how patterns become second nature.\",\n    \"Remember to **study solutions using an AI agent/YouTube video if you get stuck** - I still have to do this occassionally for some questions!\",\n    \"Move onto the **Experienced Roadmap** to solidify your understanding of each pattern across all difficulty levels.\",\n    \"Complete additional questions from the **All Questions** tab - filter by pattern or difficulty to target your weak areas.\",\n  ],\n};\n\nexport const experiencedRoadmap: Roadmap = {\n  id: \"experienced\",\n  name: \"Experienced\",\n  description:\n    \"Originally shared on [Blind.com](https://www.teamblind.com/post/new-year-gift-curated-list-of-top-75-leetcode-questions-to-save-your-time-oam1oreu) by [Yangshun Tay](https://www.techinterviewhandbook.org/), the Blind 75 problemset represents the highest-value questions for coding interview preparation.\\n\\n**Sean's Guidance:**\\n\" +\n    \"1. If you're here, you are familiar with technical interviews and the patterns you need to know. **Pick a language** and begin working through all Easy problems to build confidence.\\n\" +\n    \"2. If you get stuck on a problem for **more than 15 minutes**, consult the **Helpful Tips** tab and proceed to study the solution using an AI agent/YouTube video.\\n\" +\n    \"3. Once you have completed all 75 questions, I would recommend **reattempting ones that gave you trouble** before moving onto company-specific lists from [Leetcode Premium](https://leetcode.com/subscribe/), [InterviewDB](https://www.interviewdb.io/get-started), or [1point3acres](https://www.1point3acres.com/interview/thread/1167709).\\n\" +\n    \"4. Ensure to leverage free **mock interviews** on [Pramp](https://www.pramp.com/#/) to reacclimate to brain fog/interview anxiety - *you want to fail as many times practicing and not during an interview*.\\n\" +\n    \"5. Schedule interviews with companies that are **not your first choice** before interviewing with your goal companies - the practice will drastically help.\\n\",\n  phases: [\n    {\n      title: \"Arrays & Hashing\",\n      description:\n        \"Core building blocks. Every interview starts here.\",\n      mediumDescription:\n        \"Apply hash maps and frequency counting to solve grouping and sequence problems efficiently.\",\n      questions: [\n        {\n          slug: \"two-sum\",\n          note: \"Hash map: store num → index. For each element, check if complement exists. O(n) time.\",\n        },\n        {\n          slug: \"contains-duplicate\",\n          note: \"Set insertion check. If the element already exists in the Set, return true. O(n) time, O(n) space.\",\n        },\n        {\n          slug: \"valid-anagram\",\n          note: \"Count character frequencies with a hash map or array of 26. Compare counts. O(n) time.\",\n        },\n        {\n          slug: \"group-anagrams\",\n          note: \"Sort each word to get a key, or use character frequency tuple as key. Group by key in a hash map.\",\n        },\n        {\n          slug: \"encode-and-decode-strings\",\n          note: \"Prefix each string with its length and a delimiter (e.g., '5#hello'). Decode by reading length first.\",\n        },\n        {\n          slug: \"product-of-array-except-self\",\n          note: \"Left/right prefix products. O(n) time, O(1) extra space if using the output array.\",\n        },\n        {\n          slug: \"longest-consecutive-sequence\",\n          note: \"HashSet + only start counting from sequence starts (no num-1 in set). O(n).\",\n        },\n      ],\n    },\n    {\n      title: \"Two Pointers\",\n      description:\n        \"Scan from opposite ends or use slow/fast for O(n) solutions.\",\n      mediumDescription:\n        \"Combine sorting with two-pointer scans to handle multi-element sums and greedy boundary decisions.\",\n      questions: [\n        {\n          slug: \"valid-palindrome\",\n          note: \"Two pointers from both ends. Skip non-alphanumeric characters. Compare lowercase values.\",\n        },\n        {\n          slug: \"3sum\",\n          note: \"Sort + fix one element + two-pointer scan on the rest. Skip duplicates carefully.\",\n        },\n        {\n          slug: \"container-with-most-water\",\n          note: \"Greedy two pointers from both ends. Move the shorter line inward.\",\n        },\n      ],\n    },\n    {\n      title: \"Sliding Window\",\n      description:\n        \"Optimize subarray/substring problems from O(n²) to O(n).\",\n      mediumDescription:\n        \"Handle variable-width windows with shrink conditions, frequency maps, and complex matching criteria.\",\n      questions: [\n        {\n          slug: \"best-time-to-buy-and-sell-stock\",\n          note: \"Track minimum price seen so far. At each step, check if selling now gives max profit.\",\n        },\n        {\n          slug: \"longest-substring-without-repeating-characters\",\n          note: \"Expand right, shrink left on duplicate. Use a map storing last seen index for O(n).\",\n        },\n        {\n          slug: \"longest-repeating-character-replacement\",\n          note: \"Window valid when len - maxFreq ≤ k. No need to decrease maxFreq when shrinking.\",\n        },\n        {\n          slug: \"minimum-window-substring\",\n          note: \"Expand until all chars covered, then shrink to find minimum. Track 'formed' count vs required.\",\n        },\n      ],\n    },\n    {\n      title: \"Stack\",\n      description:\n        \"LIFO processing for matching pairs and nested structures.\",\n      questions: [\n        {\n          slug: \"valid-parentheses\",\n          note: \"Push opening brackets. On closing bracket, check if top matches. Stack must be empty at the end.\",\n        },\n      ],\n    },\n    {\n      title: \"Binary Search\",\n      description:\n        \"Halve the search space for O(log n). Know the sorted-rotated pattern.\",\n      questions: [\n        {\n          slug: \"search-in-rotated-sorted-array\",\n          note: \"Determine which half is sorted. Check if target is in the sorted half to decide direction.\",\n        },\n        {\n          slug: \"find-minimum-in-rotated-sorted-array\",\n          note: \"Compare mid with right. If mid > right, min is in right half. O(log n).\",\n        },\n      ],\n    },\n    {\n      title: \"Linked Lists\",\n      description:\n        \"Pointer manipulation and in-place operations.\",\n      mediumDescription:\n        \"Chain together multiple linked list techniques (find middle, reverse, merge) within a single problem.\",\n      questions: [\n        {\n          slug: \"reverse-linked-list\",\n          note: \"Iterative: prev/curr/next. Three pointer dance at each step. O(n) time, O(1) space.\",\n        },\n        {\n          slug: \"merge-two-sorted-lists\",\n          note: \"Dummy head + compare-and-append. Handle remaining nodes at the end.\",\n        },\n        {\n          slug: \"linked-list-cycle\",\n          note: \"Floyd's: slow moves 1, fast moves 2. Meeting proves cycle exists.\",\n        },\n        {\n          slug: \"reorder-list\",\n          note: \"Find middle → reverse second half → interleave. Three classic techniques in one problem.\",\n        },\n        {\n          slug: \"remove-nth-node-from-end-of-list\",\n          note: \"Two pointers with n-node gap. When fast reaches end, slow is at the target.\",\n        },\n        {\n          slug: \"merge-k-sorted-lists\",\n          note: \"Min-heap with one node from each list. Pop smallest, push its next. O(N log k).\",\n        },\n      ],\n    },\n    {\n      title: \"Trees\",\n      description:\n        \"Recursive thinking and traversal patterns.\",\n      mediumDescription:\n        \"Tackle tree problems that require maintaining global state, enforcing BST constraints, or processing nodes level by level.\",\n      questions: [\n        {\n          slug: \"invert-binary-tree\",\n          note: \"Swap children at each node, recurse. Top-down or bottom-up both work.\",\n        },\n        {\n          slug: \"maximum-depth-of-binary-tree\",\n          note: \"DFS: 1 + max(left, right). Base case: null → 0.\",\n        },\n        {\n          slug: \"same-tree\",\n          note: \"Recursive comparison. Both null=true, one null=false, values differ=false.\",\n        },\n        {\n          slug: \"subtree-of-another-tree\",\n          note: \"For each node in the main tree, check if the subtree rooted there matches. Reuse same-tree logic.\",\n        },\n        {\n          slug: \"lowest-common-ancestor-of-a-binary-search-tree\",\n          note: \"Exploit BST property: if both values < node, go left. Both > node, go right. Otherwise, this is the LCA.\",\n        },\n        {\n          slug: \"binary-tree-level-order-traversal\",\n          note: \"BFS queue. Process all nodes at current level (queue.length snapshot) before next level.\",\n        },\n        {\n          slug: \"validate-binary-search-tree\",\n          note: \"DFS with (min, max) bounds. Left child: (min, node.val), right: (node.val, max).\",\n        },\n        {\n          slug: \"kth-smallest-element-in-a-bst\",\n          note: \"In-order traversal gives sorted order. Stop at the kth element. O(H+k) time.\",\n        },\n        {\n          slug: \"construct-binary-tree-from-preorder-and-inorder-traversal\",\n          note: \"Preorder gives root. Find root in inorder to split left/right subtrees. Use a hashmap for O(1) lookup.\",\n        },\n        {\n          slug: \"binary-tree-maximum-path-sum\",\n          note: \"DFS returning max single-path sum. At each node, consider: node, node+left, node+right, node+left+right.\",\n        },\n        {\n          slug: \"serialize-and-deserialize-binary-tree\",\n          note: \"BFS or preorder DFS with null markers. Use delimiter between values for deserialization.\",\n        },\n      ],\n    },\n    {\n      title: \"Tries\",\n      description:\n        \"Prefix trees for efficient string operations.\",\n      questions: [\n        {\n          slug: \"implement-trie-prefix-tree\",\n          note: \"TrieNode with children map + isEnd flag. Insert/search/startsWith all follow child pointers.\",\n        },\n        {\n          slug: \"design-add-and-search-words-data-structure\",\n          note: \"Trie with DFS for '.' wildcard. On '.', recurse into all children. Otherwise follow the exact child.\",\n        },\n        {\n          slug: \"word-search-ii\",\n          note: \"Build trie from words, then DFS on the board. Prune trie branches to optimize. Much faster than checking each word separately.\",\n        },\n      ],\n    },\n    {\n      title: \"Heap / Priority Queue\",\n      description:\n        \"Efficiently track min/max in dynamic datasets.\",\n      questions: [\n        {\n          slug: \"top-k-frequent-elements\",\n          note: \"Bucket sort by frequency: O(n). Or min-heap of size k: O(n log k).\",\n        },\n        {\n          slug: \"find-median-from-data-stream\",\n          note: \"Two heaps: max-heap for lower half, min-heap for upper half. Balance sizes. Median from tops.\",\n        },\n      ],\n    },\n    {\n      title: \"Graphs\",\n      description:\n        \"BFS, DFS, Union-Find, and topological sort.\",\n      questions: [\n        {\n          slug: \"number-of-islands\",\n          note: \"DFS/BFS from each '1', mark visited. Count the number of traversals started.\",\n        },\n        {\n          slug: \"clone-graph\",\n          note: \"BFS or DFS with a hash map from original node → cloned node. Clone neighbors recursively, reuse if already cloned.\",\n        },\n        {\n          slug: \"pacific-atlantic-water-flow\",\n          note: \"Reverse BFS/DFS from ocean borders. Cells reachable from both = intersection of two sets.\",\n        },\n        {\n          slug: \"course-schedule\",\n          note: \"Cycle detection in directed graph. BFS topological sort (Kahn's) or DFS with 3-color states.\",\n        },\n        {\n          slug: \"graph-valid-tree\",\n          note: \"n-1 edges + connected = tree. Use Union-Find or BFS/DFS.\",\n        },\n        {\n          slug: \"number-of-connected-components-in-an-undirected-graph\",\n          note: \"Union-Find or DFS. Count roots/traversals.\",\n        },\n        {\n          slug: \"alien-dictionary\",\n          note: \"Build directed graph from adjacent word comparisons. Topological sort gives the ordering.\",\n        },\n      ],\n    },\n    {\n      title: \"Dynamic Programming\",\n      description:\n        \"Break problems into overlapping subproblems with optimal substructure.\",\n      mediumDescription:\n        \"Apply DP to strings, sequences, and grid problems. Identify the recurrence relation and optimize space.\",\n      questions: [\n        {\n          slug: \"climbing-stairs\",\n          note: \"dp[i] = dp[i-1] + dp[i-2]. Fibonacci pattern. O(n) time, O(1) space with two variables.\",\n        },\n        {\n          slug: \"house-robber\",\n          note: \"dp[i] = max(dp[i-1], dp[i-2] + nums[i]). Rob or skip each house.\",\n        },\n        {\n          slug: \"house-robber-ii\",\n          note: \"Circular array: run House Robber twice - once excluding first house, once excluding last. Take the max.\",\n        },\n        {\n          slug: \"longest-palindromic-substring\",\n          note: \"Expand around center for each position. Check both odd and even length palindromes. O(n²) time.\",\n        },\n        {\n          slug: \"palindromic-substrings\",\n          note: \"Expand around each center (odd + even). Count all valid expansions. O(n²) time.\",\n        },\n        {\n          slug: \"decode-ways\",\n          note: \"dp[i] depends on single digit (1-9) and two digits (10-26). Similar to climbing stairs with constraints.\",\n        },\n        {\n          slug: \"coin-change\",\n          note: \"dp[amount] = min(dp[amount], dp[amount-coin] + 1) for each coin. Bottom-up, init dp[0]=0.\",\n        },\n        {\n          slug: \"maximum-product-subarray\",\n          note: \"Track both max and min product at each position (negatives can flip). Update with current element.\",\n        },\n        {\n          slug: \"word-break\",\n          note: \"dp[i] = true if dp[j] is true and s[j..i] is in the dictionary, for any j < i.\",\n        },\n        {\n          slug: \"longest-increasing-subsequence\",\n          note: \"O(n²): dp[i] = max(dp[j]+1) for all j<i where nums[j]<nums[i]. O(n log n): patience sorting with binary search.\",\n        },\n        {\n          slug: \"longest-common-subsequence\",\n          note: \"2D DP: if chars match, dp[i][j] = dp[i-1][j-1] + 1. Otherwise max(dp[i-1][j], dp[i][j-1]). O(m·n).\",\n        },\n        {\n          slug: \"unique-paths\",\n          note: \"dp[i][j] = dp[i-1][j] + dp[i][j-1]. Can optimize to 1D array.\",\n        },\n        {\n          slug: \"combination-sum-iv\",\n          note: \"dp[target] = sum of dp[target - num] for each num. Order matters - this is a permutation count.\",\n        },\n      ],\n    },\n    {\n      title: \"Greedy\",\n      description:\n        \"Make locally optimal choices that lead to globally optimal solutions.\",\n      questions: [\n        {\n          slug: \"maximum-subarray\",\n          note: \"Kadane's algorithm: extend current subarray or start fresh. Track global max. O(n).\",\n        },\n        {\n          slug: \"jump-game\",\n          note: \"Track the farthest reachable index. If current index exceeds it, return false.\",\n        },\n      ],\n    },\n    {\n      title: \"Intervals\",\n      description:\n        \"Sort by start or end time, then merge or sweep.\",\n      questions: [\n        {\n          slug: \"insert-interval\",\n          note: \"Add non-overlapping before, merge overlapping, add remaining after.\",\n        },\n        {\n          slug: \"merge-intervals\",\n          note: \"Sort by start. Merge if current.start ≤ prev.end. Extend prev.end = max(prev.end, current.end).\",\n        },\n        {\n          slug: \"non-overlapping-intervals\",\n          note: \"Greedy: sort by end time. Remove intervals that overlap with the last kept one.\",\n        },\n        {\n          slug: \"meeting-rooms\",\n          note: \"Sort by start time. If any meeting starts before the previous ends, return false.\",\n        },\n        {\n          slug: \"meeting-rooms-ii\",\n          note: \"Min-heap of end times. If earliest room is free (end ≤ start), reuse; else allocate new.\",\n        },\n      ],\n    },\n    {\n      title: \"Matrix\",\n      description:\n        \"Matrix transformations and spatial reasoning.\",\n      questions: [\n        {\n          slug: \"set-matrix-zeroes\",\n          note: \"Use first row/column as markers. Two passes: mark which rows/cols need zeroing, then apply.\",\n        },\n        {\n          slug: \"spiral-matrix\",\n          note: \"Track four boundaries (top, bottom, left, right). Traverse right→down→left→up, shrinking each loop.\",\n        },\n        {\n          slug: \"rotate-image\",\n          note: \"Transpose the matrix (swap [i][j] with [j][i]), then reverse each row. 90° clockwise rotation.\",\n        },\n        {\n          slug: \"word-search\",\n          note: \"DFS from each cell. Mark visited, explore 4 directions, unmark on backtrack. O(m·n·4^L).\",\n        },\n      ],\n    },\n    {\n      title: \"Bit Manipulation\",\n      description:\n        \"Use bitwise operations for O(1) space tricks and efficient counting.\",\n      questions: [\n        {\n          slug: \"number-of-1-bits\",\n          note: \"Repeatedly clear the lowest set bit with n & (n-1) and count. Or check each bit with n & 1 and right-shift.\",\n        },\n        {\n          slug: \"counting-bits\",\n          note: \"dp[i] = dp[i >> 1] + (i & 1). Each number's bit count relates to its right-shifted value.\",\n        },\n        {\n          slug: \"reverse-bits\",\n          note: \"Extract each bit from right, place it in the reversed position. Or swap halves progressively (divide and conquer).\",\n        },\n        {\n          slug: \"missing-number\",\n          note: \"XOR all indices and values. Paired numbers cancel out, leaving the missing one. Or use sum formula.\",\n        },\n        {\n          slug: \"sum-of-two-integers\",\n          note: \"Use XOR for addition without carry, AND + left shift for carry. Repeat until carry is 0.\",\n        },\n      ],\n    },\n  ],\n  nextSteps: [\n    \"**Re-solve problems that gave you trouble** - if you struggled with a question, redo it until the approach is second nature.\",\n    \"Work through remaining questions in the **All Questions** tab or **company-specific lists** from [Leetcode Premium](https://leetcode.com/subscribe/), [InterviewDB](https://www.interviewdb.io/get-started), or [1point3acres](https://www.1point3acres.com/interview/thread/1167709) to target your upcoming interviews.\",\n    \"I highly recommend **taking advantage of free mock interviews** on [Pramp](https://www.pramp.com/#/) - *you want to fail as many times practicing and not during an interview*.\",\n  ],\n};\n\nexport const roadmaps = [beginnerRoadmap, experiencedRoadmap];\n"
  },
  {
    "path": "src/lib/analytics.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\n\nconst { mockSendGAEvent } = vi.hoisted(() => ({\n  mockSendGAEvent: vi.fn(),\n}));\n\nvi.mock(\"@next/third-parties/google\", () => ({\n  sendGAEvent: mockSendGAEvent,\n}));\n\nimport { trackEvent } from \"@/lib/analytics\";\n\ndescribe(\"trackEvent\", () => {\n  beforeEach(() => {\n    mockSendGAEvent.mockClear();\n  });\n\n  it(\"calls sendGAEvent with event name and params\", () => {\n    trackEvent(\"test_event\", { key: \"value\" });\n    expect(mockSendGAEvent).toHaveBeenCalledWith(\"event\", \"test_event\", { key: \"value\" });\n  });\n\n  it(\"passes empty object when no params provided\", () => {\n    trackEvent(\"bare_event\");\n    expect(mockSendGAEvent).toHaveBeenCalledWith(\"event\", \"bare_event\", {});\n  });\n\n  it(\"handles numeric params\", () => {\n    trackEvent(\"count_event\", { count: 42 });\n    expect(mockSendGAEvent).toHaveBeenCalledWith(\"event\", \"count_event\", { count: 42 });\n  });\n\n  it(\"handles boolean params\", () => {\n    trackEvent(\"toggle_event\", { enabled: true });\n    expect(mockSendGAEvent).toHaveBeenCalledWith(\"event\", \"toggle_event\", { enabled: true });\n  });\n});\n"
  },
  {
    "path": "src/lib/analytics.ts",
    "content": "import { sendGAEvent } from \"@next/third-parties/google\";\n\nexport function trackEvent(eventName: string, params?: Record<string, string | number | boolean>) {\n  sendGAEvent(\"event\", eventName, params ?? {});\n}\n"
  },
  {
    "path": "src/lib/register-sw.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach, afterEach } from \"vitest\";\nimport { registerServiceWorker } from \"@/lib/register-sw\";\n\ndescribe(\"registerServiceWorker\", () => {\n  let originalSW: ServiceWorkerContainer;\n  const mockRegister = vi.fn(() => Promise.resolve({} as ServiceWorkerRegistration));\n\n  beforeEach(() => {\n    mockRegister.mockClear();\n    originalSW = navigator.serviceWorker;\n    Object.defineProperty(navigator, \"serviceWorker\", {\n      value: { register: mockRegister },\n      configurable: true,\n    });\n  });\n\n  afterEach(() => {\n    Object.defineProperty(navigator, \"serviceWorker\", {\n      value: originalSW,\n      configurable: true,\n    });\n  });\n\n  it(\"registers sw.js on window load\", () => {\n    registerServiceWorker();\n    window.dispatchEvent(new Event(\"load\"));\n    expect(mockRegister).toHaveBeenCalledWith(\"/sw.js\");\n  });\n\n  it(\"includes basePath in sw.js URL\", () => {\n    registerServiceWorker(\"/leetcode-patterns\");\n    window.dispatchEvent(new Event(\"load\"));\n    expect(mockRegister).toHaveBeenCalledWith(\"/leetcode-patterns/sw.js\");\n  });\n\n  it(\"does not throw when registration fails\", () => {\n    mockRegister.mockRejectedValueOnce(new Error(\"fail\"));\n    registerServiceWorker();\n    expect(() => window.dispatchEvent(new Event(\"load\"))).not.toThrow();\n  });\n\n  it(\"does nothing when serviceWorker is not supported\", () => {\n    Object.defineProperty(navigator, \"serviceWorker\", {\n      value: undefined,\n      configurable: true,\n    });\n    expect(() => registerServiceWorker()).not.toThrow();\n    expect(mockRegister).not.toHaveBeenCalled();\n  });\n});\n"
  },
  {
    "path": "src/lib/register-sw.ts",
    "content": "export function registerServiceWorker(basePath = \"\"): void {\n  if (typeof window === \"undefined\" || !(\"serviceWorker\" in navigator)) return;\n\n  window.addEventListener(\"load\", () => {\n    navigator.serviceWorker.register(`${basePath}/sw.js`).catch(() => {\n      // Registration failed — offline support unavailable\n    });\n  });\n}\n"
  },
  {
    "path": "src/lib/reminders.test.ts",
    "content": "import { describe, it, expect, vi, afterEach } from \"vitest\";\nimport {\n  initReminder,\n  advanceReminder,\n  isDue,\n  setCustomDate,\n  getDueReminders,\n} from \"@/lib/reminders\";\nimport type { Reminder } from \"@/lib/reminders\";\n\ndescribe(\"initReminder\", () => {\n  it(\"returns nextReview +1 day and interval 1\", () => {\n    const r = initReminder(\"2026-03-08\");\n    expect(r).toEqual({ nextReview: \"2026-03-09\", interval: 1 });\n  });\n\n  it(\"handles full ISO datetime strings (slices to date-only)\", () => {\n    const r = initReminder(\"2026-03-08T14:30:00.000Z\");\n    expect(r).toEqual({ nextReview: \"2026-03-09\", interval: 1 });\n  });\n});\n\ndescribe(\"advanceReminder\", () => {\n  afterEach(() => {\n    vi.useRealTimers();\n  });\n\n  it(\"progresses through the schedule [1, 3, 7, 14, 30]\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-03-10\"));\n\n    let r: Reminder | null = { nextReview: \"2026-03-09\", interval: 1 };\n\n    r = advanceReminder(r);\n    expect(r).toEqual({ nextReview: \"2026-03-13\", interval: 3 });\n\n    r = advanceReminder(r!);\n    expect(r).toEqual({ nextReview: \"2026-03-17\", interval: 7 });\n\n    r = advanceReminder(r!);\n    expect(r).toEqual({ nextReview: \"2026-03-24\", interval: 14 });\n\n    r = advanceReminder(r!);\n    expect(r).toEqual({ nextReview: \"2026-04-09\", interval: 30 });\n  });\n\n  it(\"returns null after the last interval (30)\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-04-10\"));\n\n    const r: Reminder = { nextReview: \"2026-04-09\", interval: 30 };\n    expect(advanceReminder(r)).toBeNull();\n  });\n});\n\ndescribe(\"isDue\", () => {\n  afterEach(() => {\n    vi.useRealTimers();\n  });\n\n  it(\"returns true when nextReview is today\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-03-10\"));\n\n    expect(isDue({ nextReview: \"2026-03-10\", interval: 1 })).toBe(true);\n  });\n\n  it(\"returns true when nextReview is in the past\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-03-12\"));\n\n    expect(isDue({ nextReview: \"2026-03-10\", interval: 1 })).toBe(true);\n  });\n\n  it(\"returns false when nextReview is in the future\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-03-08\"));\n\n    expect(isDue({ nextReview: \"2026-03-10\", interval: 1 })).toBe(false);\n  });\n});\n\ndescribe(\"setCustomDate\", () => {\n  it(\"overrides nextReview while keeping interval\", () => {\n    const r: Reminder = { nextReview: \"2026-03-09\", interval: 3 };\n    const updated = setCustomDate(r, \"2026-04-01\");\n    expect(updated).toEqual({ nextReview: \"2026-04-01\", interval: 3 });\n  });\n\n  it(\"slices full ISO datetime to date-only\", () => {\n    const r: Reminder = { nextReview: \"2026-03-09\", interval: 7 };\n    const updated = setCustomDate(r, \"2026-04-01T12:00:00.000Z\");\n    expect(updated).toEqual({ nextReview: \"2026-04-01\", interval: 7 });\n  });\n});\n\ndescribe(\"getDueReminders\", () => {\n  afterEach(() => {\n    vi.useRealTimers();\n  });\n\n  it(\"returns only question IDs that are due\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-03-10\"));\n\n    const reminders: Record<number, Reminder> = {\n      1: { nextReview: \"2026-03-09\", interval: 1 },  // past — due\n      2: { nextReview: \"2026-03-10\", interval: 3 },  // today — due\n      3: { nextReview: \"2026-03-15\", interval: 7 },  // future — not due\n    };\n\n    const due = getDueReminders(reminders);\n    expect(due).toEqual(expect.arrayContaining([1, 2]));\n    expect(due).toHaveLength(2);\n  });\n\n  it(\"returns empty array when nothing is due\", () => {\n    vi.useFakeTimers();\n    vi.setSystemTime(new Date(\"2026-03-01\"));\n\n    const reminders: Record<number, Reminder> = {\n      1: { nextReview: \"2026-03-09\", interval: 1 },\n      2: { nextReview: \"2026-03-10\", interval: 3 },\n    };\n\n    expect(getDueReminders(reminders)).toEqual([]);\n  });\n});\n"
  },
  {
    "path": "src/lib/reminders.ts",
    "content": "export interface Reminder {\n  nextReview: string; // ISO date string (date only, e.g. \"2026-03-09\")\n  interval: number;   // current interval in days\n}\n\nconst SCHEDULE = [1, 3, 7, 14, 30];\n\n// Helper: add days to an ISO date string and return a new ISO date string (date-only)\nfunction addDays(isoDate: string, days: number): string {\n  const d = new Date(isoDate + \"T00:00:00Z\");\n  d.setUTCDate(d.getUTCDate() + days);\n  return d.toISOString().slice(0, 10);\n}\n\n// Get today as YYYY-MM-DD\nexport function today(): string {\n  return new Date().toISOString().slice(0, 10);\n}\n\n// Create the first reminder from a solved date\nexport function initReminder(solvedDate: string): Reminder {\n  const dateOnly = solvedDate.slice(0, 10);\n  return { nextReview: addDays(dateOnly, SCHEDULE[0]), interval: SCHEDULE[0] };\n}\n\n// Advance to the next interval. Returns null if past the last interval (schedule complete).\nexport function advanceReminder(current: Reminder): Reminder | null {\n  const idx = SCHEDULE.indexOf(current.interval);\n  const nextIdx = idx + 1;\n  if (nextIdx >= SCHEDULE.length) return null;\n  return {\n    nextReview: addDays(today(), SCHEDULE[nextIdx]),\n    interval: SCHEDULE[nextIdx],\n  };\n}\n\n// Check if a reminder is due (nextReview <= today)\nexport function isDue(reminder: Reminder): boolean {\n  return reminder.nextReview <= today();\n}\n\n// Override nextReview with a custom date, preserving the current interval\nexport function setCustomDate(reminder: Reminder, date: string): Reminder {\n  return { ...reminder, nextReview: date.slice(0, 10) };\n}\n\n// Return IDs of all questions that are due for review\nexport function getDueReminders(reminders: Record<number, Reminder>): number[] {\n  return Object.entries(reminders)\n    .filter(([, r]) => isDue(r))\n    .map(([id]) => Number(id));\n}\n"
  },
  {
    "path": "src/lib/storage.test.ts",
    "content": "import { describe, it, expect, beforeEach } from \"vitest\";\nimport type { Question } from \"@/types/question\";\nimport {\n  loadCompleted, saveCompleted,\n  loadStarred, saveStarred,\n  loadNotes, saveNotes,\n  loadSolvedDates, saveSolvedDates,\n  loadReminders, saveReminders,\n  loadShuffleOrder, saveShuffleOrder,\n  migrateLegacyProgress,\n} from \"@/lib/storage\";\n\nbeforeEach(() => {\n  localStorage.clear();\n});\n\nconst makeQuestion = (id: number, slug: string, difficulty: \"Easy\" | \"Medium\" | \"Hard\" = \"Easy\"): Question => ({\n  id,\n  title: slug,\n  slug,\n  pattern: [],\n  difficulty,\n  premium: false,\n  companies: [],\n});\n\ndescribe(\"completed\", () => {\n  it(\"returns empty set when nothing stored\", () => {\n    expect(loadCompleted()).toEqual(new Set());\n  });\n\n  it(\"round-trips through save and load\", () => {\n    const ids = new Set([1, 5, 42]);\n    saveCompleted(ids);\n    expect(loadCompleted()).toEqual(ids);\n  });\n\n  it(\"returns empty set on corrupted JSON\", () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", \"not-json\");\n    expect(loadCompleted()).toEqual(new Set());\n  });\n});\n\ndescribe(\"starred\", () => {\n  it(\"returns empty set when nothing stored\", () => {\n    expect(loadStarred()).toEqual(new Set());\n  });\n\n  it(\"round-trips through save and load\", () => {\n    const ids = new Set([3, 7]);\n    saveStarred(ids);\n    expect(loadStarred()).toEqual(ids);\n  });\n});\n\ndescribe(\"notes\", () => {\n  it(\"returns empty object when nothing stored\", () => {\n    expect(loadNotes()).toEqual({});\n  });\n\n  it(\"round-trips through save and load\", () => {\n    const notes = { 1: \"first note\", 2: \"second note\" };\n    saveNotes(notes);\n    expect(loadNotes()).toEqual(notes);\n  });\n});\n\ndescribe(\"solvedDates\", () => {\n  it(\"returns empty object when nothing stored\", () => {\n    expect(loadSolvedDates()).toEqual({});\n  });\n\n  it(\"round-trips through save and load\", () => {\n    const dates = { 1: \"2025-06-01T00:00:00.000Z\" };\n    saveSolvedDates(dates);\n    expect(loadSolvedDates()).toEqual(dates);\n  });\n});\n\ndescribe(\"reminders\", () => {\n  it(\"returns empty object when nothing stored\", () => {\n    expect(loadReminders()).toEqual({});\n  });\n\n  it(\"round-trips reminder data correctly\", () => {\n    const reminders = {\n      1: { nextReview: \"2025-07-01\", interval: 1 },\n      2: { nextReview: \"2025-08-01\", interval: 3 },\n    };\n    saveReminders(reminders);\n    expect(loadReminders()).toEqual(reminders);\n  });\n\n  it(\"returns empty object on corrupt JSON\", () => {\n    localStorage.setItem(\"leetcode-patterns-reminders\", \"not-json\");\n    expect(loadReminders()).toEqual({});\n  });\n});\n\ndescribe(\"shuffleOrder\", () => {\n  it(\"returns null when nothing stored\", () => {\n    expect(loadShuffleOrder()).toBeNull();\n  });\n\n  it(\"round-trips through save and load\", () => {\n    saveShuffleOrder([2, 0, 1]);\n    expect(loadShuffleOrder()).toEqual([2, 0, 1]);\n  });\n\n  it(\"removes key when saving null\", () => {\n    saveShuffleOrder([1, 2, 3]);\n    expect(localStorage.getItem(\"leetcode-patterns-shuffle-order\")).not.toBeNull();\n    saveShuffleOrder(null);\n    expect(localStorage.getItem(\"leetcode-patterns-shuffle-order\")).toBeNull();\n    expect(loadShuffleOrder()).toBeNull();\n  });\n});\n\ndescribe(\"migrateLegacyProgress\", () => {\n  const testData: Question[] = [\n    makeQuestion(0, \"two-sum\"),\n    makeQuestion(1, \"add-two-numbers\", \"Medium\"),\n    makeQuestion(2, \"median-of-two-sorted-arrays\", \"Hard\"),\n  ];\n\n  it(\"returns null when no legacy data exists\", () => {\n    expect(migrateLegacyProgress(testData)).toBeNull();\n  });\n\n  it(\"returns null when already migrated\", () => {\n    localStorage.setItem(\"leetcode-patterns-migrated\", \"1\");\n    const checked = new Array(175).fill(false);\n    checked[147] = true; // two-sum\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n    expect(migrateLegacyProgress(testData)).toBeNull();\n  });\n\n  it(\"maps legacy boolean array to new IDs via slug matching\", () => {\n    // two-sum is at legacy index 147, add-two-numbers at 57\n    const checked = new Array(175).fill(false);\n    checked[147] = true;\n    checked[57] = true;\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n\n    const result = migrateLegacyProgress(testData)!;\n    expect(result).toEqual(new Set([0, 1]));\n  });\n\n  it(\"saves migrated IDs to new storage key\", () => {\n    const checked = new Array(175).fill(false);\n    checked[147] = true;\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n    migrateLegacyProgress(testData);\n\n    const stored = JSON.parse(localStorage.getItem(\"leetcode-patterns-completed\")!);\n    expect(stored).toContain(0);\n  });\n\n  it(\"merges with existing completed progress\", () => {\n    localStorage.setItem(\"leetcode-patterns-completed\", JSON.stringify([2]));\n    const checked = new Array(175).fill(false);\n    checked[147] = true; // two-sum -> id 0\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n\n    const result = migrateLegacyProgress(testData)!;\n    expect(result).toEqual(new Set([0, 2]));\n  });\n\n  it(\"removes legacy keys after migration\", () => {\n    const checked = new Array(175).fill(false);\n    checked[147] = true;\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n    localStorage.setItem(\"showPatterns\", JSON.stringify([true]));\n    localStorage.setItem(\"hidePatterns\", JSON.stringify([false]));\n    migrateLegacyProgress(testData);\n\n    expect(localStorage.getItem(\"checked\")).toBeNull();\n    expect(localStorage.getItem(\"showPatterns\")).toBeNull();\n    expect(localStorage.getItem(\"hidePatterns\")).toBeNull();\n  });\n\n  it(\"sets migration flag to prevent re-running\", () => {\n    const checked = new Array(175).fill(false);\n    checked[147] = true;\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n    migrateLegacyProgress(testData);\n\n    expect(localStorage.getItem(\"leetcode-patterns-migrated\")).toBe(\"1\");\n  });\n\n  it(\"returns null when legacy array has no matching slugs\", () => {\n    const checked = new Array(175).fill(false);\n    checked[0] = true; // contains-duplicate — not in testData\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n\n    // Still sets migration flag and cleans up, but returns null (no ids matched)\n    const result = migrateLegacyProgress(testData);\n    expect(result).toBeNull();\n    expect(localStorage.getItem(\"checked\")).toBeNull();\n  });\n\n  it(\"ignores indices beyond LEGACY_SLUGS length\", () => {\n    const checked = new Array(200).fill(false);\n    checked[199] = true; // beyond legacy slug list\n    checked[147] = true; // two-sum\n    localStorage.setItem(\"checked\", JSON.stringify(checked));\n\n    const result = migrateLegacyProgress(testData)!;\n    expect(result).toEqual(new Set([0]));\n  });\n\n  it(\"handles corrupted legacy JSON gracefully\", () => {\n    localStorage.setItem(\"checked\", \"not-valid-json\");\n    const result = migrateLegacyProgress(testData);\n    expect(result).toBeNull();\n  });\n});\n"
  },
  {
    "path": "src/lib/storage.ts",
    "content": "import { Question } from \"@/types/question\";\nimport type { Reminder } from \"./reminders\";\n\nexport const MAX_NOTE_LENGTH = 10_000; // characters per note (server enforces 500 KB total)\n\nconst STORAGE_KEY = \"leetcode-patterns-completed\";\nconst STARRED_KEY = \"leetcode-patterns-starred\";\nconst NOTES_KEY = \"leetcode-patterns-notes\";\nconst SHUFFLE_KEY = \"leetcode-patterns-shuffle-order\";\nconst SOLVED_DATES_KEY = \"leetcode-patterns-solved-dates\";\nconst REMINDERS_KEY = \"leetcode-patterns-reminders\";\n\n// Slugs from the old CRA version, ordered by old 0-based id\nconst LEGACY_SLUGS = [\"contains-duplicate\",\"missing-number\",\"find-all-numbers-disappeared-in-an-array\",\"single-number\",\"product-of-array-except-self\",\"find-the-duplicate-number\",\"find-all-duplicates-in-an-array\",\"set-matrix-zeroes\",\"spiral-matrix\",\"rotate-image\",\"word-search\",\"first-missing-positive\",\"longest-consecutive-sequence\",\"letter-case-permutation\",\"subsets\",\"subsets-ii\",\"permutations\",\"permutations-ii\",\"combinations\",\"combination-sum\",\"combination-sum-ii\",\"combination-sum-iii\",\"generate-parentheses\",\"target-sum\",\"palindrome-partitioning\",\"letter-combinations-of-a-phone-number\",\"generalized-abbreviation\",\"sudoku-solver\",\"n-queens\",\"climbing-stairs\",\"house-robber\",\"best-time-to-buy-and-sell-stock\",\"maximum-subarray\",\"range-sum-query-immutable\",\"house-robber-ii\",\"coin-change\",\"maximum-product-subarray\",\"longest-increasing-subsequence\",\"longest-palindromic-substring\",\"word-break\",\"combination-sum-iv\",\"decode-ways\",\"unique-paths\",\"jump-game\",\"palindromic-substrings\",\"number-of-longest-increasing-subsequence\",\"partition-equal-subset-sum\",\"partition-to-k-equal-sum-subsets\",\"best-time-to-buy-and-sell-stock-with-cooldown\",\"counting-bits\",\"linked-list-cycle\",\"middle-of-the-linked-list\",\"reverse-linked-list\",\"palindrome-linked-list\",\"remove-linked-list-elements\",\"remove-duplicates-from-sorted-list\",\"linked-list-cycle-ii\",\"add-two-numbers\",\"remove-nth-node-from-end-of-list\",\"sort-list\",\"reorder-list\",\"pacific-atlantic-water-flow\",\"number-of-islands\",\"graph-valid-tree\",\"number-of-connected-components-in-an-undirected-graph\",\"reverse-linked-list-ii\",\"rotate-list\",\"swap-nodes-in-pairs\",\"odd-even-linked-list\",\"reverse-nodes-in-k-group\",\"merge-two-sorted-lists\",\"kth-smallest-element-in-a-sorted-matrix\",\"find-k-pairs-with-smallest-sums\",\"merge-k-sorted-lists\",\"smallest-range-covering-elements-from-k-lists\",\"meeting-rooms\",\"merge-intervals\",\"interval-list-intersections\",\"non-overlapping-intervals\",\"meeting-rooms-ii\",\"task-scheduler\",\"minimum-number-of-arrows-to-burst-balloons\",\"insert-interval\",\"employee-free-time\",\"binary-search\",\"find-smallest-letter-greater-than-target\",\"peak-index-in-a-mountain-array\",\"find-minimum-in-rotated-sorted-array\",\"find-peak-element\",\"search-in-rotated-sorted-array\",\"search-in-rotated-sorted-array-ii\",\"search-a-2d-matrix\",\"search-a-2d-matrix-ii\",\"find-k-closest-elements\",\"count-of-range-sum\",\"minimum-size-subarray-sum\",\"fruit-into-baskets\",\"permutation-in-string\",\"longest-repeating-character-replacement\",\"sliding-window-maximum\",\"longest-substring-without-repeating-characters\",\"minimum-number-of-k-consecutive-bit-flips\",\"count-unique-characters-of-all-substrings-of-a-given-string\",\"minimum-window-substring\",\"substring-with-concatenation-of-all-words\",\"kth-smallest-element-in-a-bst\",\"k-closest-points-to-origin\",\"top-k-frequent-elements\",\"sort-characters-by-frequency\",\"kth-largest-element-in-an-array\",\"reorganize-string\",\"rearrange-string-k-distance-apart\",\"course-schedule-iii\",\"maximum-frequency-stack\",\"course-schedule\",\"course-schedule-ii\",\"minimum-height-trees\",\"alien-dictionary\",\"sequence-reconstruction\",\"binary-tree-level-order-traversal-ii\",\"average-of-levels-in-binary-tree\",\"minimum-depth-of-binary-tree\",\"binary-tree-level-order-traversal\",\"binary-tree-zigzag-level-order-traversal\",\"binary-tree-right-side-view\",\"all-nodes-distance-k-in-binary-tree\",\"same-tree\",\"path-sum\",\"maximum-depth-of-binary-tree\",\"diameter-of-binary-tree\",\"merge-two-binary-trees\",\"lowest-common-ancestor-of-a-binary-search-tree\",\"subtree-of-another-tree\",\"invert-binary-tree\",\"path-sum-ii\",\"path-sum-iii\",\"lowest-common-ancestor-of-a-binary-tree\",\"maximum-binary-tree\",\"maximum-width-of-binary-tree\",\"construct-binary-tree-from-preorder-and-inorder-traversal\",\"validate-binary-search-tree\",\"implement-trie-prefix-tree\",\"binary-tree-maximum-path-sum\",\"serialize-and-deserialize-binary-tree\",\"word-search-ii\",\"find-median-from-data-stream\",\"sliding-window-median\",\"two-sum\",\"squares-of-a-sorted-array\",\"backspace-string-compare\",\"3sum\",\"3sum-closest\",\"subarray-product-less-than-k\",\"sort-colors\",\"trapping-rain-water\",\"container-with-most-water\",\"longest-word-in-dictionary\",\"index-pairs-of-a-string\",\"maximum-xor-of-two-numbers-in-an-array\",\"concatenated-words\",\"prefix-and-suffix-search\",\"palindrome-pairs\",\"design-search-autocomplete-system\",\"word-squares\",\"sort-items-by-groups-respecting-dependencies\",\"median-of-two-sorted-arrays\",\"majority-element\",\"convert-1d-array-into-2d-array\",\"move-zeroes\",\"is-subsequence\",\"binary-tree-paths\",\"factor-combinations\",\"split-a-string-into-the-max-number-of-unique-substrings\",\"maximum-average-subarray-i\",\"gas-station\"];\n\nfunction loadJson<T>(key: string, fallback: T): T {\n  if (typeof window === \"undefined\") return fallback;\n  try {\n    const raw = localStorage.getItem(key);\n    if (raw) return JSON.parse(raw) as T;\n  } catch {}\n  return fallback;\n}\n\nfunction saveJson(key: string, value: unknown): void {\n  localStorage.setItem(key, JSON.stringify(value));\n}\n\nexport function loadCompleted(): Set<number> {\n  return new Set(loadJson<number[]>(STORAGE_KEY, []));\n}\n\nexport function saveCompleted(ids: Set<number>): void {\n  saveJson(STORAGE_KEY, [...ids]);\n}\n\nexport function loadStarred(): Set<number> {\n  return new Set(loadJson<number[]>(STARRED_KEY, []));\n}\n\nexport function saveStarred(ids: Set<number>): void {\n  saveJson(STARRED_KEY, [...ids]);\n}\n\nexport function loadNotes(): Record<number, string> {\n  return loadJson<Record<number, string>>(NOTES_KEY, {});\n}\n\nexport function saveNotes(notes: Record<number, string>): void {\n  saveJson(NOTES_KEY, notes);\n}\n\nexport function loadSolvedDates(): Record<number, string> {\n  return loadJson<Record<number, string>>(SOLVED_DATES_KEY, {});\n}\n\nexport function saveSolvedDates(dates: Record<number, string>): void {\n  saveJson(SOLVED_DATES_KEY, dates);\n}\n\nexport function loadReminders(): Record<number, Reminder> {\n  return loadJson<Record<number, Reminder>>(REMINDERS_KEY, {});\n}\n\nexport function saveReminders(reminders: Record<number, Reminder>): void {\n  saveJson(REMINDERS_KEY, reminders);\n}\n\nexport function loadShuffleOrder(): number[] | null {\n  return loadJson<number[] | null>(SHUFFLE_KEY, null);\n}\n\nexport function saveShuffleOrder(order: number[] | null): void {\n  if (order) saveJson(SHUFFLE_KEY, order);\n  else localStorage.removeItem(SHUFFLE_KEY);\n}\n\nexport function migrateLegacyProgress(data: Question[]): Set<number> | null {\n  if (typeof window === \"undefined\") return null;\n  if (localStorage.getItem(\"leetcode-patterns-migrated\")) return null;\n  localStorage.setItem(\"leetcode-patterns-migrated\", \"1\");\n  try {\n    const raw = localStorage.getItem(\"checked\");\n    if (!raw) return null;\n    const boolArray: boolean[] = JSON.parse(raw);\n    const slugToId = new Map(data.map((q) => [q.slug, q.id]));\n    const ids: number[] = [];\n    boolArray.forEach((done, index) => {\n      if (!done || index >= LEGACY_SLUGS.length) return;\n      const newId = slugToId.get(LEGACY_SLUGS[index]);\n      if (newId !== undefined) ids.push(newId);\n    });\n    localStorage.removeItem(\"checked\");\n    localStorage.removeItem(\"showPatterns\");\n    localStorage.removeItem(\"hidePatterns\");\n    if (ids.length > 0) {\n      const existing = loadCompleted();\n      const merged = new Set([...existing, ...ids]);\n      saveCompleted(merged);\n      return merged;\n    }\n  } catch {}\n  return null;\n}\n"
  },
  {
    "path": "src/lib/supabase.ts",
    "content": "import { createClient } from \"@supabase/supabase-js\";\n\nconst supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\nconst supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;\n\nexport const supabase = createClient(supabaseUrl, supabaseAnonKey);\n"
  },
  {
    "path": "src/lib/sw.test.ts",
    "content": "import { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { readFileSync } from \"fs\";\nimport { join } from \"path\";\n\n// --- Helpers to build a mock Service Worker environment ---\n\nfunction createMockCache() {\n  const store = new Map<string, Response>();\n  return {\n    addAll: vi.fn(async (urls: string[]) => {\n      urls.forEach((u) => store.set(u, new Response(\"\")));\n    }),\n    put: vi.fn(async (req: Request | string, res: Response) => {\n      const key = typeof req === \"string\" ? req : req.url;\n      store.set(key, res);\n    }),\n    match: vi.fn(async (req: Request | string) => {\n      const key = typeof req === \"string\" ? req : req.url;\n      return store.get(key) ?? undefined;\n    }),\n    _store: store,\n  };\n}\n\ntype MockCache = ReturnType<typeof createMockCache>;\n\nfunction createMockCaches() {\n  const cacheMap = new Map<string, MockCache>();\n  return {\n    open: vi.fn(async (name: string) => {\n      if (!cacheMap.has(name)) cacheMap.set(name, createMockCache());\n      return cacheMap.get(name)!;\n    }),\n    keys: vi.fn(async () => [...cacheMap.keys()]),\n    delete: vi.fn(async (name: string) => {\n      cacheMap.delete(name);\n      return true;\n    }),\n    match: vi.fn(async (req: Request | string) => {\n      for (const cache of cacheMap.values()) {\n        const hit = await cache.match(req);\n        if (hit) return hit;\n      }\n      return undefined;\n    }),\n    _map: cacheMap,\n  };\n}\n\ninterface FetchEvent {\n  request: Request;\n  respondWith: ReturnType<typeof vi.fn>;\n  waitUntil: ReturnType<typeof vi.fn>;\n}\n\nfunction loadSW(mockSelf: Record<string, unknown>) {\n  const swSource = readFileSync(join(__dirname, \"../../public/sw.js\"), \"utf-8\");\n  type EventHandler = (...args: unknown[]) => void;\n  const listeners = new Map<string, EventHandler[]>();\n\n  mockSelf.addEventListener = (event: string, handler: EventHandler) => {\n    if (!listeners.has(event)) listeners.set(event, []);\n    listeners.get(event)!.push(handler);\n  };\n\n  // Execute the SW script in context of mockSelf\n  const fn = new Function(\n    \"self\",\n    \"caches\",\n    \"fetch\",\n    \"Response\",\n    \"Request\",\n    swSource\n  );\n  fn(mockSelf, mockSelf.caches, mockSelf.fetch, Response, Request);\n\n  return {\n    trigger(event: string, detail: Record<string, unknown>) {\n      const handlers = listeners.get(event) ?? [];\n      handlers.forEach((h) => h(detail));\n    },\n    listeners,\n  };\n}\n\n// --- Tests ---\n\ndescribe(\"Service Worker\", () => {\n  let mockCaches: ReturnType<typeof createMockCaches>;\n  let mockFetch: ReturnType<typeof vi.fn>;\n  let mockSelf: Record<string, unknown>;\n  let sw: ReturnType<typeof loadSW>;\n\n  beforeEach(() => {\n    mockCaches = createMockCaches();\n    mockFetch = vi.fn();\n    mockSelf = {\n      caches: mockCaches,\n      fetch: mockFetch,\n      skipWaiting: vi.fn(),\n      clients: { claim: vi.fn() },\n      location: { origin: \"https://example.com\" },\n    };\n    sw = loadSW(mockSelf);\n  });\n\n  describe(\"install\", () => {\n    it(\"precaches PRECACHE_URLS and calls skipWaiting\", async () => {\n      const waitPromises: Promise<unknown>[] = [];\n      sw.trigger(\"install\", {\n        waitUntil: (p: Promise<unknown>) => waitPromises.push(p),\n      });\n\n      await Promise.all(waitPromises);\n\n      const cache = await mockCaches.open(\"lc-patterns-v2\");\n      expect(cache.addAll).toHaveBeenCalledWith(\n        expect.arrayContaining([\"./\", \"./manifest.json\"])\n      );\n      expect(mockSelf.skipWaiting).toHaveBeenCalled();\n    });\n  });\n\n  describe(\"activate\", () => {\n    it(\"deletes old caches and claims clients\", async () => {\n      // Pre-populate an old cache\n      await mockCaches.open(\"lc-patterns-v1\");\n      await mockCaches.open(\"lc-patterns-v2\");\n\n      const waitPromises: Promise<unknown>[] = [];\n      sw.trigger(\"activate\", {\n        waitUntil: (p: Promise<unknown>) => waitPromises.push(p),\n      });\n\n      await Promise.all(waitPromises);\n\n      const keys = await mockCaches.keys();\n      expect(keys).not.toContain(\"lc-patterns-v1\");\n      expect(keys).toContain(\"lc-patterns-v2\");\n      expect((mockSelf.clients as { claim: () => void }).claim).toHaveBeenCalled();\n    });\n  });\n\n  describe(\"fetch handler\", () => {\n    function makeFetchEvent(url: string, overrides: Partial<RequestInit & { mode: string }> = {}): FetchEvent {\n      return {\n        request: new Request(url, { method: \"GET\", ...overrides }),\n        respondWith: vi.fn(),\n        waitUntil: vi.fn(),\n      };\n    }\n\n    it(\"ignores non-GET requests\", () => {\n      const event = makeFetchEvent(\"https://example.com/api\", { method: \"POST\" });\n      sw.trigger(\"fetch\", event);\n      expect(event.respondWith).not.toHaveBeenCalled();\n    });\n\n    it(\"ignores non-http requests\", () => {\n      const event = {\n        request: { method: \"GET\", url: \"chrome-extension://abc/page\", mode: \"navigate\" },\n        respondWith: vi.fn(),\n      };\n      sw.trigger(\"fetch\", event);\n      expect(event.respondWith).not.toHaveBeenCalled();\n    });\n\n    it(\"skips google-analytics.com requests\", () => {\n      const event = makeFetchEvent(\"https://www.google-analytics.com/collect\");\n      sw.trigger(\"fetch\", event);\n      expect(event.respondWith).not.toHaveBeenCalled();\n    });\n\n    it(\"skips googletagmanager.com requests\", () => {\n      const event = makeFetchEvent(\"https://www.googletagmanager.com/gtag/js\");\n      sw.trigger(\"fetch\", event);\n      expect(event.respondWith).not.toHaveBeenCalled();\n    });\n\n    it(\"skips favicon requests via s2/favicons\", () => {\n      const event = makeFetchEvent(\"https://www.google.com/s2/favicons?sz=64\");\n      sw.trigger(\"fetch\", event);\n      expect(event.respondWith).not.toHaveBeenCalled();\n    });\n\n    it(\"uses cache-first for static assets\", async () => {\n      // Pre-populate cache with a response\n      const cache = await mockCaches.open(\"lc-patterns-v2\");\n      const cachedResponse = new Response(\"cached-body\");\n      await cache.put(\"https://example.com/app.js\", cachedResponse);\n\n      const event = makeFetchEvent(\"https://example.com/app.js\");\n      sw.trigger(\"fetch\", event);\n\n      expect(event.respondWith).toHaveBeenCalled();\n      const result = await event.respondWith.mock.calls[0][0];\n      expect(result).toBe(cachedResponse);\n      expect(mockFetch).not.toHaveBeenCalled();\n    });\n\n    it(\"falls back to network for cache miss on static assets\", async () => {\n      const networkResponse = new Response(\"network-body\", { status: 200 });\n      mockFetch.mockResolvedValue(networkResponse);\n\n      const event = makeFetchEvent(\"https://example.com/app.js\");\n      sw.trigger(\"fetch\", event);\n\n      expect(event.respondWith).toHaveBeenCalled();\n      const result = await event.respondWith.mock.calls[0][0];\n      expect(result).toBe(networkResponse);\n      expect(mockFetch).toHaveBeenCalled();\n    });\n\n    it(\"caches successful same-origin static asset responses\", async () => {\n      const networkResponse = new Response(\"new-body\", { status: 200 });\n      mockFetch.mockResolvedValue(networkResponse);\n\n      const event = makeFetchEvent(\"https://example.com/style.css\");\n      sw.trigger(\"fetch\", event);\n      await event.respondWith.mock.calls[0][0];\n\n      // Wait for the async cache.put\n      await new Promise((r) => setTimeout(r, 10));\n\n      const cache = await mockCaches.open(\"lc-patterns-v2\");\n      expect(cache.put).toHaveBeenCalled();\n    });\n\n    it(\"does not cache cross-origin static asset responses\", async () => {\n      const networkResponse = new Response(\"external\", { status: 200 });\n      mockFetch.mockResolvedValue(networkResponse);\n\n      const event = makeFetchEvent(\"https://cdn.other.com/lib.js\");\n      sw.trigger(\"fetch\", event);\n      await event.respondWith.mock.calls[0][0];\n\n      await new Promise((r) => setTimeout(r, 10));\n\n      const cache = await mockCaches.open(\"lc-patterns-v2\");\n      expect(cache.put).not.toHaveBeenCalled();\n    });\n\n    it(\"uses network-first for navigation requests\", async () => {\n      const networkResponse = new Response(\"page-html\", { status: 200 });\n      mockFetch.mockResolvedValue(networkResponse);\n\n      // Create a navigation request by overriding mode\n      const request = new Request(\"https://example.com/\", { method: \"GET\" });\n      Object.defineProperty(request, \"mode\", { value: \"navigate\" });\n      const event = { request, respondWith: vi.fn(), waitUntil: vi.fn() };\n\n      sw.trigger(\"fetch\", event);\n\n      expect(event.respondWith).toHaveBeenCalled();\n      const result = await event.respondWith.mock.calls[0][0];\n      expect(result).toBe(networkResponse);\n    });\n\n    it(\"falls back to cache for navigation when network fails\", async () => {\n      mockFetch.mockRejectedValue(new Error(\"offline\"));\n\n      // Pre-populate cache\n      const cache = await mockCaches.open(\"lc-patterns-v2\");\n      const cachedPage = new Response(\"cached-page\");\n      const navUrl = \"https://example.com/\";\n      await cache.put(navUrl, cachedPage);\n\n      const request = new Request(navUrl, { method: \"GET\" });\n      Object.defineProperty(request, \"mode\", { value: \"navigate\" });\n      const event = { request, respondWith: vi.fn(), waitUntil: vi.fn() };\n\n      sw.trigger(\"fetch\", event);\n\n      const result = await event.respondWith.mock.calls[0][0];\n      expect(result).toBe(cachedPage);\n    });\n  });\n});\n"
  },
  {
    "path": "src/lib/sync.test.ts",
    "content": "import { describe, it, expect, beforeEach, vi } from \"vitest\";\nimport {\n  saveCompleted,\n  saveStarred,\n  saveNotes,\n  saveSolvedDates,\n  saveReminders,\n  loadCompleted,\n  loadStarred,\n  loadNotes,\n  loadSolvedDates,\n  loadReminders,\n} from \"@/lib/storage\";\n\nconst { mockUpsert, mockSingle } = vi.hoisted(() => ({\n  mockUpsert: vi.fn().mockResolvedValue({ error: null }),\n  mockSingle: vi.fn().mockResolvedValue({ data: null, error: { code: \"PGRST116\" } }),\n}));\n\n// Mock supabase before importing sync\nvi.mock(\"@/lib/supabase\", () => ({\n  supabase: {\n    from: () => ({\n      upsert: mockUpsert,\n      select: () => ({\n        eq: () => ({\n          single: mockSingle,\n        }),\n      }),\n    }),\n  },\n}));\n\nimport { mergeFromRealtimePayload, uploadProgress, downloadAndMerge, scheduleUpload, flushPendingUpload } from \"@/lib/sync\";\n\nbeforeEach(() => {\n  localStorage.clear();\n  mockUpsert.mockClear();\n  mockSingle.mockClear();\n});\n\ndescribe(\"mergeFromRealtimePayload\", () => {\n  it(\"applies remote completed when local is empty\", () => {\n    const changed = mergeFromRealtimePayload({\n      completed: [1, 2, 3],\n      starred: [],\n      notes: {},\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadCompleted()).toEqual(new Set([1, 2, 3]));\n  });\n\n  it(\"applies remote starred when local is empty\", () => {\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [5, 10],\n      notes: {},\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadStarred()).toEqual(new Set([5, 10]));\n  });\n\n  it(\"applies remote notes when local is empty\", () => {\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: { 1: \"hello\" },\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadNotes()).toEqual({ 1: \"hello\" });\n  });\n\n  it(\"detects changed note values (not just key count)\", () => {\n    saveNotes({ 1: \"old note\" });\n\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: { 1: \"updated note\" },\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadNotes()).toEqual({ 1: \"updated note\" });\n  });\n\n  it(\"returns false when remote and local are identical\", () => {\n    saveCompleted(new Set([1, 2]));\n    saveStarred(new Set([3]));\n    saveNotes({ 1: \"note\" });\n    saveSolvedDates({ 1: \"2026-01-01\" });\n    saveReminders({ 1: { nextReview: \"2026-01-02\", interval: 1 } });\n\n    const changed = mergeFromRealtimePayload({\n      completed: [1, 2],\n      starred: [3],\n      notes: { 1: \"note\" },\n      solved_dates: { 1: \"2026-01-01\" },\n      reminders: { 1: { nextReview: \"2026-01-02\", interval: 1 } },\n    });\n\n    expect(changed).toBe(false);\n  });\n\n  it(\"detects removed completed items\", () => {\n    saveCompleted(new Set([1, 2, 3]));\n\n    const changed = mergeFromRealtimePayload({\n      completed: [1, 2],\n      starred: [],\n      notes: {},\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadCompleted()).toEqual(new Set([1, 2]));\n  });\n\n  it(\"applies remote solved_dates\", () => {\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: {},\n      solved_dates: { 5: \"2026-03-10T00:00:00Z\" },\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadSolvedDates()).toEqual({ 5: \"2026-03-10T00:00:00Z\" });\n  });\n\n  it(\"applies remote reminders\", () => {\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: {},\n      solved_dates: {},\n      reminders: { 1: { nextReview: \"2026-03-15\", interval: 3 } },\n    });\n\n    expect(changed).toBe(true);\n    expect(loadReminders()).toEqual({ 1: { nextReview: \"2026-03-15\", interval: 3 } });\n  });\n\n  it(\"detects changed reminder values\", () => {\n    saveReminders({ 1: { nextReview: \"2026-03-10\", interval: 1 } });\n\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: {},\n      solved_dates: {},\n      reminders: { 1: { nextReview: \"2026-03-15\", interval: 3 } },\n    });\n\n    expect(changed).toBe(true);\n    expect(loadReminders()).toEqual({ 1: { nextReview: \"2026-03-15\", interval: 3 } });\n  });\n\n  it(\"handles null/undefined fields in payload gracefully\", () => {\n    const changed = mergeFromRealtimePayload({\n      completed: null,\n      starred: undefined,\n      notes: null,\n      solved_dates: undefined,\n      reminders: null,\n    } as unknown as Record<string, unknown>);\n\n    expect(changed).toBe(false);\n  });\n\n  it(\"detects deleted notes (remote has fewer keys)\", () => {\n    saveNotes({ 1: \"note one\", 2: \"note two\" });\n\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: { 1: \"note one\" },\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadNotes()).toEqual({ 1: \"note one\" });\n  });\n\n  it(\"detects removed starred items\", () => {\n    saveStarred(new Set([1, 2, 3]));\n\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [1],\n      notes: {},\n      solved_dates: {},\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadStarred()).toEqual(new Set([1]));\n  });\n\n  it(\"detects changed solved_dates values\", () => {\n    saveSolvedDates({ 1: \"2026-01-01\" });\n\n    const changed = mergeFromRealtimePayload({\n      completed: [],\n      starred: [],\n      notes: {},\n      solved_dates: { 1: \"2026-03-10\" },\n      reminders: {},\n    });\n\n    expect(changed).toBe(true);\n    expect(loadSolvedDates()).toEqual({ 1: \"2026-03-10\" });\n  });\n});\n\ndescribe(\"uploadProgress\", () => {\n  it(\"upserts current localStorage state to Supabase\", async () => {\n    saveCompleted(new Set([1, 2]));\n    saveStarred(new Set([3]));\n    saveNotes({ 1: \"note\" });\n    saveSolvedDates({ 1: \"2026-01-01\" });\n    saveReminders({ 1: { nextReview: \"2026-01-02\", interval: 1 } });\n\n    await uploadProgress(\"user-123\");\n\n    expect(mockUpsert).toHaveBeenCalledWith(\n      expect.objectContaining({\n        user_id: \"user-123\",\n        completed: [1, 2],\n        starred: [3],\n        notes: { 1: \"note\" },\n        solved_dates: { 1: \"2026-01-01\" },\n        reminders: { 1: { nextReview: \"2026-01-02\", interval: 1 } },\n      }),\n      { onConflict: \"user_id\" },\n    );\n  });\n\n  it(\"uploads empty data when localStorage is empty\", async () => {\n    await uploadProgress(\"user-123\");\n\n    expect(mockUpsert).toHaveBeenCalledWith(\n      expect.objectContaining({\n        user_id: \"user-123\",\n        completed: [],\n        starred: [],\n        notes: {},\n        solved_dates: {},\n        reminders: {},\n      }),\n      { onConflict: \"user_id\" },\n    );\n  });\n});\n\ndescribe(\"downloadAndMerge\", () => {\n  it(\"uploads local data when no remote data exists\", async () => {\n    mockSingle.mockResolvedValueOnce({ data: null, error: { code: \"PGRST116\" } });\n    saveCompleted(new Set([1]));\n\n    const result = await downloadAndMerge(\"user-123\");\n\n    expect(result).toBe(false);\n    expect(mockUpsert).toHaveBeenCalled();\n  });\n\n  it(\"overwrites local completed with remote\", async () => {\n    saveCompleted(new Set([1, 2]));\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: [2, 3], starred: [], notes: {}, solved_dates: {}, reminders: {} },\n      error: null,\n    });\n\n    const result = await downloadAndMerge(\"user-123\");\n\n    expect(result).toBe(true);\n    expect(loadCompleted()).toEqual(new Set([2, 3]));\n  });\n\n  it(\"overwrites local starred with remote\", async () => {\n    saveStarred(new Set([1, 2]));\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: [], starred: [3, 4], notes: {}, solved_dates: {}, reminders: {} },\n      error: null,\n    });\n\n    await downloadAndMerge(\"user-123\");\n\n    expect(loadStarred()).toEqual(new Set([3, 4]));\n  });\n\n  it(\"overwrites local notes with remote\", async () => {\n    saveNotes({ 1: \"local note\" });\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: [], starred: [], notes: { 1: \"remote note\", 2: \"remote only\" }, solved_dates: {}, reminders: {} },\n      error: null,\n    });\n\n    await downloadAndMerge(\"user-123\");\n\n    const notes = loadNotes();\n    expect(notes[1]).toBe(\"remote note\");\n    expect(notes[2]).toBe(\"remote only\");\n  });\n\n  it(\"overwrites local solved_dates with remote\", async () => {\n    saveSolvedDates({ 1: \"2026-03-10\" });\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: [], starred: [], notes: {}, solved_dates: { 1: \"2026-01-01\", 2: \"2026-02-02\" }, reminders: {} },\n      error: null,\n    });\n\n    await downloadAndMerge(\"user-123\");\n\n    const dates = loadSolvedDates();\n    expect(dates[1]).toBe(\"2026-01-01\");\n    expect(dates[2]).toBe(\"2026-02-02\");\n  });\n\n  it(\"overwrites local reminders with remote\", async () => {\n    saveReminders({ 1: { nextReview: \"2026-01-01\", interval: 1 } });\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: [], starred: [], notes: {}, solved_dates: {}, reminders: { 2: { nextReview: \"2026-03-15\", interval: 3 } } },\n      error: null,\n    });\n\n    await downloadAndMerge(\"user-123\");\n\n    expect(loadReminders()).toEqual({ 2: { nextReview: \"2026-03-15\", interval: 3 } });\n  });\n\n  it(\"does not push state back to Supabase after overwriting\", async () => {\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: [1], starred: [], notes: {}, solved_dates: {}, reminders: {} },\n      error: null,\n    });\n\n    await downloadAndMerge(\"user-123\");\n\n    expect(mockUpsert).not.toHaveBeenCalled();\n  });\n\n  it(\"handles null fields in remote data gracefully\", async () => {\n    saveCompleted(new Set([1]));\n    saveNotes({ 1: \"note\" });\n    mockSingle.mockResolvedValueOnce({\n      data: { completed: null, starred: null, notes: null, solved_dates: null, reminders: null },\n      error: null,\n    });\n\n    await downloadAndMerge(\"user-123\");\n\n    expect(loadCompleted()).toEqual(new Set());\n    expect(loadStarred()).toEqual(new Set());\n    expect(loadNotes()).toEqual({});\n    expect(loadSolvedDates()).toEqual({});\n    expect(loadReminders()).toEqual({});\n  });\n});\n\ndescribe(\"scheduleUpload\", () => {\n  it(\"debounces upload calls\", async () => {\n    vi.useFakeTimers();\n\n    scheduleUpload(\"user-123\");\n    scheduleUpload(\"user-123\");\n    scheduleUpload(\"user-123\");\n\n    expect(mockUpsert).not.toHaveBeenCalled();\n\n    await vi.advanceTimersByTimeAsync(1000);\n\n    expect(mockUpsert).toHaveBeenCalledTimes(1);\n\n    vi.useRealTimers();\n  });\n});\n\ndescribe(\"flushPendingUpload\", () => {\n  it(\"fires the pending upload immediately without waiting for debounce\", async () => {\n    vi.useFakeTimers();\n\n    scheduleUpload(\"user-flush\");\n\n    expect(mockUpsert).not.toHaveBeenCalled();\n\n    flushPendingUpload();\n\n    // Upload should fire synchronously (the async upsert is mocked)\n    await vi.advanceTimersByTimeAsync(0);\n    expect(mockUpsert).toHaveBeenCalledTimes(1);\n    expect(mockUpsert).toHaveBeenCalledWith(\n      expect.objectContaining({ user_id: \"user-flush\" }),\n      { onConflict: \"user_id\" },\n    );\n\n    // Original debounce timer should no longer fire a second upload\n    await vi.advanceTimersByTimeAsync(1000);\n    expect(mockUpsert).toHaveBeenCalledTimes(1);\n\n    vi.useRealTimers();\n  });\n\n  it(\"does nothing when there is no pending upload\", () => {\n    flushPendingUpload();\n    expect(mockUpsert).not.toHaveBeenCalled();\n  });\n\n  it(\"does nothing after the debounce has already fired\", async () => {\n    vi.useFakeTimers();\n\n    scheduleUpload(\"user-123\");\n    await vi.advanceTimersByTimeAsync(1000);\n    expect(mockUpsert).toHaveBeenCalledTimes(1);\n\n    mockUpsert.mockClear();\n    flushPendingUpload();\n    expect(mockUpsert).not.toHaveBeenCalled();\n\n    vi.useRealTimers();\n  });\n});\n"
  },
  {
    "path": "src/lib/sync.ts",
    "content": "import { supabase } from \"./supabase\";\nimport {\n  loadCompleted, saveCompleted,\n  loadStarred, saveStarred,\n  loadNotes, saveNotes,\n  loadSolvedDates, saveSolvedDates,\n  loadReminders, saveReminders,\n} from \"./storage\";\nimport type { Reminder } from \"./reminders\";\n\nexport interface ProgressPayload {\n  completed: number[];\n  starred: number[];\n  notes: Record<number, string>;\n  solved_dates: Record<number, string>;\n  reminders: Record<number, Reminder>;\n  updated_at: string;\n}\n\n// Upload current localStorage state to Supabase\nexport async function uploadProgress(userId: string): Promise<void> {\n  const payload = {\n    user_id: userId,\n    completed: [...loadCompleted()],\n    starred: [...loadStarred()],\n    notes: loadNotes(),\n    solved_dates: loadSolvedDates(),\n    reminders: loadReminders(),\n    updated_at: new Date().toISOString(),\n  };\n\n  await supabase.from(\"user_progress\").upsert(payload, { onConflict: \"user_id\" });\n}\n\n// Download remote progress and replace local state with it\nexport async function downloadAndMerge(userId: string): Promise<boolean> {\n  const { data, error } = await supabase\n    .from(\"user_progress\")\n    .select(\"*\")\n    .eq(\"user_id\", userId)\n    .single();\n\n  if (error || !data) {\n    // No remote data yet — push local up\n    await uploadProgress(userId);\n    return false;\n  }\n\n  // Remote is the source of truth — overwrite local state\n  saveCompleted(new Set<number>(data.completed ?? []));\n  saveStarred(new Set<number>(data.starred ?? []));\n  saveNotes((data.notes as Record<number, string>) ?? {});\n  saveSolvedDates((data.solved_dates as Record<number, string>) ?? {});\n  saveReminders((data.reminders as Record<number, Reminder>) ?? {});\n\n  return true;\n}\n\n// Track when we last uploaded to ignore our own realtime events\nlet lastUploadAt = 0;\n\n// Upload current localStorage state to Supabase\nasync function doUpload(userId: string): Promise<void> {\n  lastUploadAt = Date.now();\n  await uploadProgress(userId);\n}\n\n// Debounced upload — call after every save\nlet uploadTimer: ReturnType<typeof setTimeout> | null = null;\nlet pendingUserId: string | null = null;\n\nexport function scheduleUpload(userId: string): void {\n  pendingUserId = userId;\n  if (uploadTimer) clearTimeout(uploadTimer);\n  uploadTimer = setTimeout(() => {\n    pendingUserId = null;\n    doUpload(userId);\n  }, 1000);\n}\n\n// Flush any pending debounced upload immediately (e.g. on page unload)\nexport function flushPendingUpload(): void {\n  if (uploadTimer && pendingUserId) {\n    clearTimeout(uploadTimer);\n    uploadTimer = null;\n    const uid = pendingUserId;\n    pendingUserId = null;\n    doUpload(uid);\n  }\n}\n\n// Merge from a realtime payload (no API call needed).\n// Returns true if any data actually changed locally.\nexport function mergeFromRealtimePayload(data: Record<string, unknown>): boolean {\n  // Skip if this update was triggered by our own upload (within 5s window)\n  if (Date.now() - lastUploadAt < 5000) return false;\n\n  let changed = false;\n\n  const remoteCompleted = new Set<number>((data.completed as number[]) ?? []);\n  const localCompleted = loadCompleted();\n  if (!setsEqual(localCompleted, remoteCompleted)) {\n    saveCompleted(remoteCompleted);\n    changed = true;\n  }\n\n  const remoteStarred = new Set<number>((data.starred as number[]) ?? []);\n  const localStarred = loadStarred();\n  if (!setsEqual(localStarred, remoteStarred)) {\n    saveStarred(remoteStarred);\n    changed = true;\n  }\n\n  const remoteNotes = (data.notes as Record<number, string>) ?? {};\n  const localNotes = loadNotes();\n  if (!recordsEqual(localNotes, remoteNotes)) {\n    saveNotes(remoteNotes);\n    changed = true;\n  }\n\n  const remoteDates = (data.solved_dates as Record<number, string>) ?? {};\n  const localDates = loadSolvedDates();\n  if (!recordsEqual(localDates, remoteDates)) {\n    saveSolvedDates(remoteDates);\n    changed = true;\n  }\n\n  const remoteReminders = (data.reminders as Record<number, Reminder>) ?? {};\n  const localReminders = loadReminders();\n  if (JSON.stringify(localReminders) !== JSON.stringify(remoteReminders)) {\n    saveReminders(remoteReminders);\n    changed = true;\n  }\n\n  return changed;\n}\n\nfunction setsEqual(a: Set<number>, b: Set<number>): boolean {\n  if (a.size !== b.size) return false;\n  for (const v of a) if (!b.has(v)) return false;\n  return true;\n}\n\nfunction recordsEqual(a: Record<number, string>, b: Record<number, string>): boolean {\n  const keysA = Object.keys(a);\n  const keysB = Object.keys(b);\n  if (keysA.length !== keysB.length) return false;\n  return keysA.every((k) => a[Number(k)] === b[Number(k)]);\n}\n"
  },
  {
    "path": "src/test/setup.ts",
    "content": "import \"@testing-library/jest-dom/vitest\";\n"
  },
  {
    "path": "src/types/question.ts",
    "content": "export interface Company {\n  name: string;\n  slug: string;\n  frequency: number;\n}\n\nexport interface Question {\n  id: number;\n  title: string;\n  slug: string;\n  pattern: string[];\n  difficulty: \"Easy\" | \"Medium\" | \"Hard\";\n  premium: boolean;\n  companies: Company[];\n}\n\nexport interface QuestionsData {\n  updated: string;\n  data: Question[];\n}\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2017\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"allowJs\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true,\n    \"noEmit\": true,\n    \"esModuleInterop\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"bundler\",\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"jsx\": \"react-jsx\",\n    \"incremental\": true,\n    \"plugins\": [\n      {\n        \"name\": \"next\"\n      }\n    ],\n    \"paths\": {\n      \"@/*\": [\"./src/*\"]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \".next/dev/types/**/*.ts\",\n    \"**/*.mts\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "vitest.config.mts",
    "content": "import { defineConfig } from \"vitest/config\";\nimport react from \"@vitejs/plugin-react\";\nimport path from \"path\";\n\nexport default defineConfig({\n  plugins: [react()],\n  test: {\n    environment: \"happy-dom\",\n    setupFiles: [\"./src/test/setup.ts\"],\n    globals: true,\n    coverage: {\n      provider: \"v8\",\n      reporter: [\"text\", \"json-summary\"],\n      include: [\"src/**/*.{ts,tsx}\"],\n      exclude: [\"src/**/*.test.{ts,tsx}\", \"src/test/**\"],\n    },\n  },\n  resolve: {\n    alias: {\n      \"@\": path.resolve(__dirname, \"./src\"),\n    },\n  },\n});\n"
  }
]