Communitygithub.com

브락스턴로즈4/모든-웹사이트-클론

라이브 웹사이트를 역설계하여 픽셀 단위로 완벽하게 복제합니다. 단일 랜딩 페이지 또는 전체 사이트를 클로닝할 수 있습니다. DOM, CSS 디자인 토큰, 에셋, 폰트, 컴파일된 JS 설정 데이터를 캡처하고, 스크롤 애니메이션과 상태 기반 상호작용을 역설계한 후, 점수가 매겨진 벤치마크로 정확도를 증명합니다.

모든-웹사이트-클론란 무엇인가요?

이 스킬은 사용자가 제공한 URL을 기반으로 라이브 웹사이트를 1:1로 복제하는 전문 에이전트 도구입니다. 단순히 스크린샷을 보고 추측하는 방식이 아니라, 실제 실행 중인 페이지에서 DOM, CSS 토큰, 키프레임, 폰트, 설정 데이터를 추출하여 객관적인 사실(fact)을 기반으로 재구성합니다. 정적 내보내기 사이트(Webflow, Framer 등)는 원시 HTML 미러로, JS 프레임워크 기반 사이트(Next.js, Remix 등)는 컴포넌트 단위로 재구성하며, 원본과 클론에서 동일한 측정 프로브(probe)를 실행하여 점수 차이가 수렴할 때까지 반복 보정합니다. 이는 Claude Code, Cursor, Codex 등 다양한 AI 에이전트 플랫폼에서 '이 웹사이트 복제', '랜딩 페이지 역설계', '픽셀 퍼펙트 재구축' 등의 요청에 응답할 때 사용됩니다. 스킬의 핵심은 정확도 측정과 반복 보정 루프에 있으며, 단순 브라우저 자동화나 스크린샷 캡처 작업에는 적합하지 않습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add braxtonROSE4/clone-any-website

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

clone-website

라이브 웹사이트를 URL만으로 1:1 (레이아웃, 타이포그래피, 에셋, 스크롤 애니메이션, 상태 기반 상호작용) 복제하고, 주장 대신 점수가 매겨진 벤치마크로 정확도를 증명합니다.

런타임 의존성 및 부트스트랩

모든 라이브 페이지 작업은 ego-browser nodejs <<'EOF' ... EOF Bash 명령어로 실행되며, ego 라이트 런타임의 사전 로드된 page, browser, taskSpaces 퍼사드를 사용합니다.

런타임은 대체 불가능합니다. 그 이유는 메서드가 런타임에 세 가지 방식으로 의존하기 때문입니다. 픽셀 수준의 루프는 원본과 클론에서 동일한 프로브가 동일한 런타임에서 실행되어야 하며, 그렇지 않으면 diff 숫자가 의미가 없습니다. 보정 루프는 작업 공간(task spaces)이 필요합니다. 에이전트는 자체 격리된 공간에서 프로브하는 동안 사용자는 자신의 공간에서 원본과 클론을 동시에 탐색하고, 시각적 확인을 위해 공간을 넘겨받았다가 다시 반환할 수 있으며, 양쪽이 브라우저를 두고 경쟁하지 않습니다. 또한 이 스킬의 모든 프로브 스크립트와 주의 사항은 이 런타임의 측정된 동작에 맞춰 보정되어 있습니다. 이것이 런타임이 누락된 경우 대체 경로가 아닌 ego 라이트 설치로 안내하는 이유입니다.

  1. ego-browser 스킬이 이 환경에 설치되어 있으면, 0단계 전에 로드하고 모든 정확성 규칙을 따르십시오. 이 스킬은 ego-browser 스킬의 래퍼이며, 충돌 시 브라우저 메커니즘에 대해서는 ego-browser 스킬의 규칙이, 클로닝 워크플로우에 대해서는 이 스킬의 규칙이 우선합니다.
  2. 이 스킬만 있는 경우, 단독으로 작동합니다. 계약은 ego-browser CLI(ego 라이트 앱에서 제공)입니다. 새 환경이나 알 수 없는 환경에서는 0단계 전에 command -v ego-browser로 한 번 확인하십시오.
  3. CLI가 누락된 경우, 즉흥적으로 처리하지 마십시오. 중단하고 references/install.md를 읽은 후 사용자에게 ego 라이트 설치를 안내하십시오(scripts/install-ego-browser.sh를 통해 다운로드한 후 사용자가 앱 GUI에서 온보딩을 완료하여 CLI를 등록하고, 사용자의 확인을 기다린 후 command -v ego-browser로 확인한 다음 재개). Playwright, 다른 브라우저, WebFetch 또는 다른 자동화 경로로 대체하지 마십시오. 다른 런타임의 프로브 결과는 비교할 수 없으며 스킬의 환경 주의 사항이 더 이상 유효하지 않습니다.
  4. 첫 번째 명령이 성공한 후에는 런타임이 세션의 나머지 부분에 대해 준비된 것으로 가정하고, 실제 명령이 오류를 발생시킬 때만 조사하십시오.

한 문장으로 요약하면, 라이브 사이트를 눈으로 추정하는 그림이 아니라 쿼리할 수 있는 절대 진실(ground truth)로 취급하십시오. 실행 중인 페이지에서 실제 DOM, 실제 CSS 토큰, 실제 키프레임, 실제 폰트, 실제 설정 데이터를 추출하고, 이러한 사실을 기반으로 재구축한 다음, 원본과 클론에서 동일한 측정 프로브를 실행하고 숫자가 수렴할 때까지 diff를 계산합니다.

파일 맵: references/probes.md에 프로브 스크립트 복사; references/eval.md에 벤치마크 프로토콜; references/install.md에 ego 라이트 설치 가이드 (설치 프로그램은 scripts/install-ego-browser.sh에 포함); references/tool-design-template.md에 도구/함수 디자인 템플릿; tools/에 표준 도구 구현 (tools/manifest.json에 등록); scripts/eval_score.py에 채점기; scripts/slice.py에 스크린샷 슬라이서.

실행 모델

  • 전체 작업에 대해 하나의 작업 공간(task space). 모든 히어독(heredoc) 상단에 taskSpaces.useOrCreate('reverse <site>')를 사용하고, 모든 호출에서 동일한 이름을 사용합니다. 프로브 사이에 complete하지 마십시오. 최종 승인 후에만 { keep: false }와 함께 complete하십시오. 사용자가 공간을 넘겨받으면 하드 스톱입니다: 상태를 보고하고 기다리며, 스스로 takeOver하지 마십시오.
  • 작업(action)이 아닌 단계(phase)별로 배치하십시오. 하나의 히어독은 전체 프로브(열기, 동영상 중지, 모든 깊이에서 측정, 출력)를 완료해야 합니다. 호출은 실제 경계(셸 작업(컬 다운로드, grep, Python)과 브라우저 작업 사이, 사용자 확인, 저장된 이미지의 시각적 검사)에서만 분할하십시오.
  • 아티팩트는 파일로, 요약은 표준 출력으로. 모든 프로브는 전체 JSON/HTML을 reference/에 쓰고, console.log에는 개수와 경로만 출력합니다. 대화에 전체 DOM, CSS 파일 또는 지오메트리 테이블을 덤프하지 마십시오.
  • 모든 프로브 스크립트는 자체 포함되어 있습니다. 작업 공간 선택과 browser.openOrReuseTab(url, { wait: true, timeout: 30000 })은 매번 상단에 위치합니다. 런타임 상태는 히어독 간에 유지되지 않습니다.
  • 히어독 내부에서는 절대 경로만 사용하십시오. ego의 nodejs 프로세스는 셸의 현재 작업 디렉토리(cwd)를 상속받지 않으며, 상대 fs.writeFileSync 경로는 ENOENT 오류를 발생시킵니다. 모든 스크립트 상단에 ROOT 상수를 정의하십시오.

빠른 시작: 정찰(recon) 부트스트랩

한 번의 호출로 재구축의 시작점이 되는 절대 진실을 캡처합니다. URL과 경로를 조정하십시오. 합성 구조가 핵심입니다.

mkdir -p reference/{dom,css,js,screenshots} && ego-browser nodejs <<'EOF'
const fs = await import('node:fs')
const ROOT = '/abs/path/to/project'  // ego의 nodejs는 셸 cwd를 상속하지 않음
await taskSpaces.useOrCreate('reverse example.com')
await browser.openOrReuseTab('https://example.com', { wait: true, timeout: 30000 })

// 자동 재생 동영상을 먼저 중지: 렌더 스레드를 포화시켜 이후 평가가 타임아웃됩니다.
await page.evaluate(() => {
  const freeze = () => document.querySelectorAll('video').forEach(v => v.pause())
  freeze(); setInterval(freeze, 400)
})

// 뷰포트 보호: w/h가 0이면 자동으로 빈 스크린샷과 잘못된 rect를 생성합니다.
const info = await page.info()
if (!info.w || !info.h) throw new Error('뷰포트가 0입니다. 정찰 전에 실제 탭을 복원하십시오: ' + JSON.stringify(info))

const facts = await page.evaluate(() => ({
  html: document.documentElement.outerHTML,
  outline: Array.from(document.querySelectorAll('body section, body header, body footer, body main > div'))
    .map(el => { const r = el.getBoundingClientRect(); return {
      tag: el.tagName, cls: (el.className?.toString() || '').slice(0, 120),
      y: Math.round(r.top + scrollY), h: Math.round(r.height), w: Math.round(r.width) } })
    .filter(s => s.h > 40).sort((a, b) => a.y - b.y),
  cssUrls: performance.getEntriesByType('resource').map(e => e.name).filter(u => /\.css(\?|$)/.test(u)),
  fontFiles: performance.getEntriesByType('resource').map(e => e.name).filter(u => /\.(woff2?|ttf|otf)(\?|$)/.test(u)),
  jsChunks: performance.getEntriesByType('resource').map(e => e.name).filter(u => /\.js(\?|$)/.test(u)),
}))
fs.writeFileSync(ROOT + '/reference/dom/full-page.html', facts.html)
fs.writeFileSync(ROOT + '/reference/dom/outline.json', JSON.stringify(facts.outline, null, 2))
fs.writeFileSync(ROOT + '/reference/urls.json', JSON.stringify({ css: facts.cssUrls, fonts: facts.fontFiles, js: facts.jsChunks }, null, 2))

await page.evaluate(() => scrollTo(0, 0))
await page.waitForTimeout(800)
await page.screenshot({ path: ROOT + '/reference/screenshots/original-full.png', fullPage: true })

console.log(JSON.stringify({ sections: facts.outline.length, cssFiles: facts.cssUrls.length,
  fontFiles: facts.fontFiles.length, jsChunks: facts.jsChunks.length, htmlKB: Math.round(facts.html.length / 1024) }))
EOF

그런 다음 셸 작업: 모든 CSS/폰트/JS URL을 reference/로 curl 다운로드하고, python3 scripts/slice.py reference/screenshots/original-full.png reference/dom/outline.json reference/screenshots/original/로 섹션별로 스크린샷을 슬라이스합니다.

워크플로우

0단계, 설정

정찰 아티팩트용 reference/{dom,css,js,screenshots}/ 디렉토리를 가진 프로젝트 디렉토리 (절대 배포하지 않으며, 모든 저장소 푸시에서 제외); 사용자가 지속적으로 검토할 수 있도록 백그라운드에서 고정 포트의 개발 서버. public/에 이미 다운로드된 에셋이 있는 경우 수동으로 Next.js를 스캐폴딩합니다. create-next-app은 디렉토리를 덮어씁니다.

서빙 전에 lsof -iTCP:<port> -sTCP:LISTEN으로 확인합니다 (이전 클론 작업의 남은 서버가 사용자 포트에서 그럴듯하게 응답할 수 있음). 클론의 모든 프로브 전에 document.title이 일치하는지 확인합니다. 그럴듯한 curl 응답이 브라우저 탭에 페이지가 표시된다는 증거는 아닙니다.

1단계, 정적 캡처

정찰 부트스트랩을 실행한 후 다음을 채웁니다 (전체 스크립트는 references/probes.md에 있음):

  • 디자인 토큰: 다운로드된 CSS에서 --brand-*, --font-size-*, --color-*, 간격 스케일을 grep합니다. 토큰에서 크기와 색상을 읽고 스크린샷에서 절대 추정하지 마십시오. 예상되지만 찾을 수 없는 토큰은 일반적으로 다른 CSS 파일에 있으므로 존재하지 않는다고 결론 내리기 전에 모든 파일을 grep하십시오.
  • 폰트: @font-face는 종종 JS에 의해 런타임에 주입되므로 저장된 HTML을 grep하는 것은 아무 의미가 없습니다. 라이브 페이지에서 세 가지 소스를 결합하십시오: 폰트 확장자로 필터링된 performance.getEntriesByType('resource'), family/weight/status에 대한 document.fonts, family를 weight 및 src에 매핑하기 위한 CSSFontFaceRule에 대한 document.styleSheets 탐색. curl --globoff로 다운로드합니다 (대괄호가 있는 파일 이름은 curl globbing을 깨뜨림). public/fonts/에 자체 호스팅합니다.
  • 에셋: img[src], srcset, video src, background-image URL, 인라인 SVG를 열거합니다. /_next/image?url=...를 원래 경로로 디코딩합니다. public/ 아래에 원래 경로를 미러링합니다. 인라인 SVG는 그대로 추출하고 다시 그리지 마십시오.
  • 모형/제품-UI 콘텐츠: 대시보드, 편집기, 카드 패널은 컴파일된 JS 청크의 설정 객체에서 콘텐츠를 가져옵니다. 다운로드된 청크에서 보이는 문자열을 grep합니다. 히트 옆의 설정 객체는 완전한 데이터 세트(모든 캐러셀 변형, 모든 행)를 보유하며, 지연 로드된 상태를 놓친 스크린샷보다 훨씬 안정적입니다.

참조 스크린샷은 아래의 정확성 규칙을 따릅니다: 스크롤 0에서 하나의 전체 페이지 캡처를 scripts/slice.py로 섹션별로 슬라이스하고, 동영상을 먼저 중지하고, 상태 표시줄을 먼저 재설정합니다.

2단계, 모션 및 상호작용 역설계

가장 저렴한 것부터 먼저 작업하고, 효과가 설명되는 즉시 중지합니다:

  1. CSS 정적 분석 (무료, 이미 다운로드됨): animation-timeline|view-timeline|scroll-timeline|@keyframes를 grep합니다. animation-timeline: view()의 히트는 기본 CSS 스크롤 기반 애니메이션을 의미합니다: 키프레임과 범위를 그대로 복사하고 JS는 필요하지 않습니다.
  2. 런타임 인벤토리: document.getAnimations({ subtree: true }), 절대 진실. 애니메이션당: effect.getKeyframes(), timeline.constructor.name (ViewTimeline은 스크롤 기반, DocumentTimeline은 시간 기반), rangeStart/rangeEnd, 대상 요소. --parallax-from: 6%와 같은 요소별 매개변수는 인라인 스타일에 있습니다.
  3. JS 트리거 효과에 대한 MutationObserver 및 실제 휠 스크롤: attributeFilter: ['class','style','src'], page.mouse.wheel로 전체 페이지를 스크롤하고, 변이를 기록합니다. 전체 스크롤에 걸쳐 클래스 변이가 없으면 효과가 순수 CSS임을 증명하므로 재구축이 크게 단순화됩니다.
  4. 고정/스티키 상태 머신에 대한 다중 깊이 상태 샘플링 (부동 입력 바, 축소되는 헤더): 요소의 전체 하위 트리(모든 하위 요소의 rect, 불투명도, 반경, 배경, 그림자)를 여러 스크롤 깊이에서 양방향으로 스냅샷한 다음, 상태 간의 스냅샷을 diff합니다. 위치 전용 검사는 형태 변화를 놓칩니다. 상태 전환은 상호작용입니다.

3단계, 재구축

먼저 사이트 유형별로 재구축 경로를 선택하십시오. 정적 내보내기 사이트 (Webflow, Framer: data-wf-site 속성, website-files.com/framerusercontent.com CDN)는 컴포넌트 재구축이 아닌 원시 HTML 미러로 가장 높은 정확도로 클론합니다: 사전 수화(pre-hydration) 서버 HTML을 골격으로 curl 다운로드합니다 (캡처된 라이브 DOM은 이미 수화되어 있음 — 캐러셀 클론이 삽입되고, SplitText 문자가 분할됨 — 스크립트가 다시 실행되면 두 번 초기화됨), 모든 CDN URL을 자체 호스팅 복사본으로 다시 쓰고, 분석/동의 스크립트를 제거하고, 기능적 런타임(사이트 JS, GSAP, 캐러셀 라이브러리)을 자체 호스팅합니다. 해당 경로에 대한 네 가지 규칙:

  • 에셋은 여러 CDN 폴더(공유 미디어 라이브러리)에 걸쳐 있을 수 있습니다. 사이트의 기본 폴더 접두사가 아닌 기본 파일 이름으로 cdn-host/<folder>/<file>을 다시 쓰십시오.
  • 다시 쓴 모든 리소스 태그에서 integrity/crossorigin을 제거하십시오. SRI 해시는 원본 바이트에 대해 계산되었으며, 브라우저는 수정된 파일을 자동으로 거부합니다. 증상은 프로브에서 볼 수 있는 오류 없이 스타일이 지정되지 않은 페이지(문서 높이 폭발, document.fonts 비어 있음)입니다.
  • 키워드 차단 목록에 의한 추적 스크립트 제거는 우아한 폴백이 있는 분석 API만 언급하는 기능적 스크립트를 죽일 수 있습니다 (다운로드 버튼의 OS 감지 텍스트 교체를 소유한 헤드 스크립트가 PostHog을 참조함). 삭제된 스크립트 목록을 감사하고 기능적 스크립트를 허용 목록에 추가하십시오.
  • 비결정적 실험 스크립트 (Math.random() 트래픽 분할, 리디렉션 카나리아)는 기능적이더라도 제거된 상태로 유지됩니다. 벤치마크 실행을 반복 불가능하게 만들기 때문입니다.

JS 프레임워크 사이트 (Next.js, Remix — 수화 불일치로 인해 미러가 불가능)의 경우 컴포넌트별로 재구축합니다:

  • 최상위 섹션당 하나의 컴포넌트, 섹션의 비즈니스 콘텐츠 이름을 따서 명명합니다.
  • 1단계의 토큰은 그대로 globals.css에 넣고, 폰트는 실제 가중치 매핑과 함께 자체 호스팅합니다.
  • 보이는 것뿐만 아니라 원본의 DOM 마운팅 전략을 미러링합니다: 원본은 모든 캐러셀 프레임, 로테이션 변형, 아바타 벽을 마운트된 상태(숨김)로 유지하는 반면, 조건부 렌더링 클론은 요소의 일부만으로 동일한 픽셀을 표시한 다음 벤치마크의 섹션별 미디어 수에서 실패합니다. 원본의 DOM에 대한 섹션별 요소 수를 스크린샷이 아니라 일치시킵니다.
  • 시각적 요소뿐만 아니라 원본의 컨테이너 계층 구조를 복제하십시오. sticky는 부모로 범위가 지정됩니다: 푸터까지 살아남아야 하는 탐색에는 페이지 전체에 걸치는 부모가 필요합니다. 커튼 공개(curtain-reveal) 패널에는 효과가 시작되는 위치에서 시작하는 부모가 필요합니다. 그렇지 않으면 첫 화면부터 고정됩니다. 재구성 후 섹션이 깨지면 먼저 sticky 또는 스태킹 컨텍스트 범위 변경을 의심하십시오.
  • 뷰포트 고정 요소 (부동 채팅 바)는 잘 구축된 원본에서 body 수준의 고정 컨테이너입니다. React 포털을 사용하고 섹션 내에서 absolute를 절대 사용하지 마십시오.
  • 문서 높이를 변경하는 축소되는 헤더는 Chrome 스크롤 앵커링과 싸우고 지터를 발생시킵니다. 강력한 아키텍처: 흐름에서 일정한 높이의 플레이스홀더(문서 높이가 절대 변경되지 않음)와 내부 헤더가 완전히 스크롤 아웃되면 fixed로 전환하고 슬라이드인 애니메이션을 추가합니다. 높이가 애니메이션되어야 하는 경우 overflow-anchor: none과 축소 및 확장 임계값 사이의 히스테리시스를 추가합니다.
  • 장식적인 톱니 모양 가장자리(가리비/스탬프 테두리): 이웃과 1px 겹침으로 틈을 향해 뒤집힌 단색 측면 SVG

[이하 생략됨]

관련 스킬