# v2 git bundle
932bc192fad99dcc188b6b67660a3c612909a9a5 refs/heads/bundle
932bc192fad99dcc188b6b67660a3c612909a9a5 HEAD

PACK     @xtree a7f4527a558d70d4d3b79c4a2d539fbbce9c971c
parent f69147604663a70c71c8c0e287fd2525b593fef1
author MythEclipse <asepharyanalm001@gmail.com> 1778167850 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778167850 +0700

feat: integrate Geist font and enhance layout with utility classes

feat: update Home component with new UI elements and improved request handling

chore: add components.json for component configuration

feat: implement Badge component for status indication

feat: create Button component for consistent styling

feat: develop Card component structure for content display

feat: add Input component for user input fields

feat: implement Label component for form accessibility

feat: create Select component for dropdown functionality

feat: add Separator component for visual separation

feat: implement Toaster component for notifications

feat: create Tabs component for tabbed navigation

feat: add Textarea component for multi-line input

feat: add utility functions for class name management
iix4tree e82a3359489d2e0b4c70b3862046013ad292d98d
parent 1a0a9476741de0be2277be0d07443c49c6b06ccb
author MythEclipse <asepharyanalm001@gmail.com> 1778167539 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778167539 +0700

fix: prevent encoding issues by removing problematic headers in relay responses
hbx)tree 08ac7528df5b5568a63dfa6fc221f235a2a8b1c9
parent 83e66805c40c555a937c5d5fd0776c45c366ae46
author MythEclipse <asepharyanalm001@gmail.com> 1778167355 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778167355 +0700

Refactor code structure for improved readability and maintainability
_!x(tree 1879f96c0b1f74014144dd7edf06e6e3d8ba6480
parent 001bba1d2474ee403ab535fe00170e3731ffaacd
author MythEclipse <asepharyanalm001@gmail.com> 1778164479 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778164479 +0700

refactor: remove Tailwind CSS configuration and update dependencies
E^wx)tree 5dfbfcbd02c4fd048ecc4ba349f4e27437aa320c
parent 7416bfb2bd279900d71c0caa619a466633e4ef44
author MythEclipse <asepharyanalm001@gmail.com> 1778164260 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778164260 +0700

fix: add CORS headers to relay responses and OPTIONS method handling
]ŝx-tree 2020e06d0bf5f9dadf9b5d927c5e983b15f3213e
parent 083d77e252c665ab3b36aafd0e983b39b1162051
author MythEclipse <asepharyanalm001@gmail.com> 1778163987 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778163987 +0700

feat: migrate proxy logic to new structure and implement new relay route
n
`xKtree a93d91dd88d1ee4a9cb436952895a6be0e8f575c
parent 52845da8328ddbf436157be0b4b7c10871f14f6e
author MythEclipse <asepharyanalm001@gmail.com> 1778163750 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778163750 +0700

fix: migrate proxy handler logic from pages/api/proxy.ts to app/api/proxy/route.ts and remove old file
kxCtree fcc1d8df06bab530f3b527f7447a4fc486c6da03
parent 751eb083c4ca0bc58b97714aee5248b27fb990f8
author MythEclipse <asepharyanalm001@gmail.com> 1778163631 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778163631 +0700

fix: update proxy configuration to use 'config' export and move allowed methods inside handler
hx'tree ba41305b6df6136c6aa174a3fc7cada24493bc7f
parent f3ec82cbbff2c03f60431209b0c6ce08864c4568
author MythEclipse <asepharyanalm001@gmail.com> 1778163560 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778163560 +0700

fix: migrate proxy route logic from route.ts to pages/api/proxy.ts
^Ux2tree 852a687d6a9ebdf686b13ec7490e7ebc6de8a75d
parent f2ebba51bb080c03f43183dcf9bb14c324e7ad99
author MythEclipse <asepharyanalm001@gmail.com> 1778163391 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778163391 +0700

fix: replace stripRelayHeaders with filterHeaders and add BLOCKED_HEADERS set
ax,tree 24c6ee3c09cf62bca0de9795b6cb43795652fd8b
parent 3fdbaf3cede389656411435bcebcf87c880e1a48
author MythEclipse <asepharyanalm001@gmail.com> 1778163104 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778163104 +0700

fix: remove commented console log for request headers in Home component
/N`x"tree fee2b5d86e54da7ad5307f6b21fee5246c472b4d
parent 9d1ef77cda9826eff9e858b6384c5f806644e210
author MythEclipse <asepharyanalm001@gmail.com> 1778162949 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778162949 +0700

fix: update .gitignore and tsconfig for Next.js compatibility
\x$tree 162ae1d6d390ec0fc38b034df8d750cda39754ed
parent b11763a7ab324103edbd6824a04d8415664467f5
author MythEclipse <asepharyanalm001@gmail.com> 1778162859 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778162859 +0700

fix: update duplex type assertion in buildRelayRequest function
]x5tree a0438044399a4f70756eacd3f388a553f7d5292f
parent e553ed3e026873d035ddf1dfc157c4e802879e01
author MythEclipse <asepharyanalm001@gmail.com> 1778162758 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778162758 +0700

fix: update devDependencies to include TypeScript and type definitions for React
ibnx4tree edf4e85ab5d5136d775ef3ccdb17d6768eec0476
parent 262dbfafbe72b0b327511f8a072c9fc0de924890
author MythEclipse <asepharyanalm001@gmail.com> 1778162685 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778162685 +0700

fix: update PostCSS configuration to correctly reference '@tailwindcss/postcss'
&2cx%tree a93b6f6db7cd2d181d13efbedc20031529b32b67
parent b0dde3c88575899000eb0cf7ecd2a8b8dbf7c0c8
author MythEclipse <asepharyanalm001@gmail.com> 1778162610 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778162610 +0700

chore: migrate PostCSS and Tailwind configuration to .mjs format
-]3x8tree e8b12528787c775ae8115766a40dd7953f7ecd5e
parent 4c30f2a6e19093cb5959ca2e903130b382eacc94
author MythEclipse <asepharyanalm001@gmail.com> 1778162363 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778162363 +0700

refactor: migrate to Next.js with edge functions and update relay utilities

- Removed old relay utilities and tests, replacing them with a new structure under `src/lib/relay-utils.ts`.
- Implemented a new edge function in `app/api/proxy/route.ts` to handle proxy requests.
- Created a new Next.js layout and page structure for the frontend, including a user interface for testing the proxy.
- Added Tailwind CSS for styling and configured PostCSS.
- Updated TypeScript configuration for better compatibility with Next.js.
- Removed unused files and configurations related to the previous setup.
4x$tree 824993a93716c538147fe6509a92887fceeadf6f
parent db175e6e95926095e3a884e3fd7ef03582a23407
author MythEclipse <asepharyanalm001@gmail.com> 1778161518 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778161518 +0700

chore: remove index.html and update vercel.json for API routing
u[x)tree 2240a2eeb9ef126a3177ee89e031aa12ef097f75
parent cc38b7c7c6f2ff06f960f3a3c3d1b04089c9a992
author MythEclipse <asepharyanalm001@gmail.com> 1778161389 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778161389 +0700

fix: update fetch options to include target URL in buildRelayRequest
;`^x!tree d3fe3ff0692f8bbe92488d02382f36464374f556
parent 9e2f4d39b1a8d3a73ab92dac2b0e243b49fbafc1
author MythEclipse <asepharyanalm001@gmail.com> 1778161030 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778161030 +0700

feat: add isAllowedTarget function to validate URL protocols
Z`[ˑxAtree dbe56af9bf4b4b47a7abfff8e4291030eacdf049
parent a6333750e4f0a7e37d876cdd0841d45d9d96bbe7
author MythEclipse <asepharyanalm001@gmail.com> 1778160918 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778160918 +0700

feat: implement API handler for edge runtime with method validation and target normalization
h'xtree 0dedabb72e23890df0dc0259e55409ba0858b817
parent 9c05003e7014ae4176f3161d336da42e48f44eb3
author MythEclipse <asepharyanalm001@gmail.com> 1778160411 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778160411 +0700

feat: update project structure and enhance UI for Edge Proxy Test

- Add .gitignore to exclude api directory
- Remove deprecated api/index.ts file
- Create new public/index.html with improved UI and functionality
- Update package.json build script to copy index.html to dist
- Enhance request handling in index.html with custom headers and presets
- Improve response display with status, time, and size metrics
~1xtree 800017aa5fc029cc11e0b5225f3c5b5dd3348c03
parent 7df78ad117b4bd2d910f55d936cde0fd69af8a1a
author MythEclipse <asepharyanalm001@gmail.com> 1778159727 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778159727 +0700

fix: remove .vercelignore file
@Pxtree 0dcc76fa1c63756dd35dab912c3b8800456a1c1e
parent c0bfce3d268036c71afbd608f3c2fa3611d68f8c
author MythEclipse <asepharyanalm001@gmail.com> 1778159683 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778159683 +0700

fix: remove rewrites configuration from vercel.json
zYxntree 3a0f90b39e148b9a943c44dfa6cfe223891176cc
parent 149a646feac130302b05ed8a603021155a53b6e6
author MythEclipse <asepharyanalm001@gmail.com> 1778159621 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778159621 +0700

Revert "feat: update build output directory to 'dist' in package.json and set outputDirectory in vercel.json"

This reverts commit cfbc4609b59325f7b13e91d62e0f8b94002cd4a4.
96xtree 344e642adea961c456748be7213af6ef6e204cc1
parent 9759d63b38f0d3fa886b383d0ea4475b3ab77429
author MythEclipse <asepharyanalm001@gmail.com> 1778159284 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778159284 +0700

fix: correct typo in comment for allowed methods
1Vx=tree f331a4037f67e251d73de011fbd71e15cddac4b2
parent 49f50b7aa89cb81c9c8f5aaab448c20e0d640a7a
author MythEclipse <asepharyanalm001@gmail.com> 1778159037 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778159037 +0700

feat: add index.html for Edge Proxy Relay interface and remove rewrites from vercel.json
\
fRxtree c31ce882efc5d91844656c0d89263c9522fa686f
parent e5de4bbbfbb4c69d6fd70df07fc7c8c047fb506c
author MythEclipse <asepharyanalm001@gmail.com> 1778158843 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158843 +0700

fix: update fetch request URL to use '/api' endpoint
Xx&tree 89c66c04db9db94a94a43abd6a2034e1d2c43777
parent 6f28d18b955b3910603456a27f32e841125961d1
author MythEclipse <asepharyanalm001@gmail.com> 1778158774 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158774 +0700

fix: update fetch API endpoint and adjust rewrites in vercel.json
[x*tree 9bdb89021677b80d33ff8ed704715a9e879c16a1
parent 1b86a33b4ebfe0c7975141209bf853a19caf453b
author MythEclipse <asepharyanalm001@gmail.com> 1778158539 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158539 +0700

feat: refactor header handling and update module path in package.json
^Nxtree fa3ca0df2a6bc08c55865b1b04182f086bfd278e
parent 8e0cf9d0f366b6278f51f404c0e58d0aa7a6825e
author MythEclipse <asepharyanalm001@gmail.com> 1778158465 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158465 +0700

fix: remove functions configuration from vercel.json
Yx?tree 132c208074e34e844dba3863c01b8ca912b6e4c0
parent 212ce324805a01280ca3b01a8462b7a9ca0538ad
author MythEclipse <asepharyanalm001@gmail.com> 1778158400 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158400 +0700

fix: update import path for relay-utils and restore functions configuration in vercel.json
|^fNx0tree b260d37e9b556c24ebf22ccb576e25579d337ccf
parent d8e1e125b15af9903a433f22664717c411e9ec05
author MythEclipse <asepharyanalm001@gmail.com> 1778158192 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778158192 +0700

feat: implement Edge Proxy Relay with request handling and header filtering
W`xEtree aa4701c8ba846de84d57912fdfcc6dbea97368fa
parent 4cf3a520d50a941037e411582bd68ba5585880d9
author MythEclipse <asepharyanalm001@gmail.com> 1778157968 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157968 +0700

feat: update vercel.json to restore build and install commands, and adjust routing configuration
4igx.tree 4e5fea38ffc53deb285a8d6a2c3cd0c3ec428a0d
parent cfbc4609b59325f7b13e91d62e0f8b94002cd4a4
author MythEclipse <asepharyanalm001@gmail.com> 1778157960 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157960 +0700

feat: add index.html for Edge Proxy Relay interface with request handling
%aVx˹1@<D>di3@avgD/y}s`4#np72aiZ(dpP޺a(ΕD7ށ B_;h$0x:tree 42c57f376aacb019117a6896b6277660fe6ec45c
parent 098eabe5bf58ceded334c84aec21fad22bf01301
author MythEclipse <asepharyanalm001@gmail.com> 1778157571 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157571 +0700

feat: simplify vercel.json by removing function configuration and updating bunVersion
$fVx4tree e53f17f47db7c4ed24b58311fd833b0fd145369f
parent c49c1d7441bd6114d8ac9b6524c68fae9ec2c4d5
author MythEclipse <asepharyanalm001@gmail.com> 1778157124 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778157124 +0700

feat: add 'api' directory to .gitignore and update build script in package.json
UVbx!tree 13bf95cf078fe41cbe5b712a0246749dfa3c0595
parent 42cf7a89c7dcdf6ba019fa3fc4761de52ffae2c9
author MythEclipse <asepharyanalm001@gmail.com> 1778156931 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778156931 +0700

feat: add schema and bunVersion to vercel.json configuration
\Ôx$tree 16c4490cdaad8731da37b00549f4ed0f837d8722
parent b4f5bad19d991fdf2a385f828552bbf0258539bc
author MythEclipse <asepharyanalm001@gmail.com> 1778156765 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778156765 +0700

feat: add function configuration for Bun runtime in vercel.json
CE\ʐx0tree d174693999c8b1caeac264668058f25af27f5bfd
parent 9448a9cc7adc4296dc00d77f324d062935b0ed89
author MythEclipse <asepharyanalm001@gmail.com> 1778156548 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778156548 +0700

feat: enhance request handling with method validation and target URL checks
`ߝx-tree c66bb43dfece634df9c2966d72f842f199e14fcc
parent 86ca59ad8ec27cdd1e3a830507fc57f77d444808
author MythEclipse <asepharyanalm001@gmail.com> 1778155980 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778155980 +0700

feat: add code-review-graph integration and update project configuration
Ya)xAtree 8706934b5a75bb33997b6198061e8d1bc871db58
parent a4fcbbcca92256fb8df5a160f30215a98ec82d81
author MythEclipse <asepharyanalm001@gmail.com> 1778155966 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778155966 +0700

refactor: restructure project by moving and renaming files, and implementing relay utilities
R(h!x .tree 2aa466ef7705afc32b53317c523af03857c85caf
author MythEclipse <asepharyanalm001@gmail.com> 1778154129 +0700
committer MythEclipse <asepharyanalm001@gmail.com> 1778154129 +0700

initial commit for proxy-bun
FB?)x31 ҔT@OW;&f&Icd2CDA/=$3=/(!h옞wl~z]1LUnr^Vq~IeZ-G_81U/7Ag3FPN>
\]|z݈]m/V&u77PEIEj+}aJr](΃BB{9I6?jsRJ!vn-Mf emJSs2J2rX]ܢ }xa6kSezjӋPřy)z%%z%!+78<Sz?¼
@//氺TgWOl
~TeAbrvb:;?8ɸIa7_S_\\\k~͗/ܲxgGΗM)4)'3D{E8v(A`kk_K~](℔PJր]u~NYMU{w*,K-JŃ(sx$^WX䤯9 xJ 100644 settings.json e\Glj'	f,b40000 skills @2{*PfHnb/x{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Edit|Write|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "code-review-graph update --skip-flows",
            "timeout": 30
          }
        ]
      }
    ],
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "code-review-graph status",
            "timeout": 10
          }
        ]
      }
    ]
  }
}
evx K100644 debug-issue.md 8V6/DզK100644 explore-codebase.md zP6.$2!x100644 refactor-safely.md τBW់Է-NƩ100644 review-changes.md kUwpxxr+A.ICx?---
name: Debug Issue
description: Systematically debug issues using graph-powered code navigation
---

## Debug Issue

Use the knowledge graph to systematically trace and debug issues.

### Steps

1. Use `semantic_search_nodes` to find code related to the issue.
2. Use `query_graph` with `callers_of` and `callees_of` to trace call chains.
3. Use `get_flow` to see full execution paths through suspected areas.
4. Run `detect_changes` to check if recent changes caused the issue.
5. Use `get_impact_radius` on suspected files to see what else is affected.

### Tips

- Check both callers and callees to understand the full context.
- Look at affected flows to find the entry point that triggers the bug.
- Recent changes are the most common source of new issues.

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
0KxJ---
name: Explore Codebase
description: Navigate and understand codebase structure using the knowledge graph
---

## Explore Codebase

Use the code-review-graph MCP tools to explore and understand the codebase.

### Steps

1. Run `list_graph_stats` to see overall codebase metrics.
2. Run `get_architecture_overview` for high-level community structure.
3. Use `list_communities` to find major modules, then `get_community` for details.
4. Use `semantic_search_nodes` to find specific functions or classes.
5. Use `query_graph` with patterns like `callers_of`, `callees_of`, `imports_of` to trace relationships.
6. Use `list_flows` and `get_flow` to understand execution paths.

### Tips

- Start broad (stats, architecture) then narrow down to specific areas.
- Use `children_of` on a file to see all its functions and classes.
- Use `find_large_functions` to identify complex code.

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
JxR---
name: Refactor Safely
description: Plan and execute safe refactoring using dependency analysis
---

## Refactor Safely

Use the knowledge graph to plan and execute refactoring with confidence.

### Steps

1. Use `refactor_tool` with mode="suggest" for community-driven refactoring suggestions.
2. Use `refactor_tool` with mode="dead_code" to find unreferenced code.
3. For renames, use `refactor_tool` with mode="rename" to preview all affected locations.
4. Use `apply_refactor_tool` with the refactor_id to apply renames.
5. After changes, run `detect_changes` to verify the refactoring impact.

### Safety Checks

- Always preview before applying (rename mode gives you an edit list).
- Check `get_impact_radius` before major refactors.
- Use `get_affected_flows` to ensure no critical paths are broken.
- Run `find_large_functions` to identify decomposition targets.

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
RBx+---
name: Review Changes
description: Perform a structured code review using change detection and impact
---

## Review Changes

Perform a thorough, risk-aware code review using the knowledge graph.

### Steps

1. Run `detect_changes` to get risk-scored change analysis.
2. Run `get_affected_flows` to find impacted execution paths.
3. For each high-risk function, run `query_graph` with pattern="tests_for" to check test coverage.
4. Run `get_impact_radius` to understand the blast radius.
5. For any untested changes, suggest specific test cases.

### Output Format

Provide findings grouped by risk level (high/medium/low) with:
- What changed and why it matters
- Test coverage status
- Suggested improvements
- Overall merge recommendation

## Token Efficiency Rules
- ALWAYS start with `get_minimal_context(task="<your task>")` before any other graph tool.
- Use `detail_level="minimal"` on all calls. Only escalate to "standard" when minimal is insufficient.
- Target: complete any review/debug/refactor task in ≤5 tool calls and ≤800 total output tokens.
7zxmPMK1ϯzPA<(dt&$N *2o>ޛL'6ĥ7H"*W7Bb: j_owwop.M"Fer5f;؀3FIct+NYfnҶDD`[~iWL
s?V|$=n6g_3Hlh?v*x吻3n.t|jxɼyF"F Ix  C.next/Crnext-env.d.ts

տ
x P{
  "mcpServers": {
    "code-review-graph": {
      "command": "uvx",
      "args": [
        "code-review-graph",
        "serve"
      ],
      "type": "stdio"
    }
  }
}
8I,xmVmo6_qS)l	žu]]q%:f"
I6=GJ$@"~]Ob6%1ME2V9-}UL3J6/dȗT`	JSbl6ر{aGCM#v,s'k73ĕiv	G
9- TWz3T9)&ӲiĘګ~\9ЂJoŒ*˴_{	ZicUZ#*EzQ'GT~pGFG1H4u5]lV*LzYM	IES3Zc!y(P鄶|Ga3cȥR8ʓ{n{e>mZ>,8ٙF{zVho|gϷ&97Q+F(|~L
38,y_ЍAp3hS^'mv&\"~CSГcBi-2KϴժSτCwDQ(+]&tZȪ3~ayxU_[6v7F(uxld+T6+ib튵I}ކŞ"_82Ϲ(ù1<&<~m=}A?WbM*#&W}('O$9:ii"5<{1JUa.?owۛo{|^s?jp6
ekӈ`YP%r(
UؕzNV7Պ,B<=}|8lj!D aJh4-K.5`p`ι/J?(YǨ7Y+Yi6vp߯ȳpDʏKb~9"Ƌ'@X˰TyS.mۓp-c9ܧv'0!ak?HV;~s]3fJ?|2XMxf*㬐< [%spwy?EK$WY8p 6<cgT7X?0pv	0ma	=Yt/މjt?`vlW @s2Wl+b@3_71&fz]T=zox!	
<!-- code-review-graph MCP tools -->
## MCP Tools: code-review-graph

**IMPORTANT: This project has a knowledge graph. ALWAYS use the
code-review-graph MCP tools BEFORE using Grep/Glob/Read to explore
the codebase.** The graph is faster, cheaper (fewer tokens), and gives
you structural context (callers, dependents, test coverage) that file
scanning cannot.

### When to use graph tools FIRST

- **Exploring code**: `semantic_search_nodes` or `query_graph` instead of Grep
- **Understanding impact**: `get_impact_radius` instead of manually tracing imports
- **Code review**: `detect_changes` + `get_review_context` instead of reading entire files
- **Finding relationships**: `query_graph` with callers_of/callees_of/imports_of/tests_for
- **Architecture questions**: `get_architecture_overview` + `list_communities`

Fall back to Grep/Glob/Read **only** when the graph doesn't cover what you need.

### Key Tools

| Tool | Use when |
|------|----------|
| `detect_changes` | Reviewing code changes — gives risk-scored analysis |
| `get_review_context` | Need source snippets for review — token-efficient |
| `get_impact_radius` | Understanding blast radius of a change |
| `get_affected_flows` | Finding which execution paths are impacted |
| `query_graph` | Tracing callers, callees, imports, tests, dependencies |
| `semantic_search_nodes` | Finding functions/classes by name or keyword |
| `get_architecture_overview` | Understanding high-level codebase structure |
| `refactor_tool` | Planning renames, finding dead code |

### Workflow

1. The graph auto-updates on file changes (via hooks).
2. Use `detect_changes` for code review.
3. Usie `get_affected_flows` to understand impact.
4. Use `query_graph` pattern="tests_for" to check coverage.
ykxT͎6)A-;z-Y;qlkzAcil1H?u}=$4-o15xbn3^9_d
jzɖfBpa5wkQ`)wF%9q^*=œ쬄8etm!L:shr,3|$O%Iu
J]wW>c);1OWZ9]	m%ưٰrI50gQx@eՋhha$<jo2bWZcGfubTi؎3b'gmЬ~OfޒO6V ҳ#\K+5kZj=&mAvͧW9ӡv<ۨܥW۫u8,WVU/I]aH5a&Us3똉\ӎ^_єTƒF('*mY#}rvxh,2Fa>Y툂rѵp/z$F~x>9NX_;o.)qԳח?SYqU(0%N663'!8$A`,d]J5;%}feSk˶Pڴh3$4a.G;o]`A"a"OzJh藊,x*'%Jqx\WmxV`@\weSa2`CC?a^Ao.:'W(>k!ଂ*H%S{މh3nz:gqLĒI*EY\/"?Cx{
	"$schema": "https://biomejs.dev/schemas/2.4.14/schema.json",
	"vcs": {
		"enabled": true,
		"clientKind": "git",
		"useIgnoreFile": true
	},
	"files": {
		"ignoreUnknown": false
	},
	"formatter": {
		"enabled": true,
		"indentStyle": "tab"
	},
	"linter": {
		"enabled": true,
		"rules": {
			"recommended": true
		}
	},
	"javascript": {
		"formatter": {
			"quoteStyle": "double"
		}
	},
	"assist": {
		"enabled": true,
		"actions": {
			"source": {
				"organizeImports": "on"
			}
		}
	}
}
`WPx̽גH _1֏ɂV+A@lZZgKf2LQ==gˬ33kqf0vU,s^߶vYyrvSZNm/~O=G^fкqs7u<2+	5B?˳fU=f>MdeX{uOء> ߂n?mC#? /ӳ軮'KwW[6W4u>'\fwa<$n?R{001k&|z-@Uۮ0Y[i<?Q3n@*ӧg?<L|?~.-<"M.4BEǇlN=5N::YAþi־if'0еVTbgh^b:v!ӌ{g&tmϙ)$QE&#e?.Lˍę|R_[?`u5Mny8t:+N֡^"LUuvtOR<3;Y*PrzTP}apE+,_"-[n*;FbYβ%͓ЙyM' wxBBQь &;M5 KxnRBnҚ=$R(Ɔ#V5kBV'
 +?ܥwOvv)f}ry:um=|9M>ԥV^V&BЙe~0MpO>{D?mWTvnQby<Ȝ^Z?TYsrr'
ǵ"SӉՐ9?=Xnm#K'ܤ zlpr%kygI 5T$BqGC7PxA>Mb6){+5>_0l oN~bL>d/<EeJXiFʦBCFʰ`r^fӣsޝUh\z	S%uj(Y}f7
șīW<?rwvRnΗ# ekK=x5l_ԖEokSc,է;P?_5Kpl^TOqHlYwSQՅ'a<۴'#=Ŕ_B
	<fV^[v%96_р_")vY5؂1>hL		bx=}&)j:xw$7'?LOupG_CpGg^gq GϜKS`ª7oC{'P5;hu'Ou2XX d[޼[;2r{\o}-gwr-T|peaP&/B:`m?F+0Ihk ^̮v¼WwGS.gV9gKϓh'n7PqSGɹˠ7y׆Wno>/FZ
|bV[>~eroX-WuP-15c$lH.+师)w-k䊣.wUrPn	¬?1ȗh,LkoS
bp4D$:Z61쾲-G[Du yIgy퍔l(n<nmj0%mcyy;)-9gr'&0ߧGs0Qz	ܒSgnJ3,)~+\惰2$gԖMy| F+0n5Avvʩ}>LK7lAc٢<)Pҙa;5vJt_Tj@sc=6w~"yOûVm)Id*i;M	էu/B!\GF6H7'O;z;kWߠ<IA
k#\՚.tdqfLj;zo
Ǯҗ;38lJS9!-o(o5顰ז1<AB̂\Wv@	.)r@L5̣>Ӫ$v08iBN(V%}G74x&J|ӧG
N5pN}sqpp5%@@&c.׃l"V[[%%܎Mm^7P-GUk:p؂H
Nԟ~B徒lP-=
fF6ހ t}hu[ϋܶSua?Qaݵ*OMm1mH41qlh{	
cBf))Q\PǂsG|>-u;q"l+bթvW$LβyX.Ușάv/9993TErzMEy,<yJ `rΦs?~ _G~?bj!v+]>YoVFWzJkd-1	Bs++́)~56oieQuO._vڻ<܊BOgx%EI\Gv"bVG2i'"Cdj6?<a8kH7ѻpE
&)''i6͜5K@P-|$[i,a@|_ PFt2HݎG3,/PdlO%=Dy2+ҨClnY<W-T*ԴN)B1M#X7.73fk4Cj[Ϸ5^ɑTuOtF)EOo&}}sSh_'	-߯iBM#nO$I4%WvX<Z6w[+/,PLu6(\kvꄴ[O|ӊ_'v>Ԛ|{j)Xŗ#_>r?8<^J6d1V(E	d8<@{1Q"/2WvVLfɽȫlN!&z;r`b8׆)وI9vRb̠c*;Xf1!ə o@jnU+-0!vopoB_m^q
ZoM4.X̚h,_K8φlEL{pc|Kaؓ( 9hUAㄹݿ~ݶwGNQ_~HZ8*v]:_J	Igν6rmiK`A8%=/qQTZ<21T5D6wŔyJLIMDoijۼ;5c-H:8vb{~lYyFm:5d7+֠BKPAcn~^
Ⱦ$ū`/ <.%̬t{Kҹ=̫#9zTszkLċg,s4ĥ}qO|A=?*q>qONЌKv{;VzhB`'KQ;n[όvbm/X:0˖5YEmf:
TsV8⚻n~pIW?({r&ao?|Ȩ]{ճxղ-Za!`Mn AnDX7"D:qNI6@I{kP讝nEO >7<5=4zyzÅgsk(m^sߋ~~P?2+:I)s{,g=y0qY.4=e:{R	7s1cZ6`3ferEyXvHٲgԹ.ȈxTk#Sl9Hܗ|sJO)8 L)	aUӣ>4bk.U7fW8;ADӝzhs
TPD֭,پM.}J鼂t',}e$5MޛBޙUP'r'yH^]šu4;_hN,cŵ^uB+Sݝaԙ ̀9aYئcVd6H=tѯ۾X]GL Ge#@N |). Cbd[ Kf*iP l#s(3fju<}2f'J'tkcêJKF
r~Y(Zq팯L5Wh Ufh0#S랓Iz .oۧ ?_]۰
!9!|ޔjg|t%oȡbLS6?lp=S:(؃%~h\n40h^|Μ%ǞeW:}|N>? ֥D'NpklR =7[mg۽$Ocg9նĀ4DXP|a!Bmd^*PKqYvWg	}Dqy|*;p%%:$|Ep=waqKE0񅲙@2[S6maL,	͛cM%h	$jlWKq	}F]?>[x@;I_	!"*dsPmj$pÌ;Wzj
*g]#k|Cdp%[,qOP{?/H\Lg}n5,jYI͙4GPvN%nS.D!niPFB1T,8rq+1ZeӲ)+"!eF~@n§'=7^9,{yNah_~T=f\){ʦGm)M^ `j%c	Κhk\{حQ_sh|v Nma;sl"ѵnDOB=ő7f,T]tҳZ&G6MV]^6R1C3Cg*C:! Sg~kv~bnFM;1ɐ0oP,4=R;JfrdISqcWEηduX!+H/ǰky|rz$v5(gǒX.md+sɶeT]jB8nG;YlpDjNɷyչm=@&hABj;mI,d3ea{ilJlܙ꒻L7[M;XM	#lRPvθzJgk|D;9ZΔoli;HNz-% @9^8M}=cq:qxqv`]>鵮nTKϗ緉~LV}YG}L1? fryf`kI@
$98hNOdޖPedOR$gNSóSlҺ$ER|XDM dn}Ki>sX]u^Hk	.r-g!ϯuP.8e4}kdiyr4gvq~{ݐ{խ_TZ)lcaubNs#XE#2:HF;VAmt%Ĥa>Mb^Ʋm94_<ÿNşCwr2ayI-68*da{kK"2m֪YzsWLI)Iy5sZ..][θ_h5Qy/ϗ.΋%y^|ƻ u
l }SJA90/" SQKڴ	Tr^fvn~Fy@3S]oXB&D带J.XA))ZP=%ׄK6uCf."~3_ wBM'=ތ[WEUtMm~C7S<LUE'4l$PfId9PV:L%vd{w#~x:mDTpgޚuSS9/WʶFɻT]tS׊ZlU,xMͱUn񮧬8m.}ͨvDP/U(nަw%+iKCw1Pw(K 2{YN_+,Slf	}߉6y۾380n8I^U	v;?	I 8XzNϙ@ɬ!pg61?ha^	8\Nq	>'K!Q$w	ߐC< kWI91U<՜|"odZ-tfl̑9Lp,EL,G+챈ؘ5
E^LN|#.P΍69zysֻmk U&f\ĳ0-,ScZbkAHXya";Ѷy R&xo$7tD۪4^X=v_(	eVZEQDIꊎ&
Y{l,ҝ6[KE%r&[e>7heI!"1ހ|~HΫ{=$%5rZ٬{DWVi'{,'0+1<ȂoT$0L1>_iӸ
GRh3<&o0kF)aڒiV3V=@nC{OJv1V,~(3}οǵ2+.@C/R
L,{{]&¾& 
k3VDnK@7p-}=̾]P`9n mWR_R63u[* #
JkMX,a+&wNk6zܟukE
kdnStY#+ovVSr|A#I2-{VѲ5e7F[vf=EK*yWy 4rQ
mq1h	<bfWsH)k$8y:'Rh9-zaMaf&|`EЍ{~|rZٱw=͆+|ۨNT(T%!ޅl&I<}>thώ_޾}h;eQ]CMs`fXnBaԉQV\;d\8* q#0	D$"n5>'n5 VZ-QFzuMNG3zzeDju!!>YAl)«լ}҆\:>pn/ 7&{Kcfz]yl89
["',x:rŻ|m2& X']VJgSdyׄy1_4N4<צZ2 3VNanf+l \Dqp8.RA uq|__@o|M?Uj2e1,@8{hZhR2n["US"fBH,7hu/ GiWnF9_ګndYwSP([kqb:g.:PzJ'5npHYxpd֟^^.6TRWTÝ8:w=dCsS]&MX0ΙGuACçqh6Hgfᓝ(jX@fc1$RHY-mfe9z ZDY/mtȏ3o׵-~Չ͕z+#^»ǹ؍v.QQKsmsiqiy4.LRzGf=B|*Śоjւz"$es"VW	vW'N3hvpiJ.\-oyAq]uGR4мòob눟J BN\69	戩M2;ػ}
rcA:>	*E%<:01hm`9"wThaJB姡F!nI˺u[Yp{$m8bMV\s<L&,0*mY6;WO-x:ZҌk4_Jٞrڢػ32*|G-J4=VJ7?%{toy^?<vׅx.W|3ٱ<x֤ҩa;M0I!XRSYI	eNcӷ~,ĚJKH1gƝ5'WG|^ڼjJIw#kyFQa'pzgVчOIS@ď<JUބamf1f0dͶgGbC;S@jHrrp	TdhlV	϶
el؁n^}
*W
=^Nٴ#!yWJj$A<JPj`=S_7M?ѷcwtku/ljV@bPC-:q  )wg+l9u$g!NCq-ˎ*Ů_|< )<tHه
	_ z[:5@AKwܭxJK>4j431bh`.H"E1&~Xߋ,c	4UOb)P0M}rBu)Ķ,3(7.ƞbpaR+⡧*s+㤕YܺM!Ks:mk#l}V0Ԗ]k4PE]oil8*^,'{ߋu.hӠ]iATAA, rRDԜ,9TSYϳH޳7Pe7fMdUyj0#Y7j7BR
mL-mRd	}絏r.
U\t-y(o&[Gr&tפdRr#CCLj4<p}k$ϧ+f4VO==jI_29 >Cyʁ9>QdWpx=nآbg\(
]Z͗K ҧxt9|.eVUUnvG\@\H7ow	D(x4>ݝq\$)LzIbyck#nӈc7w݃9P̹46|mZm|#>^
{ڻ]1CĠ`YwҎpiͪb :1F\W.$N;윱G0̈jRuQ'96o?W}U(Zjq=,]S.?dE罐oϓKT_=<*ݶrYS?zAuã(?'i_7KIw&[12 ұ6:萜xEӫ݅YA,7Ȅd5%y׮<PV]fN{=O_l_v/ϝ`5Ϥv]PIjut
V1JB|g^ s\R+BLzC
) A]ʹLX\i׿~>_s>ڤ
|1R4r򆼚8T̤ۣmfd d/a@$nڲ!=_àq1;oM9sz	`wp֯垎ZBШT|cN$qNX~FE4Yp)4{ֶU}n;]hr=Sl0#3O*:eӵ'ig܆H7Ưb =>0+&=x$:w竃"Λ"#cg)SXYhIq(z}ȉ	1Cors<ALb?_L~HфEFR΁Ò"x4.7 :(W;Ff>>aHu1UEFz>߹ $;j+*
{y'BXP!qFԂ.]%uKQYИ<R@p8
A8hJy(GoqV`tC~fS{*gfU!	0a]'2^T{K*rq!ƣذUߞxgr=b&	]J׺.-trpB[F7m@aXx+ϡv<6,É5Hۛj|n?)}I`2+J,S2zs/Jo\Xz$~(9U(N=^ĉ5Y>éPڬ	n^ǌ΢J1d_7\Xp22
r0h1uVFgH(.(3Ho1nNrz@?uRG.@bF9	Mȕ"Q	'X¦詠@!>wؚS2v=ǖȨ}wc'rfN%^"FMÝt
f[F2`MT0^F37Ar{}2e[~B?;!wc8D.T*N?"E ^rrFCb{1#U"<2
VT>LڜGe(?L|6C֞szNu{IL	6*	,llU]cs"CMLyS&e8#|Oy."H÷~;_?c:ǲhHұt	0	Ci,ki_ҁhnч8;_rDTںfCtf||7sc]s[\؀ӈlL}k.#gtֹIkVi dhLfY'q vU]Aq:eC<xa(ҽ]@u/ieJgX51RmIl"fQ4:7gʺ%hINczn[MB[RjQPڸO~Lw܌!I{UVM\@N]*2i4^I_n:SMܝed]4fw˹o>KjNO𭙿'SJQw9sqֶ$&%>qMtpk`yzd]njotNLҷ$&3O}%{#N SDTkxd$F	L%ipqYO|mMcF`yթvZF0inʟʺ23/˟k|&djJO;h8nXf&:=;U)Qw!5=T^.ZC^rΛ^W}yxI*#U5Ic,nꦬY/UWN!][tQH0q/Ҕvoyֺ
x_2o'"qS},no0I'	EpX>_ԼH)d^,#k25Dc*rN@ǃ.\)&>MIO05Rb.DFmlh 8[/SP8gv۪/\&yRk^l: LoKf_޶(|Uk48&<\&.e>
yI屧yCr)ȗ["Wste*UɠqSFcDg>U#&(OUΩ|R2W| YZZ1?D](wYޣc-?)z{I2j^kHt]r2+qN]
ۏ
|x|S)G%;2L q۵@Ya`S:wf0V2P,dQ8mE/ @	tAw-Kfws+yO⯁^@>-p	iUB[::̘,f:%FK9E<J8'Ja3OZFGVAcyWw5{oL[" fҮzZ,b2M(>wZ-4ʹ=kz2I9ASm7ĭ6f"zRdX'؂5>\%FƔɣx-;\N;I44ѪWx]ԘLڨl8YS5,|O3DK$ 5L
}M CuI<e34@Q~wȅ9o	rdXQuwb Vg"7:Wx+ͶTE5,,,`8OIw};A%ط-g] j+NcP2FBJ!0b~mƍAxIt=[e!maQrv_)ռpI錶2ަy.,=#}Xǉ 	X`RzS"y4<C&ulaUbEUwxxv^N#$]47xJő4}1?]:q5{xSٰ-zp-6<j<cDWp;1=1&G-8^9~řӃv3+
PƫW]5wq{|:;y9+:M|I?*	x>_w00\{Ѿ
^
:w3O&EQhV.cXEX*&\oD~r8sFdZW-|e-|w_޾^YR̡Yt~'E䉱)t8/F($vܭY&妢u86+HN7k GbU@yo+!xPm,i.n8ΤeL L5TDe#;pU5HM`+^8(h?aUSn7ǌ&q\2?*_w.ÞF^^nHolakcdt
`% gyarj*.yg=J6w7zH2K|.n<W|qU?E#<琾|upr?ε&Ծ5.'!LXSjZ;j $ͣgqy|<fX()<hQ8<d`$rozޛ_>ڱOK>?c{1>]#/Dey
c.*(b;tK弢N$0@Ef#3_wG OAJ#ț{Y( $k;ؾC$
a}N[͐u̎SQ8V\VlKgY&@J9Fv]N1M5&%^f'`\_2p~2X(~̷nӨ6!Udl"^$-eDJl?	qKٜ`gQ,#V>4A
GՄXtl
X;eP$Jy'fHjR	cQp!X W ~!niR'Çc?|U,9 ъĞ		IKZ%bmј-b{cZ`d*.n?/y>f;`TKc0ws`Qrj?fZKk_ͺMNwK:S#sH	nmiR@h%ףKoPywPBx	:WU^P[<~
0iyd1[Wٹ؎=s1%&<9}tvC
:/w|L7ۣ=gXǋlF
`[..VUIh^UMp b&zD"P{:/a7su{v`Cã>9XSHzݢA
P#L(;Y{VsVcYr/=ss}HD[hJ?z\$.sn0٩+bGoSU4C~RQME4qwVI6q9y6?pu^my
:^{/TegyA#5/5ܯ_}nyOSAD
x.N3>y8ͺcpξ^p`x57{#^S#qz1`{՝ġ}IX]rd^h.6oY*0 Fd)"4ba	R<d + .ushG]7Yqz{:؝$qnSh.}=pA6Ryi[eYЯK	9lc~3l&et[~k?' uuU'SLOh' g'1hGNYmy107uYuu3C))mnWkԯ>%П$4aO՞XvEr{(LL`hVS뻙ܗSȁ?6;c<x4|.6w	uR0Ld!c\4$ޙb&DEMGXfQ+Od&x@0Tv=0ǭhY.DJD}_ foU?-7
3dj0k,KfB(¬
+yYbmGd8S:ʢ=)boToWIxBo-\GB`CGI	Xm \ZZ_B03V;T%(R|#-!}^ ½IKP]IrB:L4|?|}Iz&wMx+-0L%`Y4$fAM|Z <qk
G+3	[42㡈LG̲UnEMcG=ׇ㖰zan Hz17qoa2%؝;.Z@Èc`aR7T^<}h0!&"nnTEJGAĺ줫^A4iY=yP>XaoySMp.jx`ϕ۫>>Z,fUs=5KQF}Ho2e9bPIŒ%΢hϵ, <ob?v9N<Ga\ت@$WlV@Đ%*E\u2as3fMCM}WZ}(*MP1LV<AѕOZk-mj!pBHV&J쿏͏kD9񂭸	 mYWdu]5i:mB P$Bv/йRp63o|wW_}X^Q|c|IL9?so4
+hDAED@91o1v~w"N0*++3+ʂ4SaLj!\
󅄕[Y#G!]֯3Yb{ zd셴Yt 0K!":}1q@?zXUX`gv[r9{#>;Cٞ|T,>"z#6)i\rrCKݸw˴5Ęb[ۭ2Ko1֘m~#}YFXGnL-|w=[%`v%Tb0C\`1lARY5u.1f׾6E]x {\0$f1bm꘦z=S()`-L,9]沣;7mmHmFy\J7W~w+{˱~=!5%PQj@cv&
]30.4vÌi-.&&:$&	1J~!F{b!UմZ)|!fK	$ȟ:}yRJ\$SE`:VcI+Hǥ梴{Nؾsc z^Պc 1'YdԬ5~+rQSNF:t5>TMBLҬrC`4':̥zRc

NL/ I1&]f9gAz5lJTQyie>[/w(l;%B/a;Sq2vT9L߯hk=}g;%t? u塔L;AM9NAt?'vqM&蠐|>⎤h~CXۗV]{"|bC+{ii9ȓ,qW	ӆ!=l
z@zm߬-<3>`M20oI*H9.7^ZGwJRkL{G@%(̓0`g_ct{Lx*/<+&XJVEᐜK\9LfʏUe؃i\P"\vM
~ϛ90cyrsh5NSV0_ۋQOtD]jS"4@>?:5i6SXw}+&)~ބf_7~U4aǙ<IrmۈY\rpoG[@f:nz1'Ai7Kص%[5O=(xO)pT|jaJ	M#ci>;-*;&Ȉy|/؍9ZXqs&3ŌְIc#4
[TJ)n7~S'W%UL5OPnĘM.g@cuS@u⻩kjv}KoaN؉?l?Kh;i}#Gub{$AaUq|xn=HL{9sr?5bQ11qĨ~SE;[bu7l{n!m.~)m`yԺUxu}F_z>3/?aKFaB~a-&iF-)*n9e.t<\[B4+wTܗoTx_2*Zvd	MJjC8$}l@ؕM[[1.PLxKjϷfn\\&4:f'8Yf}@~+ˤ'$*mɹG6^pgfJVEM麽YEkKP?$[m*fUYإ^fH.וwwo=Nɂ7;ώ0:`YPXpPl[~+UdntWMoƑɔ:2$u0w *l1K&49kY)QmM\)+k)l}@TXmlӒAӈ6F%XӱLm?f>5y+[H |Q,
W8d7lI$Vc>`Fm ' #Y+_mgV9Ysz?6ұ$y6snG4?z;o2!	Ϫni&6DvK0+,#D&'$̅lOUN2B^WhxI\eGAwc<Rz1vsԛ/B)R{>F/&ǖ:_yӹ6GOtᴑ[8ݻx7ғ{_5`Sah7;@~`4I)lS	ϟ(Ѣ%@}5G9!oQN@p;Ke9>_Igjh<Ǭ1u!G7onU]mߦ(qt~vx>ṁd-e/SP*Jmd|ݩ
5R9yёD1:[6嘇>w=u~qrV@>⿵k,6<NuNF@&܆cf|bcgTe,?[eyi 0!18z^ߏ琉?f	]:L(seɴv!qx!dxJXtRSc&P}aӞYoŷulmA.PZ0V97NEmy0k;&i׉愬5O:qd\D?NYy՜k9hDh*F\8ۓc"`	Z8mv"D]8}#8st93*mv{xpNnj4C}+t|y]9d}5ﯸe{vrf+bFtw:*foϬ͹R^j q<@#鬛g_,~D˘D8#pϧ&Sn&F'BD-Y~v&pzL3kV!uGV : {nsKsI6qSa0ue#iI.spg(BL"\{ei&8PәXqQȍ2mIgfu7SlU7	V!vIfۧ&;ZLX-jhInb'yiAǓ/BiuY~anJ7f#/FC"鵳C=HWqp+bD(Ѫ(J7>1AʣKKeR!tb4)EXdBF{ubFfR]S&o3eՏ6ZG0GQipq([:vKYqxcv!c;Rw!% '+iZ1\?N|Շw0<93,[-eC)ECr n|
MǸ_5mY)suwt$ \?*,3w5T(0䊫Fp"XAtj5fM'X-5b\=.t7`BUŶu7OtTl=Vɉ2_-}x
#z
BuIϺi^jCl/A77I)/g5j.b(yZ	ckt)I9%v+jW{t]uL^9zas4Mq}&9)ޒ'/x^up3_e}8C/Ʌ>⯜<.$_cZLod `NGծ6f@.5p4Bkm(B#Wdmq/smĪa틓&BLn_Νz:l>/F)lל7Dl܇H_Uk#<xzݯCgT@0ACBc}nO*%fz&Y/bhx^ˤG%.*^N.xwv	7\[T<~"mh|?yw<ˆa:|,lkϊ5!PU$G
]ѽ4M/ac/WǞf}y93{mqvͱn<=/%{
Tܕ\;RݶGXT8Ԣar?+UQ\ՁSc4?M9ɝ]z5xN?DcNUɚUw1S&[3cpFl4vK&y֬QJ|<}Xyf8lz\Ay!Z"W]#1"isSXsWU>\.^Jm"΂رw֣^Yzx3NUA6?4O|飼aTjVȣ0:0\E!p{v"-Ƭ
T[JET#|ΗuXz9M_F绶οn/cV
/p?CdGwf+q.Z^G<4  c]"5kQ	#(S|nD+mc.
9['ݡ(B"GŘ)G9BN$)l"#s|̷ݮhН^ѩ!e*&Sq/[VzG{8k}o(#m>Qi''vj-j;/3pF::*{JhIa5]M⽴sN6fyx<	W4#Жj.84P;*{iX+4;rF
A(ǣ6o,(=i¯w,ac{rY<lY&Og#?1%ۢȫ攲!ɯ.t/
Aں205̊й(m`@;jkF8nI9Ew(@
X>9-m
y6N#jS͞HnpḪ<VN97:S/3RQ3M pˡk~$|۪Ϋ8w쟿XTnݜh^~e2*b?ԇHH'1"
apV [qq 5t1c<@6j.pY>^[Rv#wGЕ;bÑ)<BNZ߰teeUA<x*&Eɴ%9>&vd5bs,9䔽2>KJy
8)tnqjr"R	MxGJCm'+;1joSYa8Uy\V~E{j!bFp0*)̖CE>[Fd.pђR( r.(ai2}U^KJxYa^iwo&03ǖK]eUƊʆ&0DRvч kdzK:>EöB;Ԍcn:F8@LG<֚,8IE&buzx^NKAi;nj5 $.rtEaPrxLXVf%[ıX|UYt>\6>)'`Μr-YInoݑF]d/Ѓu̽,Jdg5۴RaM9*2hpV`\NC	kq0"RŔr+gNؓI+F<<m|our|h<̺z]3(UVG F-L49hiWq̈'*F#1ciK_GqD>7,Aa1ney+#]@;^e,%$'=A+B|c%D_tOS8WE!ŞpH6rFTĶd/e?̅,Alf}fG[`%l IB{dd9Iژcą$?woIT{ԩAZ<;Rr<êMp2aMbL;bXAN"&gN{+|BE~Z"Q0k;n.Ez2`e%î92fWp:WYp)[lT3C%=ٟ	l=fa;:Gwix4(yi-k@il6)
6LpGW͢M>S\{~4ǽg|QJb:v$جgaxsm*%nJEt}-F`7ۮA3e!ޤ5bO9;EI:;W."Ҕ:[ .&.`zcj[3*(\Tb3!6qR	+,ou蟣CY}c 8!V%6Qj:Sx#u=l#yΈĥ:#>ĺȭiڎ!X\]P?\/;ʔEc3Gsdi6?h9)l-,yllgБI\JO&FʩU#8e;o(WZ5LOr;.
bj!6i(ǜg1=a3!|PaЍ&Wt沈@DT|h`7&T)!5`v㱨!}Zu(ԅ]gGD9.ݫ~*eٻ~zo5Xk864
BI,6LcJ7tlbv IwгUk򶉈^uG]Ǚ1ߝ:l)=l,`aG<AJUSgJLfNbl`aRUSPȎ-+m80
6X`SO~}T{"hn{9\h=&tS"D[E9,S-˥ ڒeHU `̄⮠O#S[ݬEͺ	ڑvLSmiJ"2SY@D;j-n9P\z撙&Yx7j?+,wp9)Tl̷{9yQ} e*e٘&EƐ@wBǹ*yV[~#J5ǥAxh`Ca¸we7jǰ~Ɲ)/bGqPpQ2yH٨p:^o`jˬX	d؉8K+ 1l_ZS65r"`~ QZJXgQr	^!\SdD&x7C!m^MG;W4dr)X!^Lwg!9KNs5f!Q+{nGzr nD~[dc\pJ➦r%v10AdBY'c30-QS-w)X$h4Ҧ*6LjH,u瑱$ˢs(iheu\Vx\Pi%q>w7\؃AtKjtȐwӣHypa,TO
	q!ԧŶ bCkz(1=Cj@pdRC\*'XlC2-=0#8|e݊]B˔\w}FT(7	X34̝7xΥ+:ji	S pV"ʃ#w1V4Qdjm +!Ԛu6	k Z
(w(pi~<Mw쟿u=xO_zo3j),+-}sx.0)|!u-q;iN8:#
6U9vVLfG%0_F{>k_-GiLh3";s:n}O$KL䶙V]*觞NR';">ZgUE"6`I>!&L#lw[Z4ǧ#eah4webof8:B9-z$mpa&\XЁF+kl	<g'VmHp]^VH!{+r,rtB05E@uن<
]|}~<߬s`ַR~X-Aч{B!T),,ACy&ڶM lʺeYd4zYs"Hf{fjiZ*o+>5GYp4W06OA*gx ֵ'y6]2 &20*{5t]ϫ}ޜf#	-έ|Vzal"x,"vaK
^SI*VbZkU pp<GHHA,rs4=y١paxuM!ȦQt/ihıqAъjlœx-R*n9ox3[6KlK!OfTv8;}@cה>WnR%nQCǴs|8Z̀rgώHEHxZ;TJu9O h)+4'axfاw8rZV3*ɛmzB-2aҡuͬ}U>SBfњ^jV`PDRD3iOJ뷳Hwet=~Gf+ ,ƒl(,R4K9,im>)L]J#Jv_Xъ؀Ǖ]TvH(wyHN4sW:N8V>{mk$04)dpkA(ʰOY(h{rL˅b Ѱ[{1+WtzHG	 [kw6p~](h au߼X]rBR@VV5h/Mɑ7@SV
0EjrJƋvLqL\oBWaYݟgK<&YLGB݆iB	'-?WN+sP5툸o;-8n=EdvHrv~$"X-U==nGww]"'5G(\tYcb%S4NL}K-kU 1u?GjXZpV F#mx*;DG4[M3j&h/ZnA.Ř0ZRȼPU	a{re;xoh3/]Vzgb,'ȴɈk736j̳sӅ#_ҼiݡI,xV9ju4`s='(WSSYSO\3Ψfʝgg-(|`]6-+Xde]*YfD=\F9:Ы>t mnK3øyDz߮y)~丯IE'4u))L8DHUoh6܌fᔎc|4Ifcta#cΦ#Mzs_~?{rl
7_?ݹ~6sz~>Az.7]6Ga@Ҙ7G\Ta6}no3ʰ<ٷCjM/wDSx3i~\'^7|ԏlȻ<iƭ23yy}\Z׉-뛧%y;U~Ez9=H>/$vguHkƕ=}lOT7LB^KVM;H_`V{o"梭nQ=7q'DH+/N&FنWyݓsQ+^ucVw} PDNO9/eYyAw7z:`Q²5Dn)cb`.6F&thXO4)\ΐ=am	Lyх[HJyQ([W jՊ[쫾jNVhsQO+g4[:;.Eݪb-YzCAdxZװ]NLO(S0Ob@{{C{^z g)X0l 
îԥۏю0\ME_\^X2_x~vYOrk~OwmI3[hS.Z3Pb	I5ޛ)ٲ[&e-+
QT0UD8r͞x?uo=P¤p
S*"1]sfSLYȆ}Iٹ09lR<Ynq7=D3qti _qϋ(ݐ'y{z䌞88wG"cu}Pq`ab|Y$mq"&;Zrw'.Қ_k?_M_FPirF9?öLkp|Lgz/%|@E,rg}b<=od8Lny>yRetp-"Y$Qr
+$@֜ %]k .V#o	/"9jb<0AW2->TGM:\1U#LzSjD%fdpȰYM9 P!*7cdM3>Bet )ܷIHyj*mar]	7w!,rGD1 V6]/+Qd!
EEV\v'Ϋ4Z :}$|X5j=ر(wr,=HpumMT-U0[K?SqE^tO.>YF𤱂_tWWgO\`]!֏sp'as6533Cm]5Nowl;o64П)/3v⫳{	k}F+{w_,9_ٹ}h
dފϷa;>DK}}Ͻ8qyBiW.@"Ľ#9S	y+uӶ:;'\&>8I/ԾuaGk$s?Wp!WFLkzdES;csH?ĝH?J_\ع?/U=A,N	*7jYvEB?R
`/:OJ.!s{v_~}}iXXc}vCָ~u.]3!h Uʽ
K?m F Ĝ2kxW*63ȑӿiF88b@ˋ+KZ`#`hH,fνEJ'qԌA4̟n/pS	9O:񂧓踊UivXqrO&rŗ|h7X :0K|*)lKn<,NUjrs1v-:]\\-<K/Ng_߳<NS3sW5 OfTg}G/|ȇNocby6ϟvoÎj@	;K%^wbJ`(b8QicS.z=Aba"i9~`QFZҳWsa>3?I5zAOHn.yRV:ӸR	C6m	f],hbTGՠ8arN~vUS9PWa"	Pp@qݺzIu|׻3"-v«J^Ui-OkۛIK_Myw7pK|Q]^9E_fHR)4cs\LU2FWAf2FDӵ'"ٻ)`΍*g2cff"IC>V),5^/ؙsmz?s9\{|90ǹϠG̗Є/.|`7W{<A$~p|^]?o.f?L-7-=FM9+4r*3#Y)jJc.UD:/c-a
|J fuGi^&k?_\O3ǿ!?kRCzzyH]5s|:ue~F{/I+|Kt$|6k~\]mha~nR_/ܐԸm*
aqݞ]a,	*PZ"U9vu*ggB2]p1ъtr|y?}߇-pwmG\>~;W7%α/ap	}Zo}<Roe%	֞q|e#KgWk)i'䤊w,[dE] ߟs=Eb'h^3V)^/B5@&&â"E#
tIk+pb2^`Uy5'W7&jX핶9h8y-ra'Jvs%Mfh#febSҙ5ܹhO\?ߞ.<e-};gᚖVjmh1$Pl*gbTO˶!ތybIfRJa5ݗhϏk|"'ey`MZUcr-vZ8G4g[P6:hb< iZA{n=zDnrnY1cVy!ܪ}T}y>Ye=픯WtsVlXWGK:e `/	UkKÁ7aqC/KR8"6Ds,=/9zpn$5)x	{dV;}ڔkX"o*rjf"3AY7!l'sW|Ǿ[m*]Lȉ/i4%GŲ9&a.pVMyv(5Frw'\}\//cK=FEI ؟qd! ȏEkٹۍ(6 WNi̠#fԄ`JQ
SR7ۅte0K4fW*~Y@bK91Rj(.`Z!b_c~jl[o9L"؏YO"QٳǨw}䅷"#ofh|Bp}j`w?4#6CBzhQv5K~&6mEYWVN!HC>UD	\\uW/m.]y<׭$&]IƈR!M6lab]U~ vphGbۻԞީ>J"'
8_Y\3zB.7~#>?sw/_9ߐ~D|ᑯK#yKngGVs*}Dܦ.Cn!?`8j?fn\4}z?# 5v#_t'S`Z7.<7i;+8Gνe#2'8#Rky-@;Tx?"`X}!0'g$S?ѿ5:ٝEoa>Cu:k^:#4>-.$Ｎy\nq|t1?Ƴg7|MgEy=GѲLǩn?x&)ί/mmb0}g#y짾Ǻ~׫uyE`ݚ!Nܑg3Y4X,M\NDsM|CM%2޴98݋Vf>J9q.l0Z_GZ"a1-BÂF-z0#qLqXg[k
7r>><|_*<׽,&)%**`3Jc([aTwԝH|R3VQfO̰j72~YzG9tpQu]-fkg쪁:g.x}j1P4KRfЉY&:cjU.a ˖!bF
P<2ꄨuDJ6Zk8/f[_~h~4I9	cG˘"gFaDYyd&6)a),PdQ	29[&=]hdfcm=Wa|Fov\!@,,
"[zw7&T$· \XO:_XB5G>.bŦ(OLI`}"5EM4߃uۇB#zy(j6Y#\t"Z{{qZ=k_'Uα$'ɒ`UXR
	6"p<<YRAuhĘQ	GxP_V税oOTZt',
HH&H KV7PY+ˋn[2^ɍ`\G}O<UՊjY-ewx
mdWRv&bF!D)Q^#B,H	BA#FPZ<#|yEyreF1۳]2m~tP50jBě0A3RbJ{c`BH"p:
hT\;X;֖M#N!TpD"HE!gfnjm2Wq,ԝ)hGh*ȖV]LG 0kh+f]aGXrAg#=~U9qOsV\,knXMTL`UX!4)De{X_^B^̭ՐߏLM<K2
tywt<oD\zD7i7Qk|F	RT#,e3u*P?=l7$Nf˰8IlAqKݜO98^R~H$fsgoԎه妽5xf7^B8ݘMĘĊNRpbB``ڭd;&Say=]?wſ_4b3B񲫹E%Om^kngZ4A mƀ!uh&E#9śˁQ0ނz<p*lQt3][)B`zʗCjdbժ+7}b: ,jtyfR<GdHE/UN.!Fh~O?ͤ\g#O+úero
pOFlDtC1uQOz&uZӐQ9bFFAUS-pgܣÆsW|OxDDqͱ;/vaG=sn +ī9:x6JnA;vƀ_m>Q	6Iy8ɗi::.!{݀5JAٴut<tQbLPMlkKdX2~L^ĖG.PO!ؕm~(d`/[?]5jUl\Z8L#j|=3pe/iU9X7Gp<hWXQnq6_b| bb TYc2seK̕Zh̃Ep:rq-=F)F&0F6bsyiƊWVv7˩n8.A84vzZB*U=B]5e>#vfg2ωC$Y6%[
qAS/d	ww[?bm1[?k#!j%ACW1qnF΄ȏbE\y:;6/>oF}EХi=ecl#d``:m8?'yiX}#-lψRzK]mza.lџTy{%G&mVp؄fRlB ;Bi/bᠯ}ܥTVMSeD<ve.1VRq*Se(Ȭ$CrN-v %?{ /kb4"oؖ߬C?iEx
FPXH:~v2R9LQ)\ځA-I?NnA4	ʏN/>*t7FLIUzVmg.1A9fj38"(2L<:X#ͻ5 0l]oX)DTʝ<5>eMdB^2]<!6D/y?,K9`C;^mD^Y׀ZqG,znO yݐϾ1x19$')+e]8izIbsrM,yy5^¬}9P[e|-PE+`W_ڏI#q%j/[(&,ʋKԪ4\湢Z_1֌r̸utXP;ڗ_u=|{iGL/H0! j N%oh]t+򴭘hMk0	 9Fib|οW&/G/_M_oCZoU~ҥ<Z
'Fz0/7͕]X6.{T4}vJ'lrSf{Kw|.bd5W5Q. cW.FKi,VW{m$5K"T!If|Qb)m+;6B4g}]Qzr_c?{ZI|CZ4[{t\|gbެ`'#ylϝWBwhkP+7oC#g4;0``|xM8^t5TJHwwcx[|[['L,t0`2wnq l02Y	~_yj|}Q/ŽwhG<(1p/K)+*CBVZоd'U˂Tу$h)ArBGYh_aS-tmOz!/2KCr6օ8AĠCoJޚ[MtՉ"r9KvsB-hfWUKSR4 F;ߺKPMƕnoݿsxyNL)Vaͺg YC(l5*zXU>{aIo8;kxԛ駏ߜb,CdȡYb1_XXxͪl@ GqtQƵCmЪF'\-s&N77PEp hmlIK\K͇$
JbeC^QۨB~IAHB(ASY0|Kτ8*#9MgK,36 .Pp[P_M[]H%)&Iz.
E6_?O:Dp֙bkVGzqA숋۵\Cj,i+myKQ >0%(3@5H5VqEt)-־{`4@i;	\j.YX.ti)|kvGx;ʨJE>_8$. r'G+Iǐ1S䝩JKzoH9=ܙQrMHsSDFFe|m/tʳzH`OM)RLrEEpp>VD>x9+<p
{"]?Bә y'e6a7DP/WJ2!6A;5UuH1چ(8:L }kў|W?Zyנw;sW/%dImefvȂ-!s쁖gԪNbp¯	Komv}n_]+_VVH|qL;{7Vj\;rDzb9CVidxS ӰL"H۫ᬪ[}_?KA]D7!rx[ Dc"!DwGL#{ȢD/xJ{?ǎ;Xm(7=04x2	{"ٖ/HJbU@`&P˒g=
FYQpW⍜3ם)A6Q{HRJ_z!MT~-uYLr̝v׫Ԑa ~0ˊaؾh̗C>.ot{sq2ETս8##T۹b`Pdx\lb#g8S&Y8Ӏgl(l0;s%63gݗђ2C$$p3Nys\L	L}cUʹ8 wunϷ9w=wϷ?mdcẲwkmkAEjS<T.a@5Ri 
mdXd:Ic	$Us& ݆FEj^^9+,30$17*(F<Fqo7ckx\p+W@Qkη_~N84qz+wGδa
iJdG)89..U
d3
X!.j\B:v)Nz1kC8^=î}
^Z	/ٷy֮.A[V_r=͐qk	b=5-8cһkR}[j[FrC{%<C$y;ΊaH5Έ4tl=xyylyƜ!~LtFERr1=̫aiu`3ٷm+Lr.].(Y"qרpW~Wg	e?P2lM%4%hm͇MNB	zƜ=3iěc^d-@U>ZXmJM52+]ꦕ? מ>5jM+Yٺb\'F)OL/N긍wJZϮ\Nq2&%2!QH
ͦvǛ|{|="0[1?:Dh
vҬD`#&0k5kvn:T;8<r:[!ᙼt`
wiCe֎,Ԕva~;-܇\vNTY:KyX:0N-;[=RL0|Z5(Bt
 a":G^$f&>$a~kJm؜'5pW+f-R>͛	tNĪjc|<xɕs|g&G&沌r#;wl$VFiH :ϓcrcF8ZM</J%58 i	wcvj#og/ɧO>B^o\Lv@%7hrۉH|9=rcd˯r]O>UwROW
0p5jK&PpZ-vFVM94E/3!Xu 'L%~J*KXҥ5w:;3RT8BA°*պ(;Bx v$a\UC+B+3yT}L??D3xsAs|}R.Q3HAZnUEK53eIe1Ķo=rgo_yp7[XY)P }2ۂ8R~vT7%@;yt, y?gXȄTFL~Ϸ]^S^[saGIṚռû؝:n=1)LNzlR)ᐥڢ]e.0)Ev=oQl܍ww6@`U	l:^-8c]\HA)XNIJc>dڗr?n
osnj}5P˻:ѷZK3[eB6p@sr]_%n&ERxp41sU9/'^Tcm}y~ A7%$HL%X%/5~nD#EǍtNjsrٌtA<.KO5>m*U#jV}/VLdڄ'kְI8?wLeYAs_MDW+9MƿE0#OC(dr*(,HW~Fr" q)ӫ,mJBS=F	AEj`owY5(~i">yh"p!)"
V9}+	a[]74{u[6D5EV(/SxMDZ?4~ s&nHS)7jsd@y IV,.@	36=mրМC=q`gduSGgv#CF<1ЧY}rd3ᜳ< csm9Lp1g2f}
'Mq/K@<:]iߠX~xgǝI$1L1C	Y/i94R)~l%W Zjf|N٭QO	4}dS_G~$ɞ,Cݢ!՜u%~bG$寮ϖae8\VI1?=|s}WDoTߺjy{>Pmڣvli2VΛ%!'s͈Ak;Z܃/?Ԓ(5s~:^&o>cXOU{bY;dFi*MDGLCң9
ׂf/,y8E9<cWzPj=~ A*lL^<+˞'0QXUBfFT3R~)DчÚf;uh2;@-:lae5dZ#}z/֎>Vs_V]㵚Y{(`OcX	<#ɪ5ܟǕ
{D>	|<f%зF3p~f_I˾ee &\'N=Bj5 p\Ɣ]jo ̢Sq߯ٯxo?$-e.\ dy@Y'1`0*!1_T@I%t?0p$ `&$\2]a9s; 0kûKj}jiVlo'mXQJjG/!1ׅ̾aȹ{JwI;:{.NDxKޓDvYWe K@d#<wC>]MIezM=~[Ȓs6jZU6fmٞV\	߈=X%*KL<'H~zN}75ALOv٥W1âpat`],HmBUnxBʆ
$ "G紻*0lYC5E[ӿB$==HRkUʩIDZٹGzR\$A`//=T[LL`<+hMWB
E/tx&zEw)Sqޔ3`P؝$;ش0*mbC)"Cy%>o>>2Q_;\EGG=5nnlV&M:SE5!'8،ŧx
q0ev>f:澭>)O?8x{|	=2ɔh{3J\S iY	##*Z!fJvAk7pKPh}~s]3-tQb1o#	N@O=2sgEt:xv;;4w$ӲwWWX$~]7>pM(k;}rHE-eٶv2 +I;[p9r蔳t:C^/2;մ]ܫ{>Ed $SRvQZDN..2mL .9 bTחفkOomWw>!qS垷V%T%p09zm`춭*PL9#."t- }Y..?DKjSrg` ~h%މ	zVR7ߪN-a5yfp;b[%eaaǀm$NS1jSf[&
ks2c~ 2+b튀	֫绬O쟪&vIܮT9fmKyߪ(0{
lhE0p4IF^h;ϥTo0)Ap6JL5 SfY$Zs-epm	Q~c@lFVf[jRW./[6N&-![T¤Övfuˣ軳=h{X'ЙFCU<idK-W.X*zq?>џ$G,?XζV(UIku;CsM:IXhSAx6stcVK#Joeu,ptG.>&ʮ6&3GJTmLٻdXK!!w)+żxpi>{>VN2Ԥ
@bCM{W lTG(09lw@%l~:ui)rdaW$x=S)oe}APQe@6Tq9`;U|p:rEfSdf'?붗Qؗ4-Zf"keLݺؘajQTiR'N^IowP7n%yR 郸~lu~sf9NwmWz~ŵ?VY;id>/)qZO}C4HR>J*փUx2ޢ)ͺ6];$ѫﴏW$)fڐ+?m:MѦs?qvՊ68bEZY'-P	u3hiN?3WROeP\.PцDR܅BRApm7v4DYr		\Ȁo.ڡ-b&W篖ԏ@ uig>*9l[ގ;o9RĊːk-V[;x(--)L
!B]c/I4oo/G;2&Wʝώ{[屒y~ ZV0[ss8[0c=CV+t^Xt7)J N`Mp66VFro^ {9Ǆ#.<(=;a).\9"Kt`i|:UNB2rc@vg߉z<c}I??M@@*5.[h+I^8]zhx8`(&1e1XJw`sibjU&Kof^ .Wwߖ=|a"3$"P<Pb1(mN,;gv=N*s$o_7^<}8$zw(8{Ljv·'uKE81hMԍ rAZH>l?yy}q~rT=wa"YR+>d  i)K~p+6\e 9~}dߕ
zbZv&$Úg	wĔ,3q 85:8yK^*86t#1ۘ@?$E[rZezxir'bµu\,rTsx^¡=O gL{e#J}O~E_=\32*-ouyn%ũZ&ݭ8dw$(NR'H=2s[="w-}m'DLKe7Wf@!BS1Yb0Z
Un;x|]؁t\*$+kޟ5v(9J44ܛzÇ{~yxzM{)(* )ssT&+n"I݂TKo=zD-ΗX 5*FHA<_zj%jծt^	XnJ,4A"qɚq\Ҿ-ABߩsJALsS2zZ0G<|p6/?|-m_eTmiT[t})eJ8+zZUN,3OT+hFlF\F|A M_QS$"{A,F^XŞjrD,dOGJXtFA4LuzF\Q!<QݐAf;Ip>ylq_6H^_٪np4coU9nrGܕؔC}[`+R`oڪMsP *["x\zB[9è/v5
8\/یXZ/;"	3~Goyv=[(}i?J?+;v(r-%ŀ_~r/mC -bsvҠT;RA-2ZXfshZQqPБ8'aU+Z-lpnڍzG2:8F߼6~)V/^~"6ǅg K^!rkv8ECD4A|p.!{dAMcokVIP!_\Opi9{-Dj&5-PIXP35sGL:]e%4Tni 6z'=yf]\:Q9,(`OH
6n`<꼕M}8RCߣQ.xFpѱy¹+5Z>.1TyÎB-Hͧ<fB?໩݈y^*6݃|8X6EIK6Fё1NG
i8L5oa&HIvG7Xso}_k]y.Lt+1pCڪYyR6Ѐp Ц|c|Xe`<4κ6p)`MŮ%|.FtCcg;L)W2r

!] n>bş$Hl$s>ǈ+<`wğ޺aJQh2za^,}#syxZ삒꧀8X4O\mwpiRA^F5 8_w=5kt_'Ǻ%hM|HşIg-ln룴4цQ@#i+L3 4r$F^z<<z)
*ؤ7NkQ@p)RCsZ")³1_;鎋9d̕|b|ukju[ZZ,g.hE2b1FC ibƶ@',p\E剺җ; vZ-.UGë)R9ɀd@$փNUHV\2++w`V]@xя^s_EN|
1ݬ~pI}/x̷gT:#<tCMQ),dpxrP
$ؿlmK0;j9$J)<yazg	eRlfPvQ,m̐ϲ	b*s"deA--Ed|[*xV KɢdwExD%v-GPf!ʤ̍+<&tt)ݴ<dsEѱWГ4RXVGikjwC׌埈[}.ԍҶ S?=.g7l,&9s-edCaRE[;q/'O[qI~ʗF7nV7\ZK}ns8+0}lS&g3eSr,ὸ`b}Y{u0xKSe])Kq`%X,3t3v(8h
?T<H15p/dBFnDppWϷWWۥԱ+mf/G?=1O^̢ZhD&c>34%bSףk  &P((pL#A~D6v3jDĢ::w!ș`ɢ[ĸlaW 9"6ez`vL{ux|/~Oz5~I(7yMIVALwy&7P'D$i[ g9@98Y	\V@^Ԣ[ǑG<st(Q3DԞ}TYW6!#%kq Ϻw5"$)v!cн빥0@P_Ff(@up;)8ú"ԜϺ?k0߱=_!s9E
xzT5bg[	AY wvE)?[ëAg"޺!MxEWN30t\$`mK클vK7$		OpF_*$s,8ۧdD..8Ddu`DҀS9H/LrVv {0f=<&  i9yl/_t=>/WǸ\!3ݔPA	dL.EUxlVxbYm]6,Djz?@pqk!}3gM^+u@Gq&Pdn-pFM|;/Ut#FP0_lXmU1/{YP93Zв9ќ[\2Mãw	{̺J휳@s5}$}8,2EUp5o>ktm-ףZui4 2-	Na3깃l"Um1IɨbZ]Rw9zX`7e%][שzq_Ldd-So\򅨟1TVx	Mj֤cKR3Ĳ21,D&ztJ|iN[^4M[]sv&ECMHL)5PS*YvB5Ur65d,ͪ23yۤoyr0&;G۟0ـhԧdb&d{wa\4xa(aRвz cw2oBW)_>yEy_a^^F3IQ^yѠ
>oj;h%%'&򋊹|T{ N_
]ulFp՞މʷ6x;f]7ˡMo8{g~_.Z[^/۾8/]|'n.~}iNK= }6N_$z7YW/@za~/?O<_J?_cczXC:qkeDr4DU[^3he[]۟&L1͎aY1Mp1Vy ۍko􏁂ޙ$rN(Qȯcro=碧Z4];Y	#iJ{iZ[7+Tnk@{_яF(֒'b 
5:Zn3`ecDNuMX09R²zVkf >V)ʷ2er5(o/j|=$vHKȭC9cNnmZRy%2OC\!4IZ4xrEuX-ݶ+l-LvzX.7y)RF+he``j[9$[tBk0ZsM4)?mvSO­s}E__H"r6i#HKWxx2XY%btK7pRʡY
 8ӪLc&^1߫OrpJ~jBн£^흿ė'늹gny|YHBb;@\Au![~%5?7ߺ:V"wܗ.><~,7?|gƇ1#/֏wݠS?z.+=Պ|CvXvW^422eW>?^w#~WkӯY>{P@YB Wc.烆xT?n޷E^vk$в5tmd bؓI:ۜ<,$~Yf{=nJ\!oD
Âk~F;]ߗ0&o73[iK<辿ûSR[sb pyhH
hr[O[ b7xpQ܊6C`GF#d=%HS![eH+i{lRyvy2'b֧Z: 9$%CXV2oY^d(E^cςS¯	FJ~W5}Y a4Ĩ|iPB~.א,ߢ;`IsCJoQa5{7q*+-^Hr?%,|&>qPU}߈q+
ojH2Ԏj=V>b{x`}HƴaVkLf2	]bZ/ݼ9p8n4s);Q(\>룺=!& cXFL$@*]Sf.ףٟn;b\vü|ZT#״H:r8ɩIJOKk0(#.m/-k<N2҂T٧ȼiwwۜ8(\Yea37fa)5V`	cVLD8N=.2kQ \=| E/Qgn~}p},uBq7b]WqGCՍP>ƉJڝF3 tn$ 3$mhU?Bg
sJ[>w50Me65Fv*t.y܈ ?+)m֋l#
f2BPXI$R<sS)q	&	L2<૵G\ܫme~7gIcmk0}y7B%ę:m'wf )hn9>pRBP^+j{P&LL|6V01GkmFߗ.ccޘȿݘߛ(82
A]䑤'q
aZ~ I3Q/t뎭(E+cPXLxiKˎ/f)lim+.3FCKfFQZʆ"<@-lRhM$I[FЩ`͉!MUZ#tYOƹGZ7whɺu]r62~PFTwu%$36%@^vɣB=-vhG}]}LHN|6%3zy~d5*CFu~1cgNnyU1v "M%)ſhqz~4#!L<CׯvN!3Pݭiu+drYam%diQ5NF|2&#h|EMEwHFuE&ήKWm>zNt*k'}t/87ncKQET)r͸WPoKSڪ4X(/pZj/ͅ8m<t(yF-@1*COHo."ߙ
Զ[PҫU(owin{ax똷y29ns~ٛ^b2d/s=fW1{h?3c3˦Eu x:@\0;y{=2PAeS1%`1_7VO~+\KO~FbyUBwh|李_Q'	ە*QqO~wp0Wإ,Gi3`ՖgvdIɮPhOPϠdimOff{hEm6Vm?b|/}M;@}_0t(qzFT]7]{t5^z~y7t3} N`yJfʞ4UB|[Ind;A|@g'+:MRQ(e|ϯכ_O7w犅B:]mS؉B$1#Ԯgor7ZNG@%pVG<D(90swWs[Ϝy:[5ѯƑ̙Z1G$6OBwbDޗ㥮U2@e:Zɦ	0񴨠#]z\(-Om+|+pWeMZ;a;$BYҦcV,ظՁ"]mhu}R.:օ2^7:NDw~H<4|KV1uZ,fi@\TuqbEb͞#pB>aޔm:m
Vk;-K)-=omΟU=l}oe6R6pXlܪtl1yN3Z_	-6vAUpj{uix_m4n{8Կ-y?,O/WˢM/&_/0_x=p1D=|eDnRTR"WTGsV%l6L7[RYD_m߲j	¬-5KQ6y. L ʪc*<<&wϷKOˤJ~
\Пn^hw!(Ш+"	grtbX5̝o9Xrn6*&B^4eIϩa~G50?K43wȊjFiWϰ<O V-g<!
CfKCAҝxy|1j^ewSCݷ)dІ(N9 i< ,MWVfP<\(\q 9g|6{!')Ъ}郪>){+;f4TCÂv($v: BYQZ]S`NI5)҄BdYPf~(ܣjKa,q2Ljd@r:zd6cZi0u0v7RmE:@/	.N[D>}wכˇ:@ӂeYU1`r"r]Da/7  b+yY"PntBȜ0V5Om6;d=1_=!!2wʎ:ܮ7!׳Kr2a@@]څmtc8z)~kmGԯ-TAT	ۡX8B~lbYV1Wg	dQMd-HӧG2˟Y5 (Rje3e/`mecr6K\yʷ˙]dByC9܍6AkSc8YޛyOJ%Sc;5<[NZ)XԹĢ*?DOAR@!+֜:&7efpLx#ęUym]+tI{A!G;L-9mC"6ΐtʐLFj#\aXʊD+%iZ
c>;.&~zM-%+UZ el(	q݅z(+)FLZS2ҘE ɳ$ߴ?Vxj|D>Jj|őb1DT8s
غ$mi)*VDef1cI0NР"Q;Й=bxR2fimsbc"RSDf7Lآi9Grl+!C^ihJb5MIͨ~@RT_%E6/:yEf&sڬ$VMˇecx
ՇVhC6{/not{v:9?rRil$5Ǳ [;]v#Eʭkl._eIT<-BŮ[;M\Iue ^r YeDI<CIŬ#͈ʽē8WOîQ+!`mNք4jB@o"9>/ o1??oeW78Dm2?󶤎aLOa N,0lvl7_ۉ5c\V*u}Pa]ҵ{˅o/0ݬ6˾/6}}0FZ%/Vf$<@"N7ur$99cև>,X"5E$p7ah&j[oQ/[7<_=̈́`*bf-Lh%MIw#36x*m\wGO6s2~&QfhXN_׳ɶ߭[%[	&!.B	݂.Q=[h*ezmfR$*.bYEo]gڲ1+sci͕,"q i3yEKwU '2DAKI 9p"^D%xNK}Uxqz[^.^O/M|ΒLlh'YrInu%INASeWk(=:FMOoJeeJ[@0kQ~2:*agi_w&̓	OBG-dE.~@q;n,zy|:Yd9
Ydm==V3ddGtsO|kL@YotrB;ڝ/ؕ@kbFxBYB&rN0LkztIOFоrus|,=tپs6~}w0߾E{??.2"d+H@`ʞ4uYEDwmsE2ƙ\icN$\6sIWꦧA~|e>8ؐ0QJiGt|nPۊU(Է;-
 FMFR,*mƤj=T4O=EYO9M>GŐ_%eiaKrgE=WMc&\ta\MAPJ34d!O`-%fl6yIHMn)h^~i+5 [0<3X{ӓ<oIT&O๊6f8a'$Ox`+X	8aѢcG`2)ۭ .JuVovw?,g#̽5C$?g_szy"QxbȜCgYH!UL%-z)P;؟d@Hs}S*pwtYG%DYvIWlaUy#2XG
;E4j%W:Z/6`CM[1l@tQ*`=?uw,5{wL1fB3+6VpcsJlΜrhpul 1|.-}oց+io{xXQ
8'9+d؄a7= us#.SdSn(.uscHO_S^`~nC8<_R_@3/g.|u>;1lvQ뼶?u1K{~Yds8
Y@F4;G#.2KŶ'6W9H:	%29oR=@n쫚Jz""XR9n#mHx}ޡh~n@,amd8׵Eƀx#|&WwW~Q9+hBؚ*4mTh
_) ,`CT ;HnKŊ橧8mx},ߞM7gތ0^(vg2	Úsmdq`VZQ avU	[!>#P3BTYQ8^]_n-.zQ:f%m=ikYe2,(Ag9lmj`uSVt6[kiם"!ã9txq" cٔPh)ǜ\Qt}pKx]kc̚]E<^e5u@?o7a77hy≎.FJ,2ͼ1rҋIK̐1.ʸXE[TɴbE4d5L8H~pX3X3*HWBs1N[&\̈́tLz#ļHLG=ɸTMjtIZIWi0G/ԝxW?H{alྚ,Gp	lq b?u,,ɱj*rY0
ŖdzS=70ƥ8k\3FU_uI[7F}HW5W3a!3]~v!%t023"O#nFMiArN;[L?V4S 'b)ryU+b^~ξ|;Főr,gkX6K!Ɣ'<᝸eeX[qء :s.c7y73?Wz`BqR^&k&Z_(kǀ%aI~\.T3MKۤoeoqXś]fNHdj\`ʂfbģӕ&AZq.u>,Rmx]V~x^?K/WSWe;ٕ[Kj@n-c:j QAڝ7vHnض0>`.z 8MOI#-Zݶz}|n-7gTY}+}ňt|2VP&S쮊YSgqSf.[MFq"$) 9/@z4ZV}(sIWz'[Hkz6ɿ2s_'D{3a&=pla3~I߻߇	zhD3uv͈m@ZR'5pbTԷ%eWfz5nOA53^ػ*a0.̉gE}_s:%-ۍiz4Z)U{f8n3jJѡU|rtj]W;~q+oGaJ̞TL@9z'J7&&:gw TVI?6x֌^"x8+m-1|@sv>ݧ_Q K7Bu+~nCMu'$A6Z;zIB=l-e-Ѝ)Skyٔv>9\PtP [qAw!~f{Z{o?o##6\8%k3MMGvAiMF;M&q)-i!]u`dOrȶwggxFN'_ۑcKgHjU O0ҊeMò3BN8@zRE4,a24CWLKvգzO2'P)Q?XTs91dG^ z.Ww\-Xa2zu?]_v<Ougr#sn+^[9>{_~#=xV?ycV;i׿qFNo,v~ךqp<A
rӎy>ZV(ހ$1CU*	. ~oB9qn{sw|_cD;yg/L'\tO˷sǡ Sg>=&ӧ#c,\;L7J.4_c.T\y]LϳvS=_>Bo'?.	/{vʷIQAܝ_񑂵~%Y_ؤzŻԵGY7ٻ!|Ԝ97@?_Ln?|vs'c:Wn`%D$.0b4U[َcZm;Zm9jRJ\~+pįѮFǌi_G쓼/yDT]%~_sO_-n
M?{OK]^w1coo޹aoo<©ݷuf͟ u??~=۔DgM[r3eOlZ"A(lkvpdS0f¤UkamDXX'U̾nXwuO}x4=MCnǤ\8F</%G}.Y_精4:9f4FZ1agy[U|29r~m0eȲ)F?h51#f1`n)~@q\fרH8mUl-Ecz¯)Pk@wcFo<Ll`9f_-umZY O,Sx-)Uu	7nD,OD'}yZs _2ѻ,	S`j@衍c\4w&i9]ԮrͭkJ( ';PX #KՖ6{/(sJɕ5M
kpBUH5 Ub!T+ۊlAACI"׿]C7si~SW
HZd3.(AG	
Py2ut yX~NW:F9*!2x{}z[~A1Yp f
,ĭ.w*#ZbNfu}$fp1ERPhW$AxbΤ|77ŗ'4-x>A!M#j$0>$/Ŝ؊XMf&r>[GYS{[Fm/i&wbk~Ds;,%"d$٫v)`ӭ1p@6xSvdǳٗ<>_o/Ο	=3o]l~Dy7"55q@j/NT8TCIE8btgFZnn䁃9ez_f%t[!BԚ]c3i`՛Rg	ZWZfX+'1]~e97|Y<Y&dBYMM_.ȋU)LsVC,e3Ggk`p4P̜Tߔ~cZv@-Pxz6A_
VNtZoQxUӷIsmwA71Sr>e
a0▼>ֽLoc33 eJ~RLrZxتվ(X3ԛuRzelȑZ s}m5,[g\,oO;OvA9U}_fgw #1hSNm+$lXWU4[R,
;sDJCI)ʱJ?p&ӆDc^<Ϫ,\capɜҩEQ>hԏO덒jI긍Dtm3ە:o@m^*^T$fjΧ3jN}I,'$NV[rFٷWN$ip{/++$n+m`ٛSx+@":|7Aj(f<i}LNo{3|bϬ6q.&tUaH?jHW[#'nk!ݺ噿VɕVB:)BCl|%r=ķMdFtѽͷ:xA/w~z05 q4Qwqߒ-58ƙYkT\3&t٢ppʐK;7v i}Þ-7~@?6L`Kb5 qI}w |5A!>w];<koY a/:|PʁV@ۮ[d?Y5/|
;ɧ* F1BYCM RO9ϊF¶J-84j!ThXc)~9Mˇq%BiBdŻÂL=M6|UÃS9+:PWˢq n	w{cul?9	7f_	ѫnʱK4Ue_,0MMӶR5;Z(vlv-w6h''uʟGI$ɞUOXsuS͹яW۲J|+otG!@ ix&lzPC;Ćn4U"q|d7ş[7b Uy`Tynp*Pm&O$	6gˣSzrH$F<mx+Ks_v@OoӭْW<jt`dqR$p9cgbT6->h8lz2bvyjD\m7~10ve~x'q u3mw߃_517̐-Nu*p(-d<nhbL%U<p-oRȌo;I1nsU{#*L\Г"VNF	VvMBV.]>-C[	+,r|q1IzS|yK)VBN6hm\dugJ4ڑjF&Kӹ@Oɡ=RU
ѓ=Ky`I+(Zip
-V:	N~vǀ]޸rW=bpW&[=Uu#U).\o[t
iK+dl2-n1515B	2o]l?FηYY=ݥ'ڎ`ILF51{::%$/fk&*l۶#;#@ojNӰYrgǉ\=TE֔ǪMd
(fb )OH]6Fz4=8>TV16fӥ[&Z5dˊƦfW$#h`'!g K![	8ܦXlmY7S2M56Ԏ>𞫓;t_O2 X	cK$״Cwp8Uv@U~>]/+AƼ<iUX<u4	]d Y`O{ǝM 	>uv"T~`Vl=5U߂KN8RY0iѶ X6;,>jwU&zaY[MXY 38FB3!gkjžvv{kfB҆NEO34]f>[=Itu/SAM7HӀ0{-vӼE{goD7<M.Jo9
.ftDfv]G[>dit5[,[ҽ ޟwtx;aI"nݰMcp61⹖fZHɧsXFJGb:Pg;=t%፰{g?gm?멅*;k޲DQ<dZgQqN X=H%kM R_$CQ=t#8{VnpV|JQ[&o@㔋2'%y5lMgyӅƫ aSdjM	5&܇L¯w/O>WQ},Xt͎ŅZ\{a P`/Ӛ+LQY.-,AA)ȕ@~S~VdVƏ2ո(ey	8GAl\qUV[bHX
`2 yn8_?
/Џ^"[ <[qfG2x#=苵L&Y#a.mywiv2>$rc)W-a)[NłYz!\eXz}/U/OY@M^n	jVt,EjIL+XL3w9gVv5Hѱ$7oTyllԩN
ˉAdo;oOH<_s!fMYmޥyRnHI0Vv4IS QNb,GJ#PDO~t'<H]շiqkX/?؋ŢЉn.t(3yF4-	?6ɦc",y΁6QMeSҗ/Gy;̞5/]77P%4jܔh/A	H9xO
W6A$|dn3~N/Tv,\2Z`Q[ofFi>'eMD,BFnsnn/_!pWu`H[ zR9}xYPa^rH+0TlWQ%ȃo4 R0lg
W`{nc+cvPFmPc߄~ӜY_/JրMJFp{x*F$Fjpkvlm-w+b,
i($}j[U[cfkppZ7xߴb{N"`^n6
;QSvp 0y`}ǬdoZep$QtX9{)9'%Tcv1VNiom/@ۓ.k>`T~+[E.Ak{agI8ʐfO@;P59i:'cQ%.Tn&"#'.MPngɃ-7/Nwo;@k}̏K+ȚDܭpՎ)hg@I%2I|8 EG%jCD,TV@Ϫ_皋;`{U&H-yKiaKǡg1ZICJPꂿsLFF/$t}֞agJ}!5π߾vWs'>R]e5x8{=YiĂ=\b
5Ω	/x{}PjO@`UD=HRS[(As{o&2z}G!Vp*w.WƬ?[q|2DNI"kp2KmQT!;n7FZ,N˔H\${;뾭8)sc7Hf٢Ne$љ=:-4SJtX*.qz&v	e	?&E)\jS6ۙE_K[i,kl-j__m99z{ 2w|ۊ{+2v͋tp<w|[tǭaLv
:wlVNbO x'%uyC0W y8@ᯪvԩ$%>y,a85sV5Ɗ0X"Lomi-lFgPU$쏵0zV^W^I94g926HڼfD^gB3=tiNhIz_c&u:q9wsA9_sK_5z~/	AOM\7EB*	T:Jg?(z&KˀY	^j$F~z[iJh1>.Zu78,bi%u%~:1!}?{>87*Bx$bAp/C%t9bNVnt-:/.KLֶH[_7}Oy|>/Ǿl@Do;O4P<Ϧp<8'Gd"%'q*2Z%tP&i0␭u\0>")|7Y1g[Ki30=#rddQw{p:h3bA !p*, WK8ǖ[bަG~vVg/>#_\~o^{⏱N'	2c@CWR~ˑbh1礊.ڬK-9\CB4 ёKk~jm:9h>FSels\BZbb́lRDhmhsp3gdӣG1/2є `f5D{Aa~z}FeDYjIx't2FCgԪ8Lr|"(5'ib]n*~~xzZnF5-?o4$aYq:*H0	n&)cBJQ:)2?j%@ws3Nc&I7?kxqA8B݂`Wb938P(RAdx#
Oab8@2s;8!\8#]7O?`戾/H2掛B1j
`bvy/*2vQTujRf&Cn5,ӏs!C3̥̞E~y}~onߴ{\e'2co?Z?/IHHS66
[ WpDȩ8a0~·%P9u8,!<ۍ")^T{xƍGx6>nӗ_ĘH|٤XvK\x9[(o|fxV{.G(7s}>>Ml?71R^O@`,}`ɠIMG
R-fI&8L{F(Ϲ/?G8é?@7s\C$$aJ&0"sq`3^uP\tLfvp#[+)0PkejtOzz=a`^־V"AC1_&~e>xnt&'v"TMnmI/fMkeaٌ>SPc:2B9XhUbfWW^#?J/_d Pǔeq䏨:l)7[FeCN
4]vZ0U6D֝j (o p~&	0}O-/QbEfX[V0zwUtNH> FG9y<YثQhVѰrS58>,S᣷#`YvM֍bQ Tly3)VW`p ]_X^:jRݦ(]?ݛdIK2/< gfI#3EXG!8;l |\
~!z@sj.2fߣrQ%vZM$6Žxir@bvsa.z	C+<fu3G'}qINyqYbӅO.7hkFʽx8'7oLdkNY7FJӥBCu3RTYfJ"%-Qŭqm)[T#Y^3]^3cgOjEp'\r
ϛIz;꟢>kF7f^a|C1|Յ[ḊWk	-KJ)5->$uY$o;ϥ4Lri"e[Ѝu_Xy|[}|3ǓE\nE/j-3Fg},-KV;?L؛$`ǔ؄Xd;ߒ~	NuIFg6*=9KL$}5(btjIׂZN@d䶦WW,aߨoLSГQ7	gVD>3rW}ǩUBb7$jGR02+~=΢k̽I~S}esˌ0Å!q'a1f.;+niPF>x2uZq>F`*r\ <`Aݲ~|==m.o{B7nj	0W/MFd.&ݔۆZ},ǜŮߩ=|$]M(K?L"{>~fyn=M\O_js|Ҋq4cEL-pu"JwǔNAqf56+>CЙqcr/Aj4HfCL@9R-eZ21_!ݰ-3d.٨N}RvNѹ`-']Aݚ;ж'YI.D-c6H<"ږ8<jZ߮vv*!Rs%cS4cgt{ze77ׇ·֛F<ͩۿ>w%i9{@}Ȋi\}YCf"U^d3a:цpG#CSbdF675v'"s~lm~;(2'B2|c!;.W|lkMqFZ15`r1A6Q^A$1U*brrÑ6#6P}y14iP><Up|\C_IiC0ܞ,N0Aի1mV
tKQeA8jB5f-a]a)yy'u&& 'WjK8GPW~>62jhPvUHwA\e!WQd'4MaS%u"0BqR?.`g_'O ύM|,290gOzyoDpȪQ[ʝJB)"8[c|dzf (f Gsvpf ))3V7Sm%s.6G
m	]sn.%.g=x0~?gݒ[ca0UVv.p#ױ48:騼3^9w+Y9vwLmZOVT-#Gӕm0^ǧUG(&]AJkVY7g\Q,z9lqp:Qq&a{;Np0?ǝ%%ICdf'~zJzVC2Osa$[[F`X[ @B7>}xۊȥ/Ux9?L˷ik&=ƗEu8wxDs$' txͱ4AR瑁J)jp&t At6mŋ!JTlCʳArϓk/{tGo2^,jK Rpҥ[XoA#t%S867Ɠƴ:~ק_vY"
K[Tz%/Jjn{]gt۱θDWb7WOzQ!eՠ_\N?k9K=XCg!j<%9ILx/#Ac- 0J	;##Ews3sar'd~I:nkA>WA"+~e\x[;_)=9v81,;;P,hi7]iP"O}*.R=md*?}@fgm(?Hn
\/|^c$Viڗg~vk)?".\7pBukJS2as+HQƓ86zl%3g6&>>HHP{`>6p8uPZIeۑG#fh<DJ2=\$]m5CbT.F*l鸤]O~ |g#[N!tkA#'+]'(??T=`\cu/F\y0Sxi2t[j_wj:BO¨?UYt02FG00b4a< H(Ѕ[<	.G<ߦU@C'9cYW]]#"?x> 7nǗu?	7黻G{'i:ג~b]K 	9P}(P]b6!
js]ޛIja[;]NJߤ:m\dCXpԦ-c*d:gǚFlԛ:WR]J~g;:hr]\-p0xR͇&P>XЅ'D&uY'.#TU)2:Xn^z|V(9߻>y}N  Yrټ)f&QrZ8%jL'1LxG!63'DDyn[Ys+t]Ls߷9`uK2Ŀ_~Wx.C(qcW z<B0(1UBjjmsei߭'Qɮ	i6T9~ 2-j:?^IUo"FK4srk@zLy4D$84fQD.	+58ھ\9{Kh3؆,ZE4;JjH;j4u~r>|j\*ĆTM[KviȚfbx(Oآ3-Ij V9+ڜn#gP]QwWT{	),qi׋ ȽAnfF)D$M`P<p⏽rrJI=%jI C+)-Vgg|ӱF
pT~ɼo*U7A&QguA*@3J3]ў4ߔ$}pn%qW@tiTbn)PZ܋޽I`X"< {RPHS
n@%+ךFsy`dbd&wK*fA_ۂ>wkɲJ7*w޽OmVD	~ǋ7czIilk"$J<X&ې[Jb/!;@C]5
><~P6nSlf0
8dX`K;A+hr&7
'U.8bv[W)k{oP0jX: +.](КCm9;_=OޫȄlȶ*zb<OL0-l%\ܐ޶?<VϵDCU-~zfk/'9~9]1(3}dbQ>hC!Ch#5a*6LXp檐U~Btm5aze 'Cw=ÖXlGWU,Kfp&P{[CgJnIf6^e*iv+eEpuu4D"z_Y|w<92uVR
#Np:6fRxV$X$5jjEynZ;,xO{m 5)	M8]lWƴxbKҸF]Їfoe^(!Ȣ8>vƊv+C̽zec}C<M$Ksu53:Br(+s-O=DkLL^ZЇ]Q|Ud%ԉf=}=I*S в_LN!\}ǥup.W#T 6`[VT$UI/կ'&ӍOίqQ9$IFhK2>`l&]xKpиR?NdIVSI0O2ctR1u7F!Z:?k^y]bB<( c{Ze@i+t4bU(%vg`:)^ #±fNZO5[IG̴mh=cCzI w%H-jUyVFrž0ZmejNb z&j9:N{zｔ2ðkz|ߎxŨb#Ml=	 mDGv`vYt\r&p
e[1A.P.gPvQn&Wܞ^kn$6Р^#uoj]*vԵqfԢ=2٘h/#N;Ql2R>7rOI	(_Q:,/z*K"|$(8˛*hbњח*Z;Rz:l[?U&	cGa`٨4N!Tqsv7<zê	o㲅A4@Sќ5PJ:, |so+'!a2"V=u;eW12TTR}><Q)gDFUE}b%Qme,L<&İ(2'pMuetR1c=S֛=Q߆0<u7 IFwAozE}y1MW@mW16ic6E*"Oܜ7jUѰEiϳ-b") CSL%ϼUʿ~z~3{lC x{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "base-nova",
  "rsc": true,
  "tsx": true,
  "tailwind": {
    "config": "",
    "css": "src/app/globals.css",
    "baseColor": "neutral",
    "cssVariables": true,
    "prefix": ""
  },
  "iconLibrary": "lucide",
  "rtl": false,
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "menuColor": "default",
  "menuAccent": "subtle",
  "registries": {}
}
Ix.import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";

const eslintConfig = defineConfig([
  ...nextVitals,
  ...nextTs,
  // Override default ignores of eslint-config-next.
  globalIgnores([
    // Default ignores of eslint-config-next:
    ".next/**",
    "out/**",
    "build/**",
    "next-env.d.ts",
  ]),
]);

export default eslintConfig;
xEimport { describe, expect, test } from "bun:test";
import {
	buildRelayRequest,
	createRelayResponse,
	normalizeTargetUrl,
	shouldSendBody,
	stripRelayHeaders,
} from "~/relay-utils";

describe("normalizeTargetUrl", () => {
	test("returns null when target is null", () => {
		expect(normalizeTargetUrl(null, "/")).toBeNull();
	});

	test("returns null when target is empty string", () => {
		expect(normalizeTargetUrl("", "/")).toBeNull();
	});

	test("removes trailing slash from target", () => {
		expect(normalizeTargetUrl("https://httpbin.org/", "/get")).toBe(
			"https://httpbin.org/get",
		);
	});

	test("keeps target without trailing slash", () => {
		expect(normalizeTargetUrl("https://httpbin.org", "/get")).toBe(
			"https://httpbin.org/get",
		);
	});

	test("appends relay path", () => {
		expect(normalizeTargetUrl("https://example.com", "/api/users")).toBe(
			"https://example.com/api/users",
		);
	});

	test("handles nested paths", () => {
		expect(
			normalizeTargetUrl("https://api.example.com", "/v1/users/123/profile"),
		).toBe("https://api.example.com/v1/users/123/profile");
	});
});

describe("stripRelayHeaders", () => {
	test("removes x-relay-target header", () => {
		const headers = new Headers({ "x-relay-target": "https://test.com" });
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("x-relay-target")).toBeNull();
	});

	test("removes x-relay-path header", () => {
		const headers = new Headers({ "x-relay-path": "/test" });
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("x-relay-path")).toBeNull();
	});

	test("removes host header", () => {
		const headers = new Headers({ host: "localhost:3000" });
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("host")).toBeNull();
	});

	test("preserves other headers", () => {
		const headers = new Headers({
			"x-relay-target": "https://test.com",
			"x-custom-header": "value",
			authorization: "Bearer token",
		});
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("x-custom-header")).toBe("value");
		expect(stripped.get("authorization")).toBe("Bearer token");
	});

	test("handles multiple relay headers", () => {
		const headers = new Headers({
			"x-relay-target": "https://test.com",
			"x-relay-path": "/path",
			host: "test.com",
			"content-type": "application/json",
		});
		const stripped = stripRelayHeaders(headers);
		expect(stripped.get("content-type")).toBe("application/json");
		expect(stripped.get("x-relay-target")).toBeNull();
		expect(stripped.get("x-relay-path")).toBeNull();
		expect(stripped.get("host")).toBeNull();
	});
});

describe("shouldSendBody", () => {
	test("returns false for GET", () => {
		expect(shouldSendBody("GET")).toBe(false);
	});

	test("returns false for HEAD", () => {
		expect(shouldSendBody("HEAD")).toBe(false);
	});

	test("returns true for POST", () => {
		expect(shouldSendBody("POST")).toBe(true);
	});

	test("returns true for PUT", () => {
		expect(shouldSendBody("PUT")).toBe(true);
	});

	test("returns true for PATCH", () => {
		expect(shouldSendBody("PATCH")).toBe(true);
	});

	test("returns true for DELETE", () => {
		expect(shouldSendBody("DELETE")).toBe(true);
	});

	test("is case-sensitive", () => {
		expect(shouldSendBody("get")).toBe(true);
		expect(shouldSendBody("post")).toBe(true);
	});
});

describe("buildRelayRequest", () => {
	test("sets correct method", () => {
		const req = new Request("http://test.com", { method: "POST" });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.method).toBe("POST");
	});

	test("sets headers", () => {
		const req = new Request("http://test.com");
		const headers = new Headers({ "x-custom": "value" });
		const result = buildRelayRequest(req, headers);
		expect(result.headers).toBe(headers);
	});

	test("omits body for GET", () => {
		const req = new Request("http://test.com", { method: "GET" });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeUndefined();
	});

	test("omits body for HEAD", () => {
		const req = new Request("http://test.com", { method: "HEAD" });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeUndefined();
	});

	test("includes body for POST", () => {
		const body = JSON.stringify({ test: true });
		const req = new Request("http://test.com", { method: "POST", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
		expect(result.duplex).toBe("half");
	});

	test("includes body for PUT", () => {
		const body = JSON.stringify({ update: true });
		const req = new Request("http://test.com", { method: "PUT", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
	});

	test("includes body for PATCH", () => {
		const body = JSON.stringify({ patch: true });
		const req = new Request("http://test.com", { method: "PATCH", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
	});

	test("includes body for DELETE", () => {
		const body = JSON.stringify({ delete: true });
		const req = new Request("http://test.com", { method: "DELETE", body });
		const headers = new Headers();
		const result = buildRelayRequest(req, headers);
		expect(result.body).toBeDefined();
	});
});

describe("createRelayResponse", () => {
	test("preserves status", async () => {
		const mockResponse = new Response("body", { status: 418 });
		const result = createRelayResponse(mockResponse);
		expect(result.status).toBe(418);
	});

	test("preserves headers", async () => {
		const mockResponse = new Response("body", {
			headers: { "x-custom": "value" },
		});
		const result = createRelayResponse(mockResponse);
		expect(result.headers.get("x-custom")).toBe("value");
	});

	test("preserves body", async () => {
		const mockResponse = new Response("test body content");
		const result = createRelayResponse(mockResponse);
		expect(await result.text()).toBe("test body content");
	});

	test("handles different status codes", async () => {
		const mockResponse = new Response(null, { status: 404 });
		const result = createRelayResponse(mockResponse);
		expect(result.status).toBe(404);
	});

	test("handles 500 status", async () => {
		const mockResponse = new Response("error", { status: 500 });
		const result = createRelayResponse(mockResponse);
		expect(result.status).toBe(500);
	});
});

describe("integration: full relay flow", () => {
	test("complete flow with mocked fetch", async () => {
		const target = "https://httpbin.org";
		const relayPath = "/get";
		const targetUrl = normalizeTargetUrl(target, relayPath);

		expect(targetUrl).toBe("https://httpbin.org/get");

		const originalHeaders = new Headers({
			"x-relay-target": target,
			"x-relay-path": relayPath,
			host: "localhost",
			"x-custom": "preserved",
		});

		const strippedHeaders = stripRelayHeaders(originalHeaders);
		expect(strippedHeaders.get("x-relay-target")).toBeNull();
		expect(strippedHeaders.get("x-relay-path")).toBeNull();
		expect(strippedHeaders.get("host")).toBeNull();
		expect(strippedHeaders.get("x-custom")).toBe("preserved");
	});

	test("POST flow with body preservation", () => {
		const req = new Request("http://test.com", {
			method: "POST",
			body: JSON.stringify({ data: "test" }),
			headers: { "content-type": "application/json" },
		});

		const options = buildRelayRequest(req, new Headers());
		expect(options.method).toBe("POST");
		expect(options.body).toBeDefined();
	});

	test("error case: missing target", () => {
		const target = null;
		const relayPath = "/test";
		const result = normalizeTargetUrl(target, relayPath);
		expect(result).toBeNull();
	});
});
P>HxVMlDV~8m;N7MI[
aiW^͊[WnbH)*qAF+$gU/p!ސ8 ZPh%{n
,yf>m^ǲ@Uͱտ=Q*=t PAyYԮ+O j`|M[[՜#zr17H=S`'_FK<l~}3 	C񄅲/o!o1]ῄ+?s嘎aQCê<54yC`> 	9ưEd`7H@!wEsܴfn	G.B5Nh"ŌKy)\A^Kp?L1"6x3oOU9KdoֻU`i"<R~s33M/[>6~qmZ1>4ۗUM4:ѕ8@dSDOzgZ	w=%HD0FQ܉f<<>OYMb8z<|,6n,߈,ƃ1;4LƋ^VL(Kv,N! YeپյfX"'CcUQSn@IpRo%쮾X	UW%Owspw1^jT-L]98HJG-f*.l6eO߃Y.EǦ9+C^\H725g93-(_l9x?[oKqqnig;ʱqͯl瑃SL]4Y䙰KWc[-Pg,Etj#_
PvS2~v&ePw)t($hl&.]A2d?KLw~rzD!w#A	rTn=Sw-(<c3E/`8^eZ'<R5 ./Բ[x@;cy0ҳ#5`_{e̣hDl}Vj`H'f]I9'{BGYx zimport type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
};

export default nextConfig;
-M2xmێ }ׂ[ݸW^s *ICâ|?3/,7+΃˗Q&/y1^]P:z(cy9ck5mx(ڐH 䪠H(XWIqWZ ^8-&:N,gDס֝
XdC[曔2*>N✪܅	3eC*7zhXD>|_0at$<"u~Sj-)\Zjp^/J2;iA"_!Ƥ5?!)H_L|C/_Ӕx^ const config = {
  plugins: {
    "@tailwindcss/postcss": {},
  },
};

export default config;
x340031QHI+.Kg`pt={~N#ӚOvH2IO(
p4+=N}s(/&dSOXFQgN5π)K-JN*gL03ArRϙPUy)`U6-Sp$ņuK 4Gvxx<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg> Ja@x<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>пUx_<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>x <svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>A$x~<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
\	xa 40000 app =)`yT=40000 components $?0H@۰YDU40000 lib biX	jO'.x31 ĂL/u9/xsS~C3Ĳ<= P؛Jo*S*KOJ)K..f8`Mڟ'~M,w4,'2DAvN_ב{..Ӫ:3/TUAbz*X٭-)cdlNr ܶHƠx  40000 relay bȶK0Q}k6-x$ 100644 route.ts K}(!<Ue d kxFimport {
	buildRelayRequest,
	createRelayResponse,
	normalizeTargetUrl,
	filterHeaders,
	isAllowedTarget,
} from "@/lib/relay-utils";

export const runtime = "edge";

async function handler(req: Request): Promise<Response> {
	if (req.method === "OPTIONS") {
		return new Response(null, {
			status: 204,
			headers: {
				"Access-Control-Allow-Origin": "*",
				"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
				"Access-Control-Allow-Headers": "*",
				"Access-Control-Max-Age": "86400",
			},
		});
	}

	const target = req.headers.get("x-relay-target");
	const relayPath = req.headers.get("x-relay-path") || "/";

	const targetUrl = normalizeTargetUrl(target, relayPath);
	if (!targetUrl) {
		return new Response(
			JSON.stringify({ error: "Missing x-relay-target header" }),
			{
				status: 400,
				headers: { "content-type": "application/json", "Access-Control-Allow-Origin": "*" },
			},
		);
	}

	if (!isAllowedTarget(targetUrl)) {
		return new Response(
			JSON.stringify({ error: "Target domain not allowed" }),
			{
				status: 403,
				headers: { "content-type": "application/json", "Access-Control-Allow-Origin": "*" },
			},
		);
	}

	const headers = filterHeaders(new Headers(req.headers));
	// Prevent compressed responses (gzip, br) that cause encoding bugs in relay
	headers.delete("accept-encoding");

	const fetchOptions = buildRelayRequest(req, targetUrl, headers);

	const response = await fetch(targetUrl, fetchOptions);
	return createRelayResponse(response);
}

export const GET = handler;
export const POST = handler;
export const PUT = handler;
export const DELETE = handler;
export const PATCH = handler;
export const HEAD = handler;
export const OPTIONS = handler;
-*˻xKe         (  F          (  n  00     (-             F  (                                                           $   ]         ]   $                                                                                       8                                 8                                                                                                               #         OOOggg            #   Y         555            Y               kkk                              			                  Y               JJJkkk                  Y   #               			                  #                      111DDD                                                                                   8                                 8                                                                                       $   ]         ]   $                                                                                                                                                                                                                                                                                    (       @                                                                               ,   U               U   ,                                                                                      *                                       *                                                                                                                                                                             Q                                                         Q                                               r                                                               r                                       r                                                                     r                               O                                                                           O                                                                                                                                                                                                                           (                                                                                       '                             888___                                                 SSS                           +                        hhh                           +   T                        ,,,                           T                              GGG                                                                                                                              +++jjj                                                                                                         T                                 III                                    T   +                                    hhh                                    +                                       ,,,                                                                                  GGG                                                  '                                                                              (                                                   333___                                                                                                                                                                  O                                                                           O                               r                                                                     r                                       r                                                               r                                               Q                                                         Q                                                                                                                                                                             *                                       *                                                                                      ,   U               U   ,                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               (   0   `           -                                                                                             	   (   L   j               j   K   (   	                                                                                                                                          V                                             U                                                                                                                      %                                                               &                                                                                                                                                                                                                                                                     Q                                                                                 R                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         P                                                                                                                     O                                                                                                                                                                                                                                                                                                                                       #                                                                                                                                 #                                                                                                                                                                                                 $$$hhheeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeePPP                                         U                              sss                                 U                                            eee                                        	                                    HHH                                       	   (                                       EEE                                          (   K                                          ,,,                                          L   j                                          )))                                             j                                                                                                                                             iii                                                                                                   eee                                                                                                   HHH                                                      j                                                   EEE                                                      j   L                                                      ,,,                                                      K   (                                                      )))                                                         (   	                                                                                                                  	                                                             iii                                                                    U                                                         eee                                                         U                                                                    HHH                                                                                                                                    EEE                                                                               #                                                            ,,,                                                            #                                                                                222}}}                                                                                                                                                                                                                                                 O                                                                                                                     P                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         R                                                                                 Q                                                                                                                                                                                                                                                                     &                                                               %                                                                                                                      U                                             V                                                                                                                                          	   (   K   j               j   L   (   	                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        PNG

   IHDR         \rf   sRGB    8eXIfMM *    i                              D"8s  IDATx]	ՙn]<QVAh$	N13*qdčIDL2((Ԙ2ęG	q_@屈xțЏ{oU{}O;9d(Dg8	N ]@hx?v N3=`;6.&u  6Pн@àR PiZq^DNwpXhИHg@
:|5` p"@'ɲs{p*2 d ү|(0
0 >K
xX6 IJ C|?$KEN}ϓ|h $	2 |/ . Nz #We
5ܶ;y  gsh^  I DL(;8HjgcH|x1R"aӁ G@9`/`%0H@j~,K
,t).IDTO)~Vu$b 誛U%7 _$b 8AJ3` 510wQ?vr:2K@ v*{%#AZ咁^(=g \W!:,`6643:@c.Fٟu?<'_܏vp: 8Q
IŁp{3kHȢGcѼ<62&
2uC敭T3
;d/~m.X@{w.d]G {lKEb(PRuMTCd])_Lm==@bKGUk^U)1gTm`9\Q@Ⱆ6:ڞ^wED 	5F,
X"dm<nB~@ttx;f>I88C1۪$Be+jlEZ& S::6m\G1`!nllƊ^Q`@OcS@eͷqbpS@upFD@Г2@#L3 A$H2 _hFH#rq(OD򤬈runGOWab &SgD3EDto*Ǥ9k~),$ xR1vK 9D䍁U(w&LEꩻS)3Y8x8 $.i(KŀYa]4ǀ	c@3f4 Ƣ/*b $!I~7B*-1`	o  	$ǡDL J"OQ)2@#x4"$e I8Oi8" G8[xt<.7&m&؎R^tq ؕ.Y-2 d *_&d|j\Wb G*g 釁F4"I؃/ b1qNYDp9p}w\ Ԥ1 j`OxK=H A1#
D:U8jt$b bA||UQ26%)1 _ꢳ!~D +b >A:]E$50GDhRtݻwR)P n$ 3@bSNu,Yjʲ:;@`|-[)'OVՆsFxڮۥn}͛7~ƺ:QJ_UKj8q0x;v4 ̞=[hW=		&!e58hѢEw]6_iW}SZ?	/`;vl}2 <h" A܁X,m۶+V(<w#F^;aHc )S*{apc89(^4&EoÆW/u=^*?{k^_Ezg UI-{WU*
:p9.tڷo(/ݺus>3'^RgڞGI_D~~ {?N07S.ƍ׸~?}/y]nA;أ2 ]FOB2C?_I[::=#OzK- ϣ%?jIPۯ{N-hUt: ,GK-hUchP7 @n?\-k.2: `F=-V_G܂V }0 WIFʭsMrZ8pJQ*@OK8
rZݖa, w SW^y.5at7ݏTv#~7nA"+WpM/hK8gF/^M{e R|)q7t?8'KP~瞰\r>ǷUk eP|^x
/V/v*pv ʟ]J}k8(ĉѣGǗOmڴq,Xoe.^ Qxpt4^_N{y2 s -عsgsivZ8
!~PJ?c|] ܽ{z긓R1pnztlp9frvjT殿z4*OL~ԕ34~~r;mxY+3 r;mx4:7]ՁqL4)U!r1u6$78w̙3Ǹ|5>?\zO͆ ,E32[2Wu:E^p.H1cJt]}BuSOuIcO%  AZkD?5 @Q3w+"TSUޥ13?5 M'݋>pZj~fj׈סԐn> i5D[bf ~a'`Xc -1kāIkQů|kM(92@t݂X-LדaN4qܞ'$f0@@VnAܘYL9:|/^s 	)0`jT\wuZ-¨\	@:ct{-Rb1% I,Y%T~r1C,$*ˀf<0zhF |8Z-CRTg HRfglYs-p'+m_ؒgC{ 	ȪϏΙ3g-GR|׹7`G񥡘0U_ٵZЏدD)\>ʗz N@~~-P{rs@<|.]Ը|m|g_yWKD1bM%s\r1n\ƒ"- `.4~%3I}[0A$= ->BH"Gۏ^r<EBGi %9@^~~@1@ t-[{%@C$mAgΚ5kʆх/双OlӿB@.XupO6x9MPn`߷o_^n`t
(\rsAyۂT@h
E0l0;tڵӘkƸNYjU
S#|^㽺- |pN.ޥ`^{zL64 ěbe]&"dsΜ9UޥU0!*nP*`o֨vi8Ghhmɓs={JU0ՂwZ8bEz,YD![C>}7:k׮nof >jvR?#bX(FATFi[{zv>Ca+[0B2D=G~(
ĺLO\s܂>"8|`[)
&Lp8'4 oGe#ۏlْ_\D̀܂2Zli9tȑ9f ޢ-=Yyn?uQ}XͬsAi >=1=R+ +܂.2 KCƢۃ20h ˫%535@MA%̣j[9; _(0~r\{mPx#TT9n?N#ץ&} )
TVL!j`p 8@RrUAVA=-pLH`@n*Ȋ1܂U?}w ]H2@ߴiV[˯%5 8)Э
T`|rZbZ-.!da+@ ߞZgf[0p  Igr$o%P_rCyV|߽"mY-[ lk xA ۯ9][pҤIȨpPk FeِgHEdnAm"Z$5}z82rX| SܻwrJsJ~Tfz{ ͫ xj?jQEn js|Gxз<dXt(QE.p47 );ys_VD-XTi? ~薜 `Q=V?^
.]|X
mB~?J D~h rERA݀B~wqӾ}<ŕ[й5d-`5 ?Kq~l40@)/I(؋n9Y4!Cو2ח*w9GKݐs&res?68J |(uwO䴁d&K)nA?Rn@7,8=renM69kM7JR]en9Z /?នo>󕾤rzr `V{u4448Vrap QRZ<{dK.F9#~Ts.N%* Ýu8G&/W:*x%{}@ lNc#AIi*?د0}gC"Āpۯ4薒ҏ(b8_QY r7'` j 6 *3Wg"l1:Sg}% 	P?1`Y "D0b@ 9[tF1p`k\U`RA#W81 e`)RZM [uF0	rq. #^=C"Ā9P'R~f 
pnzdC"e?\K@&$b }jz3۵ x/{1 Ra#|ƟUK= &^TMn295)?s{O'DDo [kMoK0x Td_@]b r G; DD1gaR`'`0  >\/fŀ!fnZ|bU.tटr9+	b rnEDk= 8!b RClPE`܌K'~@}*!`@6 L;	$b@D?#gF
V1v;EsQ=ɮ4b@Tn!3q0^V c 1ܶ[M=8I1@څ@Cu`No WJĀ WeI nNmீܴ_d(4`E܅I "̵1 *3+\E \M)g	r
8>p?vI 0ǀ~!b$'%"IRi1 0? S~& r{ n_L?TeƎ7C"rOQ~"qI O 8?$b ܋r#@_vJ̙/3'd/W[o'Nl-2 @jO~02` H@؄+pOB uO(lSԕ9~c:x/Xd.Ɣd Vy@F $H2 +M*il8O@F $H2 24& rPO֢7NYS Y1`;JS3n g['@W@"la`32n?'HB2p
hāmmu j@F@VZ!xIHyѱ)>Z!6 a`dDV$9f	pM6I!LG:\LdrwPy~P%L37TKAmmo|6	3-h J3?67 yr"g4. $1_[*&S/dqCh 3>6Ŷ%\#RZq=lK|ŔXXWSe j5 /$:v@8d1(z2~F)3͋lC#=.\Lt? % N$9b%:2u	 1|-	ldt $b@?@ Fcρ^Dd[9ࠐz:
H@ P2v )~@z5|Rֵ|`#W39؂<"-0\<duoGLz 1Gpe倯d .jH@jF3@ c{s<J&	@bw   nv< ,M;*p>p!0hH{=x]I DLh<'h8@V #Jf I HnW}Nt[u$ @ 2 	]&) #3,	=%Tk&  IIӳ [8		L]]tTg6-@b2 UOV: A?} .i|	xCrvw; #>i 8_b82 WP  {'n8z;Ƥys @Po|Sih $3@߹j    IENDB`ax/@import "tailwindcss";
@import "tw-animate-css";
@import "shadcn/tailwind.css";

@custom-variant dark (&:is(.dark *));

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-sans);
  --font-mono: var(--font-geist-mono);
  --font-heading: var(--font-sans);
  --color-sidebar-ring: var(--sidebar-ring);
  --color-sidebar-border: var(--sidebar-border);
  --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
  --color-sidebar-accent: var(--sidebar-accent);
  --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
  --color-sidebar-primary: var(--sidebar-primary);
  --color-sidebar-foreground: var(--sidebar-foreground);
  --color-sidebar: var(--sidebar);
  --color-chart-5: var(--chart-5);
  --color-chart-4: var(--chart-4);
  --color-chart-3: var(--chart-3);
  --color-chart-2: var(--chart-2);
  --color-chart-1: var(--chart-1);
  --color-ring: var(--ring);
  --color-input: var(--input);
  --color-border: var(--border);
  --color-destructive: var(--destructive);
  --color-accent-foreground: var(--accent-foreground);
  --color-accent: var(--accent);
  --color-muted-foreground: var(--muted-foreground);
  --color-muted: var(--muted);
  --color-secondary-foreground: var(--secondary-foreground);
  --color-secondary: var(--secondary);
  --color-primary-foreground: var(--primary-foreground);
  --color-primary: var(--primary);
  --color-popover-foreground: var(--popover-foreground);
  --color-popover: var(--popover);
  --color-card-foreground: var(--card-foreground);
  --color-card: var(--card);
  --radius-sm: calc(var(--radius) * 0.6);
  --radius-md: calc(var(--radius) * 0.8);
  --radius-lg: var(--radius);
  --radius-xl: calc(var(--radius) * 1.4);
  --radius-2xl: calc(var(--radius) * 1.8);
  --radius-3xl: calc(var(--radius) * 2.2);
  --radius-4xl: calc(var(--radius) * 2.6);
}

:root {
  --background: oklch(1 0 0);
  --foreground: oklch(0.145 0 0);
  --card: oklch(1 0 0);
  --card-foreground: oklch(0.145 0 0);
  --popover: oklch(1 0 0);
  --popover-foreground: oklch(0.145 0 0);
  --primary: oklch(0.205 0 0);
  --primary-foreground: oklch(0.985 0 0);
  --secondary: oklch(0.97 0 0);
  --secondary-foreground: oklch(0.205 0 0);
  --muted: oklch(0.97 0 0);
  --muted-foreground: oklch(0.556 0 0);
  --accent: oklch(0.97 0 0);
  --accent-foreground: oklch(0.205 0 0);
  --destructive: oklch(0.577 0.245 27.325);
  --border: oklch(0.922 0 0);
  --input: oklch(0.922 0 0);
  --ring: oklch(0.708 0 0);
  --chart-1: oklch(0.87 0 0);
  --chart-2: oklch(0.556 0 0);
  --chart-3: oklch(0.439 0 0);
  --chart-4: oklch(0.371 0 0);
  --chart-5: oklch(0.269 0 0);
  --radius: 0.625rem;
  --sidebar: oklch(0.985 0 0);
  --sidebar-foreground: oklch(0.145 0 0);
  --sidebar-primary: oklch(0.205 0 0);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.97 0 0);
  --sidebar-accent-foreground: oklch(0.205 0 0);
  --sidebar-border: oklch(0.922 0 0);
  --sidebar-ring: oklch(0.708 0 0);
}

.dark {
  --background: oklch(0.145 0 0);
  --foreground: oklch(0.985 0 0);
  --card: oklch(0.205 0 0);
  --card-foreground: oklch(0.985 0 0);
  --popover: oklch(0.205 0 0);
  --popover-foreground: oklch(0.985 0 0);
  --primary: oklch(0.922 0 0);
  --primary-foreground: oklch(0.205 0 0);
  --secondary: oklch(0.269 0 0);
  --secondary-foreground: oklch(0.985 0 0);
  --muted: oklch(0.269 0 0);
  --muted-foreground: oklch(0.708 0 0);
  --accent: oklch(0.269 0 0);
  --accent-foreground: oklch(0.985 0 0);
  --destructive: oklch(0.704 0.191 22.216);
  --border: oklch(1 0 0 / 10%);
  --input: oklch(1 0 0 / 15%);
  --ring: oklch(0.556 0 0);
  --chart-1: oklch(0.87 0 0);
  --chart-2: oklch(0.556 0 0);
  --chart-3: oklch(0.439 0 0);
  --chart-4: oklch(0.371 0 0);
  --chart-5: oklch(0.269 0 0);
  --sidebar: oklch(0.205 0 0);
  --sidebar-foreground: oklch(0.985 0 0);
  --sidebar-primary: oklch(0.488 0.243 264.376);
  --sidebar-primary-foreground: oklch(0.985 0 0);
  --sidebar-accent: oklch(0.269 0 0);
  --sidebar-accent-foreground: oklch(0.985 0 0);
  --sidebar-border: oklch(1 0 0 / 10%);
  --sidebar-ring: oklch(0.556 0 0);
}

@layer base {
  * {
    @apply border-border outline-ring/50;
  }
  body {
    @apply bg-background text-foreground;
  }
  html {
    @apply font-sans;
  }
}
["x/import type { Metadata } from "next";
import "./globals.css";
import { Geist } from "next/font/google";
import { cn } from "@/lib/utils";

const geist = Geist({subsets:['latin'],variable:'--font-sans'});

export const metadata: Metadata = {
  title: "Edge Proxy Relay",
  description: "Securely relay requests through Vercel Edge",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" className={cn("font-sans", geist.variable)}>
      <body>{children}</body>
    </html>
  );
}
;xYms6_p:5cPz^|q&vœ@$$1!	 RU[ER~xA.v],v!IL3>8Ӝq6^_K")ڢ9g)8%"(~+dY5Q`*AbTģv7,@goy˘eI%Q|˄ڻ,/d/uf]K2I/yf]k0ߕ=fNҴ|b,0?IR 4]IHK`"3q?+2*/h&Ѣ_뙚u%#^,߳|q@W%sR$͋,Tq.XJ! T~!TޔhR-hV%K E4 =F9Ʊ7|]KO\H~lMTX<q<|+s*`Pn_ .:Cj=DY'CW9FrTksuxx\'5\1S_%D`4ύE$*4C}6ߟsכ7׷ͻg-u$hY٘BV՚# hhg#Ʊ`F6B4!klvp-I7yDiw	VxaհCi+Fc -%p-,py39 nwLFց*cǥ!ƔշsgaRDTFPYlPv(䐖of@D'5H@W@50B WP#߁z0h	"h%Ncbc[psk!4t2+GDz"v5IaG >!j8Wqjuۖ.ls	xҿ3@LjƸ2 v@>Z<ס.266>RϚB$g8h;e9"<b(~ԊoX[.O9 9giUbt!a zLJU8W)	$Yɻ<FL4وT<C~;;(IJ':$+L
PG(_S?ojYOCUd;B,A+mjCCڰH0s0D4NfP@'7 	K\y8[ǣqC`ޒF]pVd7 )8ޥ$iiΛ=t5m^0#0|zЋÂR
5Q8s'- A\ +	e1OsN>d
<^\bqz5"GkVp{HFKTlԧ骺'V_(4GK&3>
oJlx;tzGTdtL9e芤yK.>'vа͒d n0Z԰-`p'\'#;5=E?w,m4uE[eLVdm[p%6]i]zռ6@7n%"HILpu^
KEbזY</NvIf]A Bd5oc8^b=l;wTjvTWXl:PhpB-gVv橽O{<:R͛>(mҼ9VC0IYL}p.<f.rڸڝ{}Gq9;ĸlEOmtP2NUpmhM6ΝF۲(ddcoavIvNkAx]ە4
6}cKutgx	uKL8Ƴ+$Tg(𯅐Ќf].niO0]`QJ{[#<&TVM'ܡ4g%Lv6ɋPw,(ߵwި"JGB&МyZj{0A[NX沣 ߼p}_J蝫tvl5~u8"{V#j6n{n[gܳcd;HDRBjm%>Eg4[4BeR^<EJ_HME5~(!/kjrj>tY29ӏ`<[&eIeZ*;y-&q&AppB_P՛$§wʦ|*Q>OؽRWp:쩇ĒD0yjgdޓ}cmχYտ/JfegYUc'i8q`{Gx 40000 ui BaIo΀fiVxz100644 badge.tsx 	Yw^@uJ_fbO6100644 button.tsx 	u6v˹7lD
CE100644 card.tsx @P`'~LeDf100644 input.tsx }!U֥cҵ+100644 label.tsx te;IG7j43100644 select.tsx _(=Cn :#UJL100644 separator.tsx ni<΋~I@v$R100644 sonner.tsx RVzHaȗ100644 tabs.tsx O?sGsMdMgh6100644 textarea.tsx }XL{7[wp6Wx}import { Button as ButtonPrimitive } from "@base-ui/react/button"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const buttonVariants = cva(
  "group/button inline-flex shrink-0 items-center justify-center rounded-lg border border-transparent bg-clip-padding text-sm font-medium whitespace-nowrap transition-all outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 active:not-aria-[haspopup]:translate-y-px disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
  {
    variants: {
      variant: {
        default: "bg-primary text-primary-foreground [a]:hover:bg-primary/80",
        outline:
          "border-border bg-background hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
        secondary:
          "bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground",
        ghost:
          "hover:bg-muted hover:text-foreground aria-expanded:bg-muted aria-expanded:text-foreground dark:hover:bg-muted/50",
        destructive:
          "bg-destructive/10 text-destructive hover:bg-destructive/20 focus-visible:border-destructive/40 focus-visible:ring-destructive/20 dark:bg-destructive/20 dark:hover:bg-destructive/30 dark:focus-visible:ring-destructive/40",
        link: "text-primary underline-offset-4 hover:underline",
      },
      size: {
        default:
          "h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        xs: "h-6 gap-1 rounded-[min(var(--radius-md),10px)] px-2 text-xs in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
        sm: "h-7 gap-1 rounded-[min(var(--radius-md),12px)] px-2.5 text-[0.8rem] in-data-[slot=button-group]:rounded-lg has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
        lg: "h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2",
        icon: "size-8",
        "icon-xs":
          "size-6 rounded-[min(var(--radius-md),10px)] in-data-[slot=button-group]:rounded-lg [&_svg:not([class*='size-'])]:size-3",
        "icon-sm":
          "size-7 rounded-[min(var(--radius-md),12px)] in-data-[slot=button-group]:rounded-lg",
        "icon-lg": "size-9",
      },
    },
    defaultVariants: {
      variant: "default",
      size: "default",
    },
  }
)

function Button({
  className,
  variant = "default",
  size = "default",
  ...props
}: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
  return (
    <ButtonPrimitive
      data-slot="button"
      className={cn(buttonVariants({ variant, size, className }))}
      {...props}
    />
  )
}

export { Button, buttonVariants }
[+&4xKlimport { mergeProps } from "@base-ui/react/merge-props"
import { useRender } from "@base-ui/react/use-render@n8adgeVariants = cva(
  "group/badge inline-flex h-5 w-fit&!gap-1 overflow-hidden rounded-4xlpx-2 py-0.5 text-xs@.-[3px]ring/50-(,20`.40 [&>svg]:pointer-events-none [&>svg]:size-3! m=}secondary/8G>40 [a]N2.textqmuted [a]:hover:text-mutedBmutedm-F
8adge8)render,
  ...props
}: useRender.ComponentProps<"span"> & VariantProps<typeof badgeVariants>) {
  return useRender({
    defaultTagName: "span",
    props: mergeProps<"span">(
      {
        className: cn(badgeVariants({ variant }), className),
      },
      props
    ),
    render,
    state: {
      slot: "badge",
      variant,
    },
  })
}

export { Badge, badgeVariants }
'RxQ
import * as React from "react"

import { cn } from "@/lib/utils"

function Card({
  className,
  size = "default",
  ...props
}: React.ComponentProps<"div"> & { size?: "default" | "sm" }) {
  return (
    <div
      data-slot="card"
      data-size={size}
      className={cn(
        "group/card flex flex-col gap-4 overflow-hidden rounded-xl bg-card py-4 text-sm text-card-foreground ring-1 ring-foreground/10 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-3 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-t-xl *:[img:last-child]:rounded-b-xl",
        className
      )}
      {...props}
    />
  )
}

function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-header"
      className={cn(
        "group/card-header @container/card-header grid auto-rows-min items-start gap-1 rounded-t-xl px-4 group-data-[size=sm]/card:px-3 has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto] [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3",
        className
      )}
      {...props}
    />
  )
}

function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-title"
      className={cn(
        "font-heading text-base leading-snug font-medium group-data-[size=sm]/card:text-sm",
        className
      )}
      {...props}
    />
  )
}

function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-description"
      className={cn("text-sm text-muted-foreground", className)}
      {...props}
    />
  )
}

function CardAction({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-action"
      className={cn(
        "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
        className
      )}
      {...props}
    />
  )
}

function CardContent({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-content"
      className={cn("px-4 group-data-[size=sm]/card:px-3", className)}
      {...props}
    />
  )
}

function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
  return (
    <div
      data-slot="card-footer"
      className={cn(
        "flex items-center rounded-b-xl border-t bg-muted/50 p-4 group-data-[size=sm]/card:p-3",
        className
      )}
      {...props}
    />
  )
}

export {
  Card,
  CardHeader,
  CardFooter,
  CardTitle,
  CardAction,
  CardDescription,
  CardContent,
}
d?,Aximport * as React from "react"
import { Input as InputPrimitive } from "@base-ui/react/input"

import { cn } from "@/lib/utils"

function Input({ className, type, ...props }: React.ComponentProps<"input">) {
  return (
    <InputPrimitive
      type={type}
      data-slot="input"
      className={cn(
        "h-8 w-full min-w-0 rounded-lg border border-input bg-transparent px-2.5 py-1 text-base transition-colors outline-none file:inline-flex file:h-6 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:pointer-events-none disabled:cursor-not-allowed disabled:bg-input/50 disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40",
        className
      )}
      {...props}
    />
  )
}

export { Input }
|2g x"use client"

import * as React from "react"

import { cn } from "@/lib/utils"

function Label({ className, ...props }: React.ComponentProps<"label">) {
  return (
    <label
      data-slot="label"
      className={cn(
        "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
        className
      )}
      {...props}
    />
  )
}

export { Label }
?x "use client"

import * as React from "react"
import { Select as SelectPrimitive } from "@base-ui/react/select"

import { cn } from "@/lib/utils"
import { ChevronDownIcon, CheckIcon, ChevronUpIcon } from "lucide-react"

const Select = SelectPrimitive.Root

function SelectGroup({ className, ...props }: SelectPrimitive.Group.Props) {
  return (
    <SelectPrimitive.Group
      data-slot="select-group"
      className={cn("scroll-my-1 p-1", className)}
      {...props}
    />
  )
}

function SelectValue({ className, ...props }: SelectPrimitive.Value.Props) {
  return (
    <SelectPrimitive.Value
      data-slot="select-value"
      className={cn("flex flex-1 text-left", className)}
      {...props}
    />
  )
}

function SelectTrigger({
  className,
  size = "default",
  children,
  ...props
}: SelectPrimitive.Trigger.Props & {
  size?: "sm" | "default"
}) {
  return (
    <SelectPrimitive.Trigger
      data-slot="select-trigger"
      data-size={size}
      className={cn(
        "flex w-fit items-center justify-between gap-1.5 rounded-lg border border-input bg-transparent py-2 pr-2 pl-2.5 text-sm whitespace-nowrap transition-colors outline-none select-none focus-visible:border-ring focus-visible:ring-3 focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-3 aria-invalid:ring-destructive/20 data-placeholder:text-muted-foreground data-[size=default]:h-8 data-[size=sm]:h-7 data-[size=sm]:rounded-[min(var(--radius-md),10px)] *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-1.5 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:border-destructive/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      {children}
      <SelectPrimitive.Icon
        render={
          <ChevronDownIcon className="pointer-events-none size-4 text-muted-foreground" />
        }
      />
    </SelectPrimitive.Trigger>
  )
}

function SelectContent({
  className,
  children,
  side = "bottom",
  sideOffset = 4,
  align = "center",
  alignOffset = 0,
  alignItemWithTrigger = true,
  ...props
}: SelectPrimitive.Popup.Props &
  Pick<
    SelectPrimitive.Positioner.Props,
    "align" | "alignOffset" | "side" | "sideOffset" | "alignItemWithTrigger"
  >) {
  return (
    <SelectPrimitive.Portal>
      <SelectPrimitive.Positioner
        side={side}
        sideOffset={sideOffset}
        align={align}
        alignOffset={alignOffset}
        alignItemWithTrigger={alignItemWithTrigger}
        className="isolate z-50"
      >
        <SelectPrimitive.Popup
          data-slot="select-content"
          data-align-trigger={alignItemWithTrigger}
          className={cn("relative isolate z-50 max-h-(--available-height) w-(--anchor-width) min-w-36 origin-(--transform-origin) overflow-x-hidden overflow-y-auto rounded-lg bg-popover text-popover-foreground shadow-md ring-1 ring-foreground/10 duration-100 data-[align-trigger=true]:animate-none data-[side=bottom]:slide-in-from-top-2 data-[side=inline-end]:slide-in-from-left-2 data-[side=inline-start]:slide-in-from-right-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-open:animate-in data-open:fade-in-0 data-open:zoom-in-95 data-closed:animate-out data-closed:fade-out-0 data-closed:zoom-out-95", className )}
          {...props}
        >
          <SelectScrollUpButton />
          <SelectPrimitive.List>{children}</SelectPrimitive.List>
          <SelectScrollDownButton />
        </SelectPrimitive.Popup>
      </SelectPrimitive.Positioner>
    </SelectPrimitive.Portal>
  )
}

function SelectLabel({
  className,
  ...props
}: SelectPrimitive.GroupLabel.Props) {
  return (
    <SelectPrimitive.GroupLabel
      data-slot="select-label"
      className={cn("px-1.5 py-1 text-xs text-muted-foreground", className)}
      {...props}
    />
  )
}

function SelectItem({
  className,
  children,
  ...props
}: SelectPrimitive.Item.Props) {
  return (
    <SelectPrimitive.Item
      data-slot="select-item"
      className={cn(
        "relative flex w-full cursor-default items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground not-data-[variant=destructive]:focus:**:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 *:[span]:last:flex *:[span]:last:items-center *:[span]:last:gap-2",
        className
      )}
      {...props}
    >
      <SelectPrimitive.ItemText className="flex flex-1 shrink-0 gap-2 whitespace-nowrap">
        {children}
      </SelectPrimitive.ItemText>
      <SelectPrimitive.ItemIndicator
        render={
          <span className="pointer-events-none absolute right-2 flex size-4 items-center justify-center" />
        }
      >
        <CheckIcon className="pointer-events-none" />
      </SelectPrimitive.ItemIndicator>
    </SelectPrimitive.Item>
  )
}

function SelectSeparator({
  className,
  ...props
}: SelectPrimitive.Separator.Props) {
  return (
    <SelectPrimitive.Separator
      data-slot="select-separator"
      className={cn("pointer-events-none -mx-1 my-1 h-px bg-border", className)}
      {...props}
    />
  )
}

function SelectScrollUpButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollUpArrow>) {
  return (
    <SelectPrimitive.ScrollUpArrow
      data-slot="select-scroll-up-button"
      className={cn(
        "top-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <ChevronUpIcon
      />
    </SelectPrimitive.ScrollUpArrow>
  )
}

function SelectScrollDownButton({
  className,
  ...props
}: React.ComponentProps<typeof SelectPrimitive.ScrollDownArrow>) {
  return (
    <SelectPrimitive.ScrollDownArrow
      data-slot="select-scroll-down-button"
      className={cn(
        "bottom-0 z-10 flex w-full cursor-default items-center justify-center bg-popover py-1 [&_svg:not([class*='size-'])]:size-4",
        className
      )}
      {...props}
    >
      <ChevronDownIcon
      />
    </SelectPrimitive.ScrollDownArrow>
  )
}

export {
  Select,
  SelectContent,
  SelectGroup,
  SelectItem,
  SelectLabel,
  SelectScrollDownButton,
  SelectScrollUpButton,
  SelectSeparator,
  SelectTrigger,
  SelectValue,
}
#"x!"use client"

import { Separator as SeparatorPrimitive } from "@base-ui/react/separator"

import { cn } from "@/lib/utils"

function Separator({
  className,
  orientation = "horizontal",
  ...props
}: SeparatorPrimitive.Props) {
  return (
    <SeparatorPrimitive
      data-slot="separator"
      orientation={orientation}
      className={cn(
        "shrink-0 bg-border data-horizontal:h-px data-horizontal:w-full data-vertical:w-px data-vertical:self-stretch",
        className
      )}
      {...props}
    />
  )
}

export { Separator }
ϳLx5"use client"

import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react"

const Toaster = ({ ...props }: ToasterProps) => {
  const { theme = "system" } = useTheme()

  return (
    <Sonner
      theme={theme as ToasterProps["theme"]}
      className="toaster group"
      icons={{
        success: (
          <CircleCheckIcon className="size-4" />
        ),
        info: (
          <InfoIcon className="size-4" />
        ),
        warning: (
          <TriangleAlertIcon className="size-4" />
        ),
        error: (
          <OctagonXIcon className="size-4" />
        ),
        loading: (
          <Loader2Icon className="size-4 animate-spin" />
        ),
      }}
      style={
        {
          "--normal-bg": "var(--popover)",
          "--normal-text": "var(--popover-foreground)",
          "--normal-border": "var(--border)",
          "--border-radius": "var(--radius)",
        } as React.CSSProperties
      }
      toastOptions={{
        classNames: {
          toast: "cn-toast",
        },
      }}
      {...props}
    />
  )
}

export { Toaster }
+\xV"use client"

import { Tabs as TabsPrimitive } from "@base-ui/react/tabs"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

function Tabs({
  className,
  orientation = "horizontal",
  ...props
}: TabsPrimitive.Root.Props) {
  return (
    <TabsPrimitive.Root
      data-slot="tabs"
      data-orientation={orientation}
      className={cn(
        "group/tabs flex gap-2 data-horizontal:flex-col",
        className
      )}
      {...props}
    />
  )
}

const tabsListVariants = cva(
  "group/tabs-list inline-flex w-fit items-center justify-center rounded-lg p-[3px] text-muted-foreground group-data-horizontal/tabs:h-8 group-data-vertical/tabs:h-fit group-data-vertical/tabs:flex-col data-[variant=line]:rounded-none",
  {
    variants: {
      variant: {
        default: "bg-muted",
        line: "gap-1 bg-transparent",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

function TabsList({
  className,
  variant = "default",
  ...props
}: TabsPrimitive.List.Props & VariantProps<typeof tabsListVariants>) {
  return (
    <TabsPrimitive.List
      data-slot="tabs-list"
      data-variant={variant}
      className={cn(tabsListVariants({ variant }), className)}
      {...props}
    />
  )
}

function TabsTrigger({ className, ...props }: TabsPrimitive.Tab.Props) {
  return (
    <TabsPrimitive.Tab
      data-slot="tabs-trigger"
      className={cn(
        "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-1.5 py-0.5 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-vertical/tabs:w-full group-data-vertical/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 has-data-[icon=inline-end]:pr-1 has-data-[icon=inline-start]:pl-1 aria-disabled:pointer-events-none aria-disabled:opacity-50 dark:text-muted-foreground dark:hover:text-foreground group-data-[variant=default]/tabs-list:data-active:shadow-sm group-data-[variant=line]/tabs-list:data-active:shadow-none [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
        "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-active:bg-transparent dark:group-data-[variant=line]/tabs-list:data-active:border-transparent dark:group-data-[variant=line]/tabs-list:data-active:bg-transparent",
        "data-active:bg-background data-active:text-foreground dark:data-active:border-input dark:data-active:bg-input/30 dark:data-active:text-foreground",
        "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-horizontal/tabs:after:inset-x-0 group-data-horizontal/tabs:after:bottom-[-5px] group-data-horizontal/tabs:after:h-0.5 group-data-vertical/tabs:after:inset-y-0 group-data-vertical/tabs:after:-right-1 group-data-vertical/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-active:after:opacity-100",
        className
      )}
      {...props}
    />
  )
}

function TabsContent({ className, ...props }: TabsPrimitive.Panel.Props) {
  return (
    <TabsPrimitive.Panel
      data-slot="tabs-content"
      className={cn("flex-1 text-sm outline-none", className)}
      {...props}
    />
  )
}

export { Tabs, TabsList, TabsTrigger, TabsContent, tabsListVariants }
9vIx.3o")
function Textarea({ className, ...props 
"textarea"	"textarea
      data-slot="textarea )flex field-sizing-content min-h-16 w-full0x-2.5 py-2 text-basei i"obg-input/50j;md:text-sm dark:bg-input/30 dark:disabled:bg-input/80 dark:)3
export { Textarea }
g%x340031Q(JI--)+)f'a]-}(tp5{y,e
tdp9ak5f !px"export const ALLOWED_HEADERS = new Set([
	"content-type",
	"accept",
	"accept-encoding",
	"accept-language",
	"user-agent",
	"referer",
	"origin",
	"authorization",
	"proxy-authorization",
	"cache-control",
]);

export const BLOCKED_HEADERS = new Set([
	"x-vercel-id",
	"x-vercel-deployment-url",
	"x-vercel-oidc-token",
	"x-vercel-oidc-token-ts",
	"x-vercel-signature",
	"x-vercel-edgified",
	"x-vercel-ip-city",
	"x-vercel-ip-country",
	"x-vercel-ip-country-region",
	"x-vercel-ip-latency",
	"x-vercel-deployment-config",
	"x-vercel-rewritten-query",
	"cf-ray",
	"cf-connecting-ip",
	"cf-ipcountry",
	"cf-ray-id",
	"x-forwarded-for",
	"x-forwarded-host",
	"x-forwarded-proto",
	"forwarded",
	"cookie",
	"set-cookie",
	"x-real-ip",
	"x-cluster-client-ip",
	"x-api-key",
	"x-cache",
]);

const INTERNAL_HEADERS = new Set(["x-relay-target", "x-relay-path", "host"]);

export function normalizeTargetUrl(target: string | null, relayPath: string): string | null {
	if (!target) return null;
	return target.replace(/\/$/, "") + relayPath;
}

export function filterHeaders(headers: Headers): Headers {
	const filtered = new Headers();
	for (const [key, value] of headers.entries()) {
		const lowerKey = key.toLowerCase();
		if (INTERNAL_HEADERS.has(lowerKey)) continue;
		if (BLOCKED_HEADERS.has(lowerKey)) continue;
		if (lowerKey.startsWith("x-vercel-")) continue;
		if (lowerKey.startsWith("cf-")) continue;
		if (lowerKey.startsWith("x-forwarded-")) continue;
		// Forward remaining headers
		filtered.set(key, value);
	}
	return filtered;
}

export function shouldSendBody(method: string): boolean {
	return method !== "GET" && method !== "HEAD";
}

export function buildRelayRequest(
	req: Request,
	targetUrl: string,
	headers: Headers
): RequestInit {
	return {
		method: req.method,
		headers,
		body: shouldSendBody(req.method) ? req.body : undefined,
		duplex: "half",
	} as any;
}

export function isAllowedTarget(url: string): boolean {
	try {
		const parsed = new URL(url);
		return ["http:", "https:"].includes(parsed.protocol);
	} catch {
		return false;
	}
}

export function createRelayResponse(response: Response): Response {
	const headers = new Headers(response.headers);
	// Remove headers that would confuse the browser or were handled by the proxy
	headers.delete("content-encoding");
	headers.delete("content-length");
	headers.delete("transfer-encoding");
	headers.delete("connection");
	headers.delete("keep-alive");
	headers.delete("x-frame-options"); // Allow embedding if needed
	headers.delete("content-security-policy");

	// Add CORS for browser UI
	headers.set("Access-Control-Allow-Origin", "*");
	headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, PATCH, OPTIONS");
	headers.set("Access-Control-Allow-Headers", "*");

	return new Response(response.body, {
		status: response.status,
		headers,
	});
}
 Y
x Yimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}
::*)xmQN0+"6.HPTTHp*ī/ٛRT߱'=3Kp ʑi/ {@,z^ړ[´$<<%G08g$M_PmtGn^xt8S+gfg[k3ei	N6uw? `UT^f>9T@\0$ƂTLh<|<wMe$ZC{/O<VELY9.-_u*Yn(nʐTWB{9Bq<8+3i~ Jyxq {
  "$schema": "https://openapi.vercel.sh/vercel.json",
  "bunVersion": "1",
  "installCommand": "bun install"
}
#&xf40000 .claude QIO긑ibf \ւh100644 .gitignore o2\P&]![A100644 .mcp.json B*?k_[7)(Ĩ2100644 CLAUDE.md ,ǘNFܬi 100644 README.md _WS?eO:100644 biome.json _ruD=㿈{O100644 bun.lock t}d1XR䱉100644 eslint.config.mjs &Ѵ qm+yge100644 index.test.ts T,=\U3f7U%;100644 next.config.ts :y_圲S9100644 package.json Tu])W100644 postcss.config.mjs ahI|qJISC40000 public ȏ8AvYBxOr40000 src qcvU?q100644 tsconfig.json Ϝegji7MzIw100644 vercel.json @H+_JPcb˳Ksx  km#!Y_>kP63x5klu<m߶F2`[{؆h [nd)ԡSJ,pmgp]01Pnq0؇Y>ɓ'V+Ř)P`A5!51ЦTӦ$=5KC/TVSʂ$uV+T޼ߩׯ0tǦh^1jRKoԦBW&F73tNˮ\cTǇP:cjCꃻ=u
?ܩGfcjmwLEU=/iPyա915ĥ,STz]1R>ȅ	$󦢊䫷MG-h&njֹəE(,"=\ <`L^bO _1G!ib@NnDH	*°I&YC"em83*]-{<ۏX%6A:0!CO|lrF`8$XfzK/^D!YM8֒h8K㊫k§G݀Y?y)x_B!cQCB}Pg&2~#ItrUPK{=2	0su̳}cWC+I,3=dߛ`'ARK>2~`ٔQWϡ[eCVfN1"ͧw'(iYBOU{T@->\(I$?k{sNÑMoUIx[6eb7+212 Eux< 40000 app Kjx\L&40000 lib F7p1O4W*xx I40000 api aG8\ONAK5$100644 favicon.ico qoH5-$jݷv$100644 globals.css A^5 ˢ7t100644 layout.tsx H
;^C85nĵp~#;100644 page.tsx YQE+24@J3x@import "tailwindcss";

:root {
  --background: #ffffff;
  --foreground: #171717;
}

@theme inline {
  --color-background: var(--background);
  --color-foreground: var(--foreground);
  --font-sans: var(--font-geist-sans);
  --font-mono: var(--font-geist-mono);
}

@media (prefers-color-scheme: dark) {
  :root {
    --background: #0a0a0a;
    --foreground: #ededed;
  }
}

body {
  background: var(--background);
  color: var(--foreground);
  font-family: Arial, Helvetica, sans-serif;
}
ƒnRx[i3,0 -x}"use client";

import { useState } from "react";

export default function Home() {
  const [targetUrl, setTargetUrl] = useState("https://httpbin.org/anything");
  const [method, setMethod] = useState("GET");
  const [requestBody, setRequestBody] = useState(JSON.stringify({
    message: "Hello from Edge Proxy",
    timestamp: Date.now(),
  }, null, 2));
  const [headerKey, setHeaderKey] = useState("");
  const [headerValue, setHeaderValue] = useState("");
  const [response, setResponse] = useState<any>(null);
  const [loading, setLoading] = useState(false);

  const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];

  async function sendRequest() {
    setLoading(true);
    setResponse(null);

    const headers: Record<string, string> = {
      'x-relay-target': targetUrl,
      'Content-Type': 'application/json'
    };

    if (headerKey && headerValue) {
      headers[headerKey] = headerValue;
    }
    // console.log('Request Headers:', headers);
    try {
      const start = Date.now();
      const fetchOptions: any = {
        method,
        headers
      };

      if (!['GET', 'HEAD', 'OPTIONS'].includes(method) && requestBody) {
        fetchOptions.body = requestBody;
      }

      const res = await fetch('/api/relay', fetchOptions);
      const elapsed = Date.now() - start;

      let responseText = '';
      const contentType = res.headers.get('content-type') || '';

      if (contentType.includes('application/json')) {
        const data = await res.json();
        responseText = JSON.stringify(data, null, 2);
      } else {
        responseText = await res.text();
      }

      const headersObj: Record<string, string> = {};
      res.headers.forEach((value, key) => {
        headersObj[key] = value;
      });

      setResponse({
        status: res.status,
        statusText: res.statusText,
        time: `${elapsed}ms`,
        body: responseText,
        headers: JSON.stringify(headersObj, null, 2)
      });
    } catch (err: any) {
      setResponse({
        status: 'Error',
        body: err.message,
        headers: '-'
      });
    } finally {
      setLoading(false);
    }
  }

  return (
    <div className="min-h-screen bg-slate-900 text-slate-200 p-8 max-w-6xl mx-auto font-sans">
      <h1 className="text-3xl font-bold text-sky-400 mb-2">Edge Proxy Relay Test</h1>
      <p className="text-slate-400 mb-8">Standardized Next.js Migration</p>

      <div className="bg-slate-800 rounded-xl p-6 border border-slate-700 mb-6">
        <h2 className="text-sky-400 text-sm font-bold uppercase tracking-wider mb-4">Target Configuration</h2>
        <div className="flex flex-col gap-4">
          <div>
            <label className="block text-xs text-slate-500 font-bold uppercase mb-2">Target URL</label>
            <input
              className="w-full bg-slate-950 border border-slate-700 rounded-lg p-3 font-mono text-sm focus:border-sky-500 outline-none"
              value={targetUrl}
              onChange={(e) => setTargetUrl(e.target.value)}
            />
          </div>
          <div className="flex gap-2 flex-wrap">
            {['https://httpbin.org/anything', 'https://httpbin.org/get', 'https://jsonplaceholder.typicode.com/posts/1'].map(url => (
              <button
                key={url}
                className="text-xs bg-slate-700 hover:bg-slate-600 px-3 py-1 rounded transition"
                onClick={() => setTargetUrl(url)}
              >
                {url.split('/').pop() || url}
              </button>
            ))}
          </div>
        </div>
      </div>

      <div className="bg-slate-800 rounded-xl p-6 border border-slate-700 mb-6">
        <h2 className="text-sky-400 text-sm font-bold uppercase tracking-wider mb-4">Method</h2>
        <div className="flex gap-2 flex-wrap">
          {methods.map(m => (
            <button
              key={m}
              className={`px-4 py-2 rounded font-bold text-sm transition ${method === m ? 'bg-sky-500 text-slate-900' : 'bg-slate-700 hover:bg-slate-600'}`}
              onClick={() => setMethod(m)}
            >
              {m}
            </button>
          ))}
        </div>
      </div>

      <div className="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
        <div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
          <h2 className="text-sky-400 text-sm font-bold uppercase tracking-wider mb-4">Body (JSON)</h2>
          <textarea
            className="w-full h-48 bg-slate-950 border border-slate-700 rounded-lg p-3 font-mono text-xs focus:border-sky-500 outline-none resize-none"
            value={requestBody}
            onChange={(e) => setRequestBody(e.target.value)}
          />
        </div>
        <div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
          <h2 className="text-sky-400 text-sm font-bold uppercase tracking-wider mb-4">Custom Headers</h2>
          <div className="space-y-4">
            <div>
              <label className="block text-xs text-slate-500 font-bold uppercase mb-2">Key</label>
              <input
                className="w-full bg-slate-950 border border-slate-700 rounded-lg p-3 font-mono text-sm focus:border-sky-500 outline-none"
                placeholder="x-custom-header"
                value={headerKey}
                onChange={(e) => setHeaderKey(e.target.value)}
              />
            </div>
            <div>
              <label className="block text-xs text-slate-500 font-bold uppercase mb-2">Value</label>
              <input
                className="w-full bg-slate-950 border border-slate-700 rounded-lg p-3 font-mono text-sm focus:border-sky-500 outline-none"
                placeholder="value"
                value={headerValue}
                onChange={(e) => setHeaderValue(e.target.value)}
              />
            </div>
          </div>
        </div>
      </div>

      <button
        className="w-full py-4 bg-sky-500 hover:bg-sky-400 text-slate-900 font-bold rounded-xl transition shadow-lg shadow-sky-500/20 disabled:opacity-50"
        onClick={sendRequest}
        disabled={loading}
      >
        {loading ? "Sending..." : "Send Request"}
      </button>

      {response && (
        <div className="mt-8 space-y-6">
          <div className="flex gap-4">
            <div className="bg-slate-800 px-4 py-2 rounded-lg border border-slate-700">
              <span className="text-xs text-slate-500 font-bold uppercase mr-2">Status:</span>
              <span className={`font-bold ${response.status >= 200 && response.status < 300 ? 'text-green-400' : 'text-orange-400'}`}>
                {response.status}
              </span>
            </div>
            <div className="bg-slate-800 px-4 py-2 rounded-lg border border-slate-700">
              <span className="text-xs text-slate-500 font-bold uppercase mr-2">Time:</span>
              <span className="font-bold text-slate-200">{response.time}</span>
            </div>
          </div>

          <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
            <div>
              <label className="block text-xs text-slate-500 font-bold uppercase mb-2">Response Body</label>
              <pre className="bg-slate-950 p-4 rounded-lg border border-slate-700 overflow-auto max-h-96 text-xs text-green-400 font-mono">
                {response.body}
              </pre>
            </div>
            <div>
              <label className="block text-xs text-slate-500 font-bold uppercase mb-2">Response Headers</label>
              <pre className="bg-slate-950 p-4 rounded-lg border border-slate-700 overflow-auto max-h-96 text-xs text-slate-400 font-mono">
                {response.headers}
              </pre>
            </div>
          </div>
        </div>
      )}
    </div>
  );
}
x* 100644 relay-utils.ts oޣG_(Ix< 40000 app H'LӪ9+b^ 440000 lib (1}	g|6ǔ|scx& 40000 api Z-94NR9gqx  40000 relay H<lX(#9,nќ3x$ 100644 route.ts   \
Pµj~qximport {
	buildRelayRequest,
	createRelayResponse,
	normalizeTargetUrl,
	filterHeaders,
	isAllowedTarget,
} from "@/lib/relay-utils";

export const runtime = "edge";

const ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);

export async function POST(req: Request) { return await handler(req); }
export async function GET(req: Request) { return await handler(req); }
export async function PUT(req: Request) { return await handler(req); }
export async function DELETE(req: Request) { return await handler(req); }
export async function PATCH(req: Request) { return await handler(req); }
export async function HEAD(req: Request) { return await handler(req); }
export async function OPTIONS(req: Request) { return await handler(req); }

async function handler(req: Request): Promise<Response> {
	if (!ALLOWED_METHODS.has(req.method)) {
		return new Response(JSON.stringify({ error: "Method not allowed" }), {
			status: 405,
			headers: { "content-type": "application/json" },
		});
	}

	const target = req.headers.get("x-relay-target");
	const relayPath = req.headers.get("x-relay-path") || "/";

	const targetUrl = normalizeTargetUrl(target, relayPath);
	if (!targetUrl) {
		return new Response(
			JSON.stringify({ error: "Missing x-relay-target header" }),
			{
				status: 400,
				headers: { "content-type": "application/json" },
			},
		);
	}

	if (!isAllowedTarget(targetUrl)) {
		return new Response(
			JSON.stringify({ error: "Target domain not allowed" }),
			{
				status: 403,
				headers: { "content-type": "application/json" },
			},
		);
	}

	const headers = filterHeaders(new Headers(req.headers));
	const fetchOptions = buildRelayRequest(req, targetUrl, headers);

	const response = await fetch(targetUrl, fetchOptions);
	return createRelayResponse(response);
}
Yz%xk'WqIQfAPjNbDe&=2yi


Ey%V
J)J
dq%(lfavgF2ar [5l9 K#_xoB~Nf
YҼT[ԔT+JK2C4RRKSK4RKJ3K2RrR@4jR+
Jqw%ΘzR9.>!XL'٨ gjE
x&.!5g3s$L>Ē XQAx[{'[ZfNIjW'lvbeNd;^ϕZQ_TW\`Zd*G2k+v@^g=\]pJx# 4Uwx}if (req.method === "OPTIONS"null, {
			status: 204,
			headers: {
				"Access-Control-Allow-Origin": "*",
				"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, PATCH, OPTIONS",
				"Access-Control-Allow-Headers": "*",
				"Access-Control-Max-Age": "86400",
			},
		});
	}

	cW#, "Access-Control-Allow-Origin": "*Z#, "Access-Control-Allow-Origin": "**m˪x* 100644 relay-utils.ts ,ͤ@x;export const config = { runtime: "edge" };

// Allowlist: only these headers are forwarded to prevent leaking
// Vercel internal metadata, credentials, and infrastructure info
const ALLOWED_HEADERS = new Set([
	"content-type",
	"accept",
	"accept-encoding",
	"accept-language",
	"user-agent",
	"referer",
	"origin",
	"authorization",
	"proxy-authorization",
	"cache-control",
]);

// Blocklist: sensitive headers that should NEVER be forwarded
const BLOCKED_HEADERS = new Set([
	"x-vercel-id",
	"x-vercel-deployment-url",
	"x-vercel-oidc-token",
	"x-vercel-oidc-token-ts",
	"x-vercel-signature",
	"x-vercel-edgified",
	"x-vercel-ip-city",
	"x-vercel-ip-country",
	"x-vercel-ip-country-region",
	"x-vercel-ip-latency",
	"x-vercel-deployment-config",
	"x-vercel-rewritten-query",
	"cf-ray",
	"cf-connecting-ip",
	"cf-ipcountry",
	"cf-ray-id",
	"x-forwarded-for",
	"x-forwarded-host",
	"x-forwarded-proto",
	"forwarded",
	"cookie",
	"set-cookie",
	"x-real-ip",
	"x-cluster-client-ip",
	"x-api-key",
	"x-cache",
]);

const INTERNAL_HEADERS = new Set(["x-relay-target", "x-relay-path", "host"]);

export function isAllowedTarget(url: string): boolean {
	try {
		const parsed = new URL(url);
		return ["http:", "https:"].includes(parsed.protocol);
	} catch {
		return false;
	}
}

export function normalizeTargetUrl(target: string | null, relayPath: string): string | null {
	if (!target) return null;
	return target.replace(/\/$/, "") + relayPath;
}

export function filterHeaders(headers: Headers): Headers {
	const filtered = new Headers();
	for (const [key, value] of headers.entries()) {
		const lowerKey = key.toLowerCase();
		if (INTERNAL_HEADERS.has(lowerKey)) continue;
		if (BLOCKED_HEADERS.has(lowerKey)) continue;
		if (lowerKey.startsWith("x-vercel-")) continue;
		if (lowerKey.startsWith("cf-")) continue;
		if (lowerKey.startsWith("x-forwarded-")) continue;
		if (ALLOWED_HEADERS.has(lowerKey)) {
			filtered.set(key, value);
			continue;
		}
		filtered.set(key, value);
	}
	return filtered;
}

export function shouldSendBody(method: string): boolean {
	return method !== "GET" && method !== "HEAD";
}

const ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);

export default async function handler(req: Request): Promise<Response> {
	if (!ALLOWED_METHODS.has(req.method)) {
		return new Response(JSON.stringify({ error: "Method not allowed" }), {
			status: 405,
			headers: { "content-type": "application/json" },
		});
	}

	const target = req.headers.get("x-relay-target");
	const relayPath = req.headers.get("x-relay-path") || "/";
	const targetUrl = normalizeTargetUrl(target, relayPath);

	if (!targetUrl) {
		return new Response(
			JSON.stringify({ error: "Missing x-relay-target header" }),
			{ status: 400, headers: { "content-type": "application/json" } },
		);
	}

	if (!isAllowedTarget(targetUrl)) {
		return new Response(
			JSON.stringify({ error: "Target domain not allowed" }),
			{ status: 403, headers: { "content-type": "application/json" } },
		);
	}

	const headers = filterHeaders(new Headers(req.headers));
	const fetchOptions: RequestInit = {
		method: req.method,
		headers,
		body: shouldSendBody(req.method) ? req.body : undefined,
		duplex: "half",
	};

	const response = await fetch(targetUrl, fetchOptions);
	return new Response(response.body, {
		status: response.status,
		headers: response.headers,
	});
}NIxEJ@0+D)/pz}FQ!je#q%9|#\#
>7yFk/u4BP"EW{QYca(jT<p©m9t#Pb2k5>5zs0<$+l
,]]>OcL*P!k w!H,~9D*MǗDA	C.b$21X3hEP k5W/sHgAx Hheaders.delete("content-encoding");
	headers.delete("content-length");
	headers.delete("transfer-encoding");
	headers.delete("c+onnection");
	headers.delete("keep-alive");TF?Fx 8	
	// Add CORS for browser UI
	headers.set("Access-Control-Allow-Origin", "*");
	headers.set("Access-Control-Allow-Methods", "GE[T, POST, PUT, DELETE, PATCH, OPTIONS");
	headers.set("Access-Control-Allow-Headers", "*");
7	TG(x31 ҔT@OW;&f&Icd2CDA/=$3=/(!h옞wl~z]1LUnr^Vq~IeZ-G_81U/7Ag3FPN>
\]|z݈]m/V&u77&`g'0
ܵSr
qX_|/\>Q8LUi^N~r6X˞"o~'XM̼
btľjfکjZTa^jE^r~^Zf:HewLf`˵rgë6,HLNL:olUjr'*X bPM1I0x|evò(9%0Ks=yZ'~{zbZqQ2ÓL%3
J]SjVIbfN9ЯȆ-5uk>э&'32/Ճ)/y+wnvNKǼfW
RSs 1CIm I_?8s +x; Ghgu$ngՉwj4
SEEzlr(?\Jx" +e%R
}5E0x  w&?qpx\t|(g.xV L8HgԔuQg2|E\@:40000 src >A\\u+p
=Pa%xW[-Z XQ$vl'1-8'qK,v'v;IQUjQU"*,h-Rxi%PQm2K~sN^xB&H+{UNW^Y},!l?/ak=އGGpN5qR;;2`>s}:&m0R=oŒdd&VnXS{t#칻$Llo4>Isd)7dP<WPX]?~}yiρX705g /3i42,?Q
|>'>prKHvFY9R4~g͞+wL͵֘w'|\Udk4'V2`twA5+x(>M`c&qVfjiezGTGV={ni]#Q7ws}nJMmcCـ"4塇#Zo9S|/j	{h0Z%lLTQ®KrW%û^&},4젖/L(żP!ѭЬ>(MF_)zr٢./l9stkdrax^=u8Ƀeza[n@Do4AFBzB@r%
`$/ȼͨchvځƹXTX]ڟx(~큍Ci}oq\RAs6\{?hQtM4W-8?s8t05穮<r;]w:zyZPb{24*XZt3C1 _;̢,#L3/~nC,
xnS
a#{\Avmدj?*S%дP` 4Fw4@bB`)YuTMQ&v|[}{=;~9 YGH
8L%UJDG.mPKù7S*ftИY9)mK)

(;CR@8,8(5Gu5l?HCdsH$j,zf:hT*@6:JB٬,Z&@9I\:fPƋNDg]<7-u==?9v­w:imnI$	#[;8:\ڊHtQS$,C0nI'*Ct:VU!q0BkR@L6Z4/z-)ؓ8#˝ Q[rՀ :_jAf|AkUĴlry_[mp!rVFئ1NҦmzBbo7|	r8p{쐋K8LѻNߙݞ\JT?z}g/o)k3e&-4K'ѯ֘C敻=pwPR}زQZmIiteОYB1)^d KT"\IQXR)_sܶ]_zX\);cя?}BZGpN;
UZ^p+PR՚|kʆ.NT[z?T뮒;o`%ZY*+э~.죧ѧF?_GO<}A7b}HyvFkjT'wn|[ `ɘ5Do96؃M[`tam{mr}lVuH]zr"P^M[??ρ7yyQKg8sE7Zh)n4
^]}(=6sR*s0mYA]bP(fMbP6o2ы_=t\֕nCͩ:,Stmf>h	B>@jTy<~)oټ,*3 _ f/sEB^ʚh-}8*ȴ}$#c,L6xJM͊Jst*uCE9x->Oπ7Kp*qJ< ?D|nm	=6qbA|dޞ[3?llq)ageVTO㩂q}:I4kR)P]iz4|H.kZ>Fxtׁ#XƐ4!za	iP+fqĔuQC)ah =A>JAXE5+ES[f/ęKBⷆ[Y9gb)xM -VlDB&| ;(@EG[!avγBc	{m  }
}
+ x-/*Q(,HUVK(qKLWUH+UP
[sq%(xVȊmJ3s2҃s3sSujS+% b ,hlx iJbع%xmQAn <ۯ87$q+)6[8u]u`ggLG3uӎ!ȱS"wNY$\ S([2֎*"|.,Dé 8'tϳ;Q,h	S9tFW9D"c.N:FIh#s :y:؍4R!=c0%rǏlk||Ϛ³ T
!(BլZo53^V1	\u/o#x oKF,x< 40000 app _BԒB炂*:;7LF+40000 lib (1}	g|6ǔ7x p40000 api Z-94NR9g100644 globals.css <ꬰ왖:׍Y100644 layout.tsx H
;^C85nĵp~#;100644 page.tsx YQE+24@I<x @import "tailwindcss";
g*xze_jEF=FԢslºyez)z%J:
JZZZ
[P Xdr52 FhTx =7
      "dom",
      "dom.iterable",
      "esnext"
    \	react-jsx^x
        "./src/*"
      8
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
   !M,
    ".next/dev/types/**/*.ts"
  ],
  "exclude": [
    "node_modules"
  ]
}
)>x\ export default {
  plugins: {
    '@tailwindcss/postcss': {},
    autoprefixer: {},
  },
};
3¬x< 40000 app O.ơ9_*8t40000 lib (1}	g|6ǔx p40000 api Z-94NR9g100644 globals.css g6ϻ"100644 layout.tsx H
;^C85nĵp~#;100644 page.tsx YQE+24@ٸ;x; @tailwind base;
@tailwind components;
@tailwind utilities;
x 2/** @type {import('tailwindcss').Config} */
export default {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
};
=۬x< 40000 app "v()!F*X 40000 lib 'M7s>Gx p40000 api v֛Š^y0`0I100644 globals.css g6ϻ"100644 layout.tsx H
;^C85nĵp~#;100644 page.tsx YQE+24@:x  40000 relay KUMGc2+m>zAx$ 100644 route.ts 
bÕMn,gZLOJx* 100644 relay-utils.ts zȺ@ zErx31 ĂLZ\|W)̳LLss.q>0߭UX_ZWR\%u$Ӽ#[ꄔ?BU$koq:1& 9fxg,(W "x$ q40000 api 3<]+=sMqjx  40000 proxy KUMGc2+m>zfxkdab,E?L jk$x= <=0    // console.log('Request Headers:', headers);}ݮx 40000 lib ?W؂ɿ2RG#Dx* 100644 relay-utils.ts ٠#.BU߳Ax 40000 api jv)O,{Ax$ 100644 proxy.ts oD\E+xxM 
 config = {
  runtime: 'edge',
};=`>
export default handler;
Rx  ["BZʞ`Ax 40000 api 䟄҂--"򲻣x$ 100644 proxy.ts B!޴5z"ryC]x; XյН#
#{Wbz>Չ^"']P/}ЩU/,r(Ux  40000 proxy y33}E(Pe
x$ 100644 route.ts B!޴5z"ry0`xkeleZQ>Q- 'Ex7 ߶
̽it%5\őwgkRfVgDX*x p40000 api  Ok
S	/ۖ<100644 globals.css g6ϻ"100644 layout.tsx H
;^C85nĵp~#;100644 page.tsx }'SMެ 	Lu8x {+.TBȓpEbVx  40000 proxy b+ɜkv[A8#÷x$ 100644 route.ts 4`Jr՝=v:1N)#x 40000 lib +|raDx* 100644 relay-utils.ts <4N1?ZjFS]D/Jx}SMo0WP8h]EXtm@Fmӗv$>=	7F[^ږ7|P#ۛ{Q<>_n (m_+Kш?)`Ce29%[(R`}1Vl
M$(,.!_xN9l?J0(`
Ƅ;	beR	HXe{Ȳ'?tQzNvz'*qŶ|E쵭UԖ8ρ}[28?dKʸuSN-FO-c?B&TD.J컼1iPàBb075˖www'FJb^MvA*	[7[F:"S3=Wj Е	^I7:!wpj&oa0m,r¼>gѱŤx+){T&;q\XBWڎ^#,v_ox 		$,
  } as any2tYLxd 4sR)0Co,^,A;{Hy\YjZ
?PN%xޕ̾]լۡɃDYqu'Z"/T0sxM 4o2\P&]![AHwgkRfVgDDnDd%&D61>"Nx  wgkRfVgDXGx 40000 lib FЍt/`Ԯyx* 100644 relay-utils.ts RBs⽃o7.GW_xR 6}"4N.aFhwQGge_(tz2B0X	b@nK˓* ]K@*߾aߒ7 fvaxR n'	6ُ g( ,:WHºZQ+։DNv'xC _a%&| ;(@CiJAL%[P{mnvRx%ڢ @tailwindcss/postcss": "^4.2.4",N&alloc/quick-lru": ["@alloc/quick-lru@5!;VUrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLwl 4jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.*!@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],

    "@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "d,:@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],

    "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, ,CbRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],

    "@jridgewell/sourcemap-codec":$ ["@jridgewell/sourcemap-codec@1.5.5!CcYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],

    "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3G@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIyt0l1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLA="*ailwindcss/node": ["@tailwindcss/node@4.2. :@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQ[tpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="],

    "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.41%tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="],

    "@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.4", "", { "os": "android", "cpu"8e7MOr1SAn9U8KlZzPi1ZXGZHeC5anY36qjNwmZv9pOJ8E4Q6jmD1vyEHkQFmNOIN7twGPEMXRHmitN4zCMN03g=="],

    "@tailwindcss/oxide-darwin-arm(64": ["@tailwindcss/oxide-darwin-arm64@4%]tSC/Kbqpz/5/o/C2sG7QvOxAKqyd10bq+ypZNf+9Fi2TvbVbv1zNpcEptcsU7DPROaSbVgUXmrzKhurFvo5eDg=="],

    "@tailwindcss/oxide-darwin-x64$": ["@tailwindcss/oxide-darwin-x64@45yPyUXn3yO/ufR6+Kzv0t4fCg2qNr90jxXc5QqBpjlPNd0NqyDXcmQb/6weunH/MEDXW5dhyEi+agTDiqa3WsGg=="],

    "@tailwindcss/oxide-freebsd-x6A4": ["@tailwindcss/oxide-freebsd-x64@4.2.4", "", { "os": "freebsd	BoMIB4vMQtZsXdGLVc2z+P9DbETkiopogfWZKbWwM8b/1Vinbs4YcUwo+kM/KeLkX3Ygrf4/PsRndKaYhS8Eiw=="],

    "@tailwindcss/oxide-linux-arm-9gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.$7pIHBLTHYRAlS7V22JNuTh33yLH4VElwKtB3bwchK/UaKUPpQ0lPQiOWcbm4V3WP2I6fNIJ23vABIvoy2izdwA=="],

    "@tailwindcss/oxide-linux-arm614-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.3+E4wxJ0ZGOzSH325reXTWB48l42i93kQqMvDyz5gqfRzRZ7faNhnmvlV4EPGJU3QJM/3Ab5jhJ5pCRUsKn6OQw=="],

    "@tailwindcss/oxide-linux-arm634-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.3bBADEGAbo4ASnppIziaQJelekCxdMaxisrk+fB7Thit72IBnALp9K6ffA2G4ruj90G9XRS2VQ6q2bCKbfFV82g=="],

    "@tailwindcss/oxide-linux-x64-+&	$tailwindcss/oxide-linux-x64-gnu@4.2.117Mx25E4WTfnht0TVRTyC00j3i0M+EeFe7wguMDTlX4mRxafznw0CA8WJkFjWYH5BlgELd1kSjuU2JiPnNZbJDA=="],

    "@tailwindcss/oxide-linux-x64-/musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.112wwJRF7nyhOR0hhHoChc04xngV3iS+akccHTGtz965FwF0up4b2lOdo6kI1EbDaEXKgvcrFBYcYQQ/rrnWFVfA=="],

    "@tailwindcss/oxide-wasm32-was)i": ["@tailwindcss/oxide-wasm32-wasi@4.2. :@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@ty+bys/wasm-util": "^0.10.1", "tslib": "^2.8.1oFQsqApeor8Fo6gUEklzmaa9994orJZZDBAlQpK2Mq+DslRKFJeD6AjHpBQ0kZFQohVr8o85PPh8eOy86VlSCmw=="],

    "@tailwindcss/oxide-win32-arm644-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.4'2L9BXqxC4ToVgwMFqj3pmZRqyHEztulpUJzCxUtLjobMCzTPsGt1Fa9enKbOpY2iIyVtaHNeNvAK8ERP/64sqGQ=="],

    "@tailwindcss/oxide-win32-x64-0msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.4(0ESlKG0EpVJQwRjXDDa9rLvhEAh0mhP1sF7sap9dNZT0yyl9SAG6T7gdP09EH0vIv0UNTlo6jPWyujD6559fZvw=="],

    "@tailwindcss/postcss": ["@tailwindcss/postcss@4.2. :@alloc/quick-lru": "^5.2.0", "@tailwindcss/node": "4.2.4", "@tailwindcss/oxide": "4.2.4", "postcss": "^8.5.6", "tailwindcss": "4.2.4" } }, "sha512-wgAVj6nUWAolAu8YFvzT2cTBIElWHkjZwFYovF+xsqKsW2ADxM/X2opxj5NsF/qVccAOjRNe8X2IdPzMsWyHTg=="],

    "@types/bu*+nhanced-resolve": ["enhanced-resolve@5.21.0!:graceful-fs": "^4.2.4", "tapable": "^2.3.3" } }, "sha512-otxSQPw4lkOZWkHpB3zaEQs6gWYEsmX4xQF68ElXC/TWvGxGMSGOvoNbaLXm6/cS/fSfHtsEdw90y20PCd+sCA22!graceful-fs": ["graceful-fs@4.2.19RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],

    "jiti": ["jiti@2.7.0", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke77kKbNgQ=="],

    "lightningcss": ["lightningcss@1.32.0!:detect-libc": "^2.0.3" },lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],

    "lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android0mYK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],

    "lightningcs lightningcss-darwin-arm64@1.32.0#!]RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],

    "lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0#1U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],

    "lightningcss-freebsd-x64": ["6lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd	JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],

    "lightningcss-linux-arm-gnueab/ihf": ["lightningcss-linux-arm-gnueabihf@1.32.0Q& x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],

    "lightningcss-linux-arm64-gnu"': ["lightningcss-linux-arm64-gnu@1.32.0Q& $0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],

    "lightningcss-linux-arm64-musl)": ["lightningcss-linux-arm64-musl@1.32.0Q& $UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],

    "lightningcss-linux-x64-gnu": #["lightningcss-linux-x64-gnu@1.32.0Q&0V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],

    "lightningcss-linux-x64-musl":% ["lightningcss-linux-x64-musl@1.32.0Q&0bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],

    "lightningcss-win32-arm64-msvc)": ["lightningcss-win32-arm64-msvc@1.32.0'28SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],

    "lightningcss-win32-x64-msvc":% ["lightningcss-win32-x64-msvc@1.32.0(0Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],

    "magic-string": ["magic-string@0.30.2G@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oi
nyCRyx8T2C3]apable": ["tapable@2.3.3!CUuxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc61E;@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/cor
$@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2RZ8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="],

    "@tailwindcss/oxide-wasm32-wasi/^
X, "bundled": true
oPtailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.G
, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="],

    "S@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1. :@tybys/wasm-util": "^0.10F@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="],

    "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.2!:
, "bundled": true }, "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="],

    "@tailwindcss/oxide-wasm32-wasi/>E "bundled": true [EmG$smxry|Foq.Z.NԂԼԼb%+L^ 3x, z @tailwindcss/postcss": "^4.2.4",vJkxQ export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
};
`xǲeFb㵜j7Vޙ~bb팸C;&`4ip3znz@ibE4D]xR module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
s:x 1/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./app/**/*.{js,ts,jsx,tsx,mdx}",
    "./src/**/*.{js,ts,jsx,tsx,mdx}",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
$>'x31 ҔT@OW;&f&Icd2CDA/=$3=/(͆ M:q:8M.*c8зL}vh˚GVA980_||۾_wg{xCTQ/XQÃ%ʤfĂLIշRK(4.3I׈ŋ/WT'g3$</{~j2RR+2Jrs4z)sx袨*I-.+)fYq@̘i桝֫*,f?u޶Zg5.USuvݛc3? APP̠{Jvi_ԠԜJҒ̜br*}uӥG{Y@*.Jf.|ɓKG'eC"d}78w1!gү~*,K-JŃ(۸yeRܬZ̗M&ej~ Hf$x9 ,RT/E"co !SH]|03^rx$ 100644 index.ts 4 EjI,믈
\iք+x 

	targetUrln]8x# s]*/E  }
}
R6xu>3 DKA!/(71'*5$(=$(G(Z\RYX鑚ZTL*IRKRyũ:\
iE
JzE 	Ғ̜b%k.
&0z2)(Lc2QL RBA!3MACM3zye|#KJLtB=AZMkP-b2|Tǀ@j.pj  kToxm=n0gL^-5[$*u%dd󽧏x/6Pছj^X]7h;3E7%"iڐ%ꀞ^E&:|Ġ7.(EM6t_)pְx#=zl.@eT~:G:s]Jhgh@F_bp'[߀dX>0E׷cD!OoxVٞ
'Wo~=ϲ&6ylADjx|yBe ijx
 5fx& 100644 index.html _rn1j]{%9쳺x;\<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Edge Proxy Test</title>
  <style>
    * { box-sizing: border-box; }

    body {
      font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
      padding: 2rem;
      background: #0f172a;
      color: #e2e8f0;
      max-width: 1200px;
      margin: 0 auto;
    }

    h1 {
      color: #38bdf8;
      margin-bottom: 0.5rem;
    }

    .subtitle {
      color: #94a3b8;
      margin-bottom: 2rem;
    }

    .panel {
      background: #1e293b;
      border-radius: 12px;
      padding: 1.5rem;
      margin-bottom: 1.5rem;
      border: 1px solid #334155;
    }

    .panel h2 {
      color: #38bdf8;
      font-size: 1rem;
      margin: 0 0 1rem 0;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    .form-row {
      display: flex;
      gap: 1rem;
      margin-bottom: 1rem;
      flex-wrap: wrap;
    }

    .form-group {
      flex: 1;
      min-width: 200px;
    }

    label {
      display: block;
      font-size: 0.75rem;
      color: #94a3b8;
      margin-bottom: 0.5rem;
      text-transform: uppercase;
      letter-spacing: 0.05em;
    }

    input, textarea, select {
      width: 100%;
      padding: 0.75rem;
      background: #0f172a;
      border: 1px solid #334155;
      border-radius: 8px;
      color: #e2e8f0;
      font-family: 'Monaco', 'Menlo', monospace;
      font-size: 0.875rem;
    }

    input:focus, textarea:focus, select:focus {
      outline: none;
      border-color: #38bdf8;
      box-shadow: 0 0 0 3px rgba(56, 189, 248, 0.1);
    }

    textarea {
      min-height: 150px;
      resize: vertical;
    }

    .method-buttons {
      display: flex;
      gap: 0.5rem;
    }

    .method-btn {
      padding: 0.5rem 1rem;
      background: #334155;
      border: 1px solid #475569;
      border-radius: 6px;
      color: #e2e8f0;
      cursor: pointer;
      font-weight: 600;
      transition: all 0.15s;
    }

    .method-btn:hover {
      background: #475569;
    }

    .method-btn.active {
      background: #38bdf8;
      border-color: #38bdf8;
      color: #0f172a;
    }

    .method-btn.get.active { background: #22c55e; border-color: #22c55e; }
    .method-btn.post.active { background: #f97316; border-color: #f97316; }
    .method-btn.put.active { background: #3b82f6; border-color: #3b82f6; }
    .method-btn.delete.active { background: #ef4444; border-color: #ef4444; }

    .send-btn {
      width: 100%;
      padding: 1rem;
      background: linear-gradient(135deg, #38bdf8, #0ea5e9);
      border: none;
      border-radius: 8px;
      color: #0f172a;
      font-size: 1rem;
      font-weight: 700;
      cursor: pointer;
      transition: transform 0.15s, box-shadow 0.15s;
    }

    .send-btn:hover {
      transform: translateY(-2px);
      box-shadow: 0 4px 20px rgba(56, 189, 248, 0.4);
    }

    .send-btn:active {
      transform: translateY(0);
    }

    .send-btn:disabled {
      opacity: 0.5;
      cursor: not-allowed;
      transform: none;
    }

    .response-section {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 1.5rem;
    }

    @media (max-width: 768px) {
      .response-section {
        grid-template-columns: 1fr;
      }
    }

    .response-meta {
      display: flex;
      gap: 1rem;
      margin-bottom: 1rem;
      flex-wrap: wrap;
    }

    .meta-badge {
      display: flex;
      align-items: center;
      gap: 0.5rem;
      padding: 0.5rem 1rem;
      background: #0f172a;
      border-radius: 6px;
      font-size: 0.875rem;
    }

    .meta-badge .label {
      color: #64748b;
    }

    .meta-badge .value {
      font-weight: 600;
    }

    .status-2xx { color: #22c55e; }
    .status-3xx { color: #eab308; }
    .status-4xx { color: #f97316; }
    .status-5xx { color: #ef4444; }

    pre {
      background: #0f172a;
      padding: 1rem;
      border-radius: 8px;
      overflow-x: auto;
      font-size: 0.8rem;
      line-height: 1.5;
      margin: 0;
      max-height: 400px;
      overflow-y: auto;
    }

    .error-text {
      color: #ef4444;
      font-family: monospace;
      white-space: pre-wrap;
    }

    .headers-list {
      font-size: 0.75rem;
    }

    .headers-list div {
      padding: 0.25rem 0;
      border-bottom: 1px solid #334155;
    }

    .headers-list div:last-child {
      border-bottom: none;
    }

    .spinner {
      display: inline-block;
      width: 1rem;
      height: 1rem;
      border: 2px solid #0f172a;
      border-top-color: #38bdf8;
      border-radius: 50%;
      animation: spin 0.8s linear infinite;
    }

    @keyframes spin {
      to { transform: rotate(360deg); }
    }

    .quick-urls {
      display: flex;
      gap: 0.5rem;
      flex-wrap: wrap;
      margin-bottom: 1rem;
    }

    .quick-url {
      padding: 0.25rem 0.75rem;
      background: #334155;
      border: none;
      border-radius: 4px;
      color: #e2e8f0;
      font-size: 0.75rem;
      cursor: pointer;
      transition: background 0.15s;
    }

    .quick-url:hover {
      background: #475569;
    }

    .presets {
      display: flex;
      gap: 0.5rem;
      flex-wrap: wrap;
      margin-bottom: 1rem;
    }

    .preset-btn {
      padding: 0.25rem 0.75rem;
      background: #334155;
      border: 1px solid #475569;
      border-radius: 4px;
      color: #e2e8f0;
      font-size: 0.75rem;
      cursor: pointer;
      transition: all 0.15s;
    }

    .preset-btn:hover {
      background: #475569;
    }

    .preset-btn.active {
      background: #38bdf8;
      border-color: #38bdf8;
      color: #0f172a;
    }
  </style>
</head>
<body>
  <h1>Edge Proxy Relay Test</h1>
  <p class="subtitle">Test your proxy with various endpoints and configurations</p>

  <div class="panel">
    <h2>Target Configuration</h2>

    <div class="form-row">
      <div class="form-group">
        <label>Target URL</label>
        <input type="text" id="targetUrl" value="https://httpbin.org/anything" />
      </div>
    </div>

    <div class="quick-urls">
      <span style="color: #64748b; font-size: 0.75rem;">Quick URLs:</span>
      <button class="quick-url" onclick="setUrl('https://httpbin.org/anything')">httpbin /anything</button>
      <button class="quick-url" onclick="setUrl('https://httpbin.org/get')">httpbin /get</button>
      <button class="quick-url" onclick="setUrl('https://httpbin.org/post')">httpbin /post</button>
      <button class="quick-url" onclick="setUrl('https://httpbin.org/headers')">httpbin /headers</button>
      <button class="quick-url" onclick="setUrl('https://httpbin.org/uuid')">httpbin /uuid</button>
      <button class="quick-url" onclick="setUrl('https://jsonplaceholder.typicode.com/posts/1')">JSONPlaceholder</button>
    </div>
  </div>

  <div class="panel">
    <h2>Request Method</h2>
    <div class="method-buttons">
      <button class="method-btn get active" data-method="GET" onclick="setMethod('GET')">GET</button>
      <button class="method-btn post" data-method="POST" onclick="setMethod('POST')">POST</button>
      <button class="method-btn put" data-method="PUT" onclick="setMethod('PUT')">PUT</button>
      <button class="method-btn delete" data-method="DELETE" onclick="setMethod('DELETE')">DELETE</button>
      <button class="method-btn" data-method="PATCH" onclick="setMethod('PATCH')">PATCH</button>
      <button class="method-btn" data-method="HEAD" onclick="setMethod('HEAD')">HEAD</button>
      <button class="method-btn" data-method="OPTIONS" onclick="setMethod('OPTIONS')">OPTIONS</button>
    </div>
  </div>

  <div class="panel">
    <h2>Request Body (JSON)</h2>
    <div class="form-row">
      <div class="form-group" style="flex: 2;">
        <textarea id="requestBody" placeholder='{"key": "value"}'>{
  "message": "Hello from Edge Proxy",
  "timestamp": 1234567890,
  "nested": {
    "array": [1, 2, 3]
  }
}</textarea>
      </div>
      <div class="form-group" style="flex: 1;">
        <label>Presets</label>
        <div class="presets">
          <button type="button" class="preset-btn" onclick="setBody('empty')">Empty</button>
          <button type="button" class="preset-btn" onclick="setBody('simple')">Simple</button>
          <button type="button" class="preset-btn" onclick="setBody('form')">Form Data</button>
          <button type="button" class="preset-btn" onclick="setBody('large')">Large JSON</button>
        </div>
        <label>Format</label>
        <button type="button" class="preset-btn" onclick="formatJson()">Format JSON</button>
      </div>
    </div>
  </div>

  <div class="panel">
    <h2>Custom Headers</h2>
    <div class="form-row">
      <div class="form-group">
        <label>Header Key</label>
        <input type="text" id="headerKey" placeholder="x-custom-header" />
      </div>
      <div class="form-group">
        <label>Header Value</label>
        <input type="text" id="headerValue" placeholder="custom-value" />
      </div>
    </div>
  </div>

  <button class="send-btn" id="sendBtn" onclick="sendRequest()">
    Send Request
  </button>

  <div class="panel" style="margin-top: 2rem;">
    <h2>Response</h2>
    <div class="response-meta" id="responseMeta">
      <div class="meta-badge">
        <span class="label">Status:</span>
        <span class="value" id="statusBadge">-</span>
      </div>
      <div class="meta-badge">
        <span class="label">Time:</span>
        <span class="value" id="timeBadge">-</span>
      </div>
      <div class="meta-badge">
        <span class="label">Size:</span>
        <span class="value" id="sizeBadge">-</span>
      </div>
    </div>
    <div class="response-section">
      <div>
        <label style="color: #64748b; font-size: 0.75rem; margin-bottom: 0.5rem; display: block;">Response Body</label>
        <pre id="responseBody">Response will appear here...</pre>
      </div>
      <div>
        <label style="color: #64748b; font-size: 0.75rem; margin-bottom: 0.5rem; display: block;">Response Headers</label>
        <pre id="responseHeaders" class="headers-list">Response headers will appear here...</pre>
      </div>
    </div>
  </div>

  <script>
    let currentMethod = 'GET';

    function setMethod(method) {
      currentMethod = method;
      document.querySelectorAll('.method-btn').forEach(btn => btn.classList.remove('active'));
      document.querySelectorAll('.method-btn').forEach(btn => {
        if (btn.dataset.method === method) btn.classList.add('active');
      });
    }

    function setUrl(url) {
      document.getElementById('targetUrl').value = url;
    }

    function setBody(type) {
      const textarea = document.getElementById('requestBody');
      switch (type) {
        case 'empty':
          textarea.value = '';
          break;
        case 'simple':
          textarea.value = JSON.stringify({ hello: 'world' }, null, 2);
          break;
        case 'form':
          textarea.value = JSON.stringify({
            name: 'John Doe',
            email: 'john@example.com',
            message: 'Hello!'
          }, null, 2);
          break;
        case 'large':
          textarea.value = JSON.stringify({
            users: Array.from({ length: 100 }, (_, i) => ({
              id: i + 1,
              name: `User ${i + 1}`,
              email: `user${i + 1}@example.com`,
              active: i % 2 === 0
            }))
          }, null, 2);
          break;
      }
    }

    function formatJson() {
      const textarea = document.getElementById('requestBody');
      try {
        const parsed = JSON.parse(textarea.value);
        textarea.value = JSON.stringify(parsed, null, 2);
      } catch (e) {
        alert('Invalid JSON: ' + e.message);
      }
    }

    function getStatusClass(status) {
      if (status >= 200 && status < 300) return 'status-2xx';
      if (status >= 300 && status < 400) return 'status-3xx';
      if (status >= 400 && status < 500) return 'status-4xx';
      return 'status-5xx';
    }

    async function sendRequest() {
      const btn = document.getElementById('sendBtn');
      const targetUrl = document.getElementById('targetUrl').value;
      const requestBody = document.getElementById('requestBody').value;
      const headerKey = document.getElementById('headerKey').value;
      const headerValue = document.getElementById('headerValue').value;

      // Update UI
      btn.disabled = true;
      btn.innerHTML = '<span class="spinner"></span> Sending...';
      document.getElementById('responseBody').textContent = 'Sending request...';
      document.getElementById('responseHeaders').textContent = '-';
      document.getElementById('statusBadge').textContent = '-';
      document.getElementById('timeBadge').textContent = '-';
      document.getElementById('sizeBadge').textContent = '-';

      const headers = {
        'x-relay-target': targetUrl,
        'Content-Type': 'application/json'
      };

      if (headerKey && headerValue) {
        headers[headerKey] = headerValue;
      }

      try {
        const start = Date.now();
        const fetchOptions = {
          method: currentMethod,
          headers
        };

        if (!['GET', 'HEAD', 'OPTIONS'].includes(currentMethod) && requestBody) {
          fetchOptions.body = requestBody;
        }

        const res = await fetch('/api', fetchOptions);
        const elapsed = Date.now() - start;

        // Parse response
        let responseText = '';
        let responseData = null;
        const contentType = res.headers.get('content-type') || '';

        if (contentType.includes('application/json')) {
          responseData = await res.json();
          responseText = JSON.stringify(responseData, null, 2);
        } else {
          responseText = await res.text();
        }

        // Get response headers
        const headersObj = {};
        res.headers.forEach((value, key) => {
          headersObj[key] = value;
        });

        // Update UI
        const statusEl = document.getElementById('statusBadge');
        statusEl.textContent = `${res.status} ${res.statusText}`;
        statusEl.className = `value ${getStatusClass(res.status)}`;

        document.getElementById('timeBadge').textContent = `${elapsed}ms`;
        document.getElementById('sizeBadge').textContent = formatBytes(new Blob([responseText]).size);
        document.getElementById('responseBody').textContent = responseText;
        document.getElementById('responseHeaders').textContent = JSON.stringify(headersObj, null, 2);

      } catch (err) {
        document.getElementById('responseBody').innerHTML = `<span class="error-text">Error: ${err.message}</span>`;
        document.getElementById('statusBadge').textContent = 'Error';
        document.getElementById('statusBadge').className = 'value status-5xx';
      } finally {
        btn.disabled = false;
        btn.textContent = 'Send Request';
      }
    }

    function formatBytes(bytes) {
      if (bytes === 0) return '0 B';
      const k = 1024;
      const sizes = ['B', 'KB', 'MB', 'GB'];
      const i = Math.floor(Math.log(bytes) / Math.log(k));
      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
    }
  </script>
</body>
</html>书xw 100644 index.test.ts  po.ῗ:ș V+5100644 index.ts ,-|?ҹ^r%:du'100644 relay-utils.ts Uw>%]eΎ1Mw-xmBgBZQ~R
"^r'G\ X
.xJ\	isAllowedTargetZG// Only allow safe methods
const ALLOWED_METHODS = new Set(["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"]);

expoFT// Method validation
	if (!ALLOWED_METHODS.has(req.method)) {
		return new Response()ethod not allowed" }), {
			status: 405,
6});
	}
nG// Target validation (SSRF prevention)
	if (!isAllowedTarget(targetUrl)73Target domain not allowed" }),
			{
				status: 403ʆlCx aͻM$WQxeTϋEf,Ӭ%{0!3r=F	ew]BM뙢+ǓABDE0?͛;&^{{_\ype23TL
h,hu>,ݲ=tpx\\A'yJOr(\h'ZE}GI4:p˨43&}!6 tG9r	 ̥9qtDݒ9ЪPaTrWR@#ӟqg/K]\-tommVdPJI>r@"ˉ֡QLRnM8t쏺u0K].,aiJ;@nYj!`ѾE7nNje]2stz xe?8b ujny慖A6yyG#]m{T0ں,e#q$GS[?]K'ѱ"zG$|`H3󘊵	$96M ࿻_]U'c+Dv_\&ZGΰE^6KpZ
Js>ׇ~	hrW
,.F:![Vlckwg;-խړt"@!9Ӝr8*`Tjǝ=֪eSi:r
S2U)J 6)Հy)dZa~4fKS	BÔD%\Zǻ)<fW*N/afái^%<b'M`$xB{
	"compilerOptions": {
		"lib": ["ESNext"],
		"target": "ESNext",
		"module": "Preserve",
		"moduleDetection": "force",
		"jsx": "react-jsx",
		"allowJs": true,
		"types": ["bun"],

		// Bundler mode
		"moduleResolution": "bundler",
		"allowImportingTsExtensions": true,
		"verbatimModuleSyntax": true,
		"noEmit": true,

		// Best practices
		"strict": true,
		"skipLibCheck": true,
		"noFallthroughCasesInSwitch": true,
		"noUncheckedIndexedAccess": true,
		"noImplicitOverride": true,

		// Path aliases
		"paths": {
			"~/*": ["./src/*"]
		}
	},
	"include": ["src/**/*"]
}
yx:{
	"$schema": "https://openapi.vercel.sh/vercel.json",
	"bunVersion": "1",
	"installCommand": "bun install",
	"outputDirectory": "dist",
	"trailingSlash": false,
	"rewrites": [
		{
			"source": "/api",
			"destination": "/api/index.ts"
		},
		{
			"source": "/api/(.*)",
			"destination": "/api/index.ts"
		}
	]
}
؟\} x+ww@f:O);;8P7i!3i!2 5i! 6i! ci!V((`.method-btn.${method.toLowerCase()}`)?)D ||
        document.querySelector(`.method-btn:not([class*=" "])`)?) // Find the exact buttonC(?{
        if (btn.textContent)NbmxqB%ʓ'jx
 
}
efx4 N:lՊE@)f⿽Yۤx$ 100644 index.ts e͙i7#x+E[xh 4B۰VbgO'Ӻa*hHow^V=l֤« 7-L^R*aI'ZU!$E9M%)+M-oxxn 4sR)0Co,^,A;{Ho40000 api N:lR\^ޜ;m6(SE@)f⿽Y3+ktx[};dv fԴx]
0@+Ipu?t1$%&])N&4<zօzc.A8-G^X(]wgg
ϸ2?FkEzBl
㕹ZR=AӁڥxD: kx mMo
}8xr 4sR)0Co,^,A;{Ho40000 api jXZf
ّ\<vJ,!+(w'a#SLLĲ1h2,x$ 100644 index.ts 9;IK\0Z .%x}Vmo6_q>FmN_MC$n}EJIEɖڴ_b=sSVr[ r#ts*%Df"r%Ic U2GNmS6m6[NԤD<uP[6wtASLzsgalW ;Ekx#M3RpQ'P(LIRP>k͊%HuU%iɜ\MҸg7/j>G|6o*5û!l)MjByeX%әP)RYաFB!l8_2]gv]sYVvZ%LcDZ6{.χ2)`B	^ԸpMSnf3> wTҕơc襯}ntNw[IVoߕ딦{+ͅ|PH4vBRᣁ'	p8\9_'W^S[,:)"*ȭ-Mwe>&9.R`8`xǿ71ZyLO$}G`[h7F#3%( ۑU	b:6|rѝ<QayDj^ZOZU2yscn@ʧҪN0HGQr[U"Ww~D{$s]RCFO89JIuveTxfu4q8\6wp'L`v-=m5KZy'C6mBRG*(_%Xz=#!s@IQ&e|Fztz6#j]i(S/w"˻[7]`M@XAAKzqNHsz޹JT>BٳGk$xW_aJ߁Bw_Sx api2jVxƼy¢:x  ,y]p˗(v՘J
/x[ t".vercelignore (u6h/}j>?B:Em9#SLLĲ1hdhzѢ`ҫ*ix gU!$E9M%)+LexV dist
src
.gitignore
CLAUDE.md
README.md
bun.lock
biome.json
tsconfig.json
package.jsongxE{
	"$schema": "https://openapi.vercel.sh/vercel.json",
	"bunVersion": "1",
<<<<<<< HEAD
	"installCommand": "bun install"
=======
	"rewrites": [{ "source": "/(.*)", "destination": "/api/relay" }]
>>>>>>> parent of cfbc460 (feat: update build output directory to 'dist' in package.json and set outputDirectory in vercel.json)
}rhocxƼy|⒍5xJLx9 ,y]p˗(v՘J4["VJo$̓TP1~xw 100644 index.test.ts  po.ῗ:ș V+5100644 index.ts j(ts.<IR->100644 relay-utils.ts Uw>%]eΎ1MS,Lxre E P((MLfY/nWMO8 V=x5 }>QKWe.*%y&	Ck\GCx  L'ND5J0E 4Ӧx& 100644 index.html <vJ,!+(w>xg{
	"$schema": "https://openapi.vercel.sh/vercel.json",
	"bunVersion": "1",
	"installCommand": "bun install",
	"rewrites": [
		{ "source": "/api", "destination": "/api/index.ts" },
		{ "source": "/api/(.*)", "destination": "/api/index.ts" },
		{ "source": "/public/(.*)", "destination": "/public/$1" },
		{ "source": "/(.*)", "destination": "/public/$1" }
	]
}Ek7x& 100644 index.html '2#HK.Μp^Ax7 y*`1SZ"bsի?s1c9e.}!xN 100644 index.ts :Hf%K%j@;%#100644 relay-utils.ts Uw>%]eΎ1MY1~lx v.wxƼy
sqQD
⒍aJfx7 G=%CF?^vի?s1c9e.}!4Fx4 y*`1SZ"bsddxɅ 95n}'x;tq^'G.NҼb%+j.NNĂL}--}-RQi^Ifn*TZ Rx;ϸqRf^qIbNs~nnb^RRiTTib# MQ.xN 100644 index.ts j(ts.<IR->100644 relay-utils.ts Uw>%]eΎ1MhxN 5dfQK:	Ho}>QKWe.*%y&8/޽`jflm[U7\]$t$xɼy-gbA&^IzFbF Gz~x "c<'2>!N#Op{[8lx;8cW- Wx! MoutputDirectory": "distHFͭnx5 bbz=w#j<<2_Z*`K#i"Dx5 b?s1c9e.}!v"c<'2>!N#Op{[3xƼy
sqQDꍷ=a{x7 ի1iBydRXę \PW|SY^Q̒eFx +$}rc&Lm"*x7 5dfQK:	Hbz=w#j<<2_Zvww`xi
sqQDuE@iFx;X=ѧ 2gxccQSc+c޽~ xE { "rewrites": [{ "source": "/(.*)", "destination": "/api/relay" }] }
{xccٰQd)[rg8^LП1  Ϯxw 100644 index.test.ts  po.ῗ:ș V+5100644 index.ts *4^谺X ,ďj[100644 relay-utils.ts X\Jei:+e0@
x[,}}%Vɂ&q'(e(*䥖+x&kd@hMk.N
ԜԒT
ݢԜJݒĢ%j
K2/h.J-)-ʃdnf& ~<x|100644 .gitignore G	OrCK100644 CLAUDE.md vLٝi~~r(	100644 README.md _WS?eO:100644 bun.lock ^YT?yVՈ^100644 index.test.ts T,=\U3f7U%;100644 index.ts ra*P[̵p3q;100644 package.json  wr"Iޕw-Q100644 relay-utils.ts -L^R*aI'100644 tsconfig.json I}rR%100644 vercel.json A
߅mGM"J]x* 340000 src o(l7hAa<x/3P
gx¼ 
uMxq;ɢ2e8ju@`r`	J%E%JV
Jq#6whe}d<)fS=Ɋy~Y9eQU.y>E%^%afn~喁YeN)Ui..&iEF&eށ)n!&I9IA>%f!-`SP 8x 8{
  "name": "proxy-vercel",
  "module": "index.ts",
  "type": "module",
  "private": true,
  "devDependencies": {
    "@types/bun": "latest"
  },
  "peerDependencies": {
    "typescript": "^5"
  }
}
%:-x {
  "compilerOptions": {
    // Environment setup & latest features
    "lib": ["ESNext"],
    "target": "ESNext",
    "module": "Preserve",
    "moduleDetection": "force",
    "jsx": "react-jsx",
    "allowJs": true,
    "types": ["bun"],

    // Bundler mode
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "verbatimModuleSyntax": true,
    "noEmit": true,

    // Best practices
    "strict": true,
    "skipLibCheck": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true,

    // Some stricter flags (disabled by default)
    "noUnusedLocals": false,
    "noUnusedParameters": false,
    "noPropertyAccessFromIndexSignature": false
  }
}
v@Px< {"rewrites":[{"source":"/(.*)","destination":"/api/relay"}]}b&G pk%}qQ;&