Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: CI

on:
pull_request:
branches:
- main
push:
branches:
- main

jobs:
test:
name: Test
runs-on: ubuntu-latest
permissions:
contents: read

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium

- name: Run unit tests with coverage
run: npm run test:coverage

- name: Run end-to-end tests
run: npm run test:e2e

update-coverage-badge:
name: Update Coverage Badge
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: write

steps:
- name: Check out repository
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Generate coverage report
run: npm run test:coverage

- name: Update README coverage badge
run: npm run update:coverage-badge

- name: Commit updated coverage badge
run: |
if git diff --quiet -- README.md; then
echo "No coverage badge changes to commit"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add README.md
git commit -m "chore(ci): update coverage badge [skip ci]"
git push
47 changes: 15 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# Ghostmaxxing
<!-- coverage-badge:start -->
[![Unit Test Coverage](https://img.shields.io/badge/coverage-73.12%25-yellow)](coverage/)
[![Unit Test Coverage](https://img.shields.io/badge/coverage-66.96%25-yellow)](coverage/)
<!-- coverage-badge:end -->
[![CI](https://github.com/vecna/ghostmaxxing/actions/workflows/ci.yml/badge.svg)](https://github.com/vecna/ghostmaxxing/actions/workflows/ci.yml)

[![Docs](https://img.shields.io/badge/docs-JSDoc-blue)](docs/)
[![Source](https://img.shields.io/badge/source-GitHub-black)](https://github.com/vecna/ghostati)
Expand Down Expand Up @@ -77,11 +78,23 @@ The project is a static web app: there is no production build step required to o
Important local entry points:

- [`index.html`](index.html) — public landing page with links to code, docs, coverage, Ghostyles, project context, and the reporting node.
- [`ghostati.html`](ghostati.html) — main webcam/AR application.
- [`lab.html`](lab.html) — translated main webcam/AR application.
- [`loader.html`](loader.html) — translated internal MP4 loader for repeatable 2D/3D video tests.
- [`ghostati.html`](ghostati.html) — legacy main webcam/AR application entry point.
- [`ghostyles.json`](ghostyles.json) — Ghostyle manifest.
- [`JSDOC_README.md`](JSDOC_README.md) — concise generated-docs overview.
- [`REFERENCES.json`](REFERENCES.json) — curated technical/cultural reference set.

# Localization coverage

Ghostmaxxing currently ships runtime translations for English, Italian, and Portuguese through [`scripts/i18n.js`](scripts/i18n.js). The translated browser pages are:

- [`index.html`](index.html) — homepage and public navigation.
- [`lab.html`](lab.html) — main camera lab, settings, drawers, controls, status labels, and runtime logs.
- [`loader.html`](loader.html) — internal MP4 video loader, language switcher, static interface labels, model status, and loader activity logs.

Other static pages such as [`about.html`](about.html), [`report.html`](report.html), workshop/tutorial pages, generated docs, coverage, and reference pages are still English-baseline unless they add `data-i18n*` bindings and catalog keys.

Runtime external dependencies visible from the HTML/config layer:

- [Google Fonts](https://fonts.googleapis.com) / [Google Fonts static assets](https://fonts.gstatic.com)
Expand Down Expand Up @@ -304,36 +317,6 @@ Current reference links:

```text
small patch
clear test
stable plugin API
documented behavior
```

Good contributions include:

- new Ghostyles with clear metadata and reproducible test notes;
- tighter unit coverage around renamed/refactored functions;
- e2e scenarios for detection, baseline saving, overlay switching, and match-state transitions;
- CDN self-hosting options;
- clearer model-loading failure states;
- accessibility and mobile UI improvements;
- better documentation for `faceapi` vs `mediapipe` Ghostyle engines;
- additions to [`REFERENCES.json`](REFERENCES.json) following [`PROMPT-REFERENCES-UPDATE.txt`](PROMPT-REFERENCES-UPDATE.txt).

Please keep claims narrow and technical: say which model, browser, lighting, camera, and threshold produced which result.









**Last commit:** `85f9100` – internationalization added, three languages






Expand Down
32 changes: 32 additions & 0 deletions about.html
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,25 @@ <h3>A ban that keeps getting reopened.</h3>
</p>
</section>

<section class="content-section" id="internal-tools">
<h2>Internal tools &amp; diagnostics.</h2>
<p>
To support deeper analysis, model testing, and pattern sharing, Ghostmaxxing includes experimental diagnostic tools. These utilities run entirely locally in the browser to examine recorded behavior and transfer designs.
</p>
<h3>Video Loader</h3>
<p>
The <a href="loader.html">Video Loader</a> allows users to import local MP4 video files to run against our on-device 2D and 3D face-detection engines. This allows for precise frame-by-frame analysis, face extraction, database recording, and signature comparison without requiring a live camera feed. It is designed to evaluate recorded workshop outcomes or diagnostic video tracks under stable, repeatable conditions.
</p>
<h3>Ghostyle Transfer</h3>
<p>
The <a href="ghostyle-transfer.html">Ghostyle Transfer</a> tool extracts painted makeup patterns from a workshop before/after image pair and attaches them to a new target face. When the local face engine detects matching landmarks, the tool aligns the pattern using a 3D face mesh. Otherwise, it scales the pattern using manual bounding boxes, letting researchers visual-test camouflage layouts on different facial structures before physical application.
</p>
<h3>Latent Space Visualizer</h3>
<p>
The <a href="realtime.html">Latent Space Visualizer</a> is a real-time visual debugger for analyzing face descriptor drift. By recording a baseline face signature via webcam, it tracks facial embedding alterations and showcases biometric distance thresholds alongside a 128-dimensional descriptor equalizer. It is used to study exactly how specific facial movements, lighting shifts, and makeup strokes skew facial signatures.
</p>
</section>

<section class="content-section" id="resources">
<h2>Read, test, report.</h2>
<p>
Expand All @@ -219,6 +238,18 @@ <h2>Read, test, report.</h2>
<h3>Open the lab</h3>
<p>Run local browser tests and watch a recognition pipeline react to your face in real time.</p>
</a>
<a class="resource-card" href="loader.html">
<h3>Video Loader</h3>
<p>Run pre-recorded MP4 video tests to extract and compare face signatures frame-by-frame.</p>
</a>
<a class="resource-card" href="ghostyle-transfer.html">
<h3>Ghostyle Transfer</h3>
<p>Extract makeup patterns from before/after images and project them onto new target faces.</p>
</a>
<a class="resource-card" href="realtime.html">
<h3>Latent Space Visualizer</h3>
<p>Debug biometric distance thresholds and visualize face descriptor drift in real time.</p>
</a>
<a class="resource-card" href="/references/">
<h3>Research references</h3>
<p>Trace the artistic, technical, and activist lineage of face-obfuscation work.</p>
Expand All @@ -243,6 +274,7 @@ <h2>On this page</h2>
<a href="#origin">Origin</a>
<a href="#research-question">Research question</a>
<a href="#leaks">Why leaking matters</a>
<a href="#internal-tools">Internal tools</a>
<a href="#resources">Resources</a>
</nav>
</aside>
Expand Down
176 changes: 176 additions & 0 deletions ghostyle-transfer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
<!doctype html>
<html lang="en">

<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
<title>Ghostyle Transfer — Ghostmaxxing</title>
<meta name="generator" content="Hand-coded with rage against the algorithm" />
<meta name="description"
content="Extract a Ghostyle from a before/after workshop pair and transfer it to another face locally in the browser." />
<meta property="og:title" content="Ghostyle Transfer - Ghostmaxxing" />
<meta property="og:description" content="Transfer adversarial makeup patterns between faces in a local browser lab." />
<meta property="og:type" content="website" />
<meta property="og:image" content="/images/social-card.svg" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Ghostyle Transfer - Ghostmaxxing" />
<meta name="twitter:description" content="Transfer adversarial makeup patterns between faces in a local browser lab." />
<meta name="twitter:image" content="/images/social-card.svg" />
<link rel="icon" type="image/svg+xml" href="/images/ghostmaxxing-favicon.svg" />
<link rel="stylesheet" href="styles/pages.css" />
<link rel="stylesheet" href="styles/content-pages.css" />
<script src="https://cdn.jsdelivr.net/npm/@vladmandic/face-api/dist/face-api.js"></script>
</head>

<body>
<main class="editorial-homepage content-page editorial-homepage--svg-footer-band">
<div class="editorial-homepage__shell">
<header class="editorial-homepage__header">
<a class="wordmark" href="index.html" aria-label="Ghostmaxxing homepage" data-i18n-aria-label="homepage_wordmark_label">
<span class="wordmark__main">
<img class="wordmark__mark" src="images/ghostmaxxing-mark.svg" alt="" aria-hidden="true" />
<span class="wordmark__title">Ghostmaxxing</span>
</span>
<span class="wordmark__subtitle" data-i18n="homepage_subtitle">
A public lab for testing face-recognition camouflage
</span>
</a>

<nav class="editorial-homepage__nav" aria-label="Project navigation" data-i18n-aria-label="prototype_navigation_label">
<a class="navlink" href="/references/" rel="noopener noreferrer" data-i18n="research_references_link">Research References</a>
<a class="navlink" href="about.html" data-i18n="vision_about_link">Vision &amp; About</a>
<a class="cta-inline" href="lab.html" data-i18n-html="open_lab_link_html">Open the lab <span aria-hidden="true">↗</span></a>
<span class="locale-control">
<span class="locale-control__flag" aria-hidden="true"></span>
<select id="localeSelect" class="locale-select" aria-label="Language" data-i18n-aria-label="language_label"></select>
</span>
</nav>
</header>

<section class="content-page__header-area" aria-labelledby="transfer-title">
<span class="content-page__kicker" data-i18n="transfer_kicker">Lab · Ghostyle transfer</span>
<h1 id="transfer-title" data-i18n="transfer_title">Lift a Ghostyle off one face, wear it on another.</h1>
<p class="content-page__lead" data-i18n="transfer_lead">
Extract the paint from a workshop before / after pair and place it on a target face.
</p>
<p class="content-page__deck" data-i18n="transfer_deck">
Mark the face on each image with a box. The transfer runs entirely on this device, with mesh alignment when the local face engine can detect matching landmarks.
</p>
<span class="tool-status" id="engineBadge">
<span class="tool-status__dot" aria-hidden="true"></span>
<span id="engineText" data-i18n="transfer_engine_loading">Mesh engine: loading…</span>
</span>
</section>

<section class="tool-workbench" aria-label="Ghostyle transfer workbench" data-i18n-aria-label="transfer_workbench_label">
<div class="tool-grid">
<article class="tool-card">
<div class="tool-card__header">
<span class="tool-card__title" data-i18n="transfer_before_title">Before</span>
<span class="tool-card__tag" data-i18n="transfer_before_tag">bare face · optional</span>
</div>
<div class="tool-stage" id="stage-before">
<span class="tool-stage__hint" id="hint-before" data-i18n="transfer_before_hint">Drop or choose the bare workshop face</span>
</div>
<p class="tool-note" id="note-before"></p>
<label class="file-picker">
<input type="file" accept="image/*" data-slot="before" />
<span data-i18n="transfer_choose_image">Choose image</span>
</label>
</article>

<article class="tool-card">
<div class="tool-card__header">
<span class="tool-card__title" data-i18n="transfer_after_title">After</span>
<span class="tool-card__tag" data-i18n="transfer_after_tag">painted face</span>
</div>
<div class="tool-stage" id="stage-after">
<span class="tool-stage__hint" id="hint-after" data-i18n="transfer_after_hint">Drop or choose the painted workshop face</span>
</div>
<p class="tool-note" id="note-after"></p>
<label class="file-picker">
<input type="file" accept="image/*" data-slot="after" />
<span data-i18n="transfer_choose_image">Choose image</span>
</label>
</article>

<article class="tool-card">
<div class="tool-card__header">
<span class="tool-card__title" data-i18n="transfer_target_title">Target</span>
<span class="tool-card__tag" data-i18n="transfer_target_tag">receives the paint</span>
</div>
<div class="tool-stage" id="stage-target">
<span class="tool-stage__hint" id="hint-target" data-i18n="transfer_target_hint">Drop or choose the target face / painting</span>
</div>
<p class="tool-note" id="note-target"></p>
<label class="file-picker">
<input type="file" accept="image/*" data-slot="target" />
<span data-i18n="transfer_choose_image">Choose image</span>
</label>
</article>
</div>

<div class="tool-controls">
<div class="tool-field">
<label class="tool-label" for="thr"><span data-i18n="transfer_sensitivity_label">Paint sensitivity</span> <output id="v-thr">28</output></label>
<input type="range" id="thr" min="6" max="90" value="28" />
</div>
<div class="tool-field">
<label class="tool-label" for="fth"><span data-i18n="transfer_feather_label">Edge feather</span> <output id="v-fth">3</output></label>
<input type="range" id="fth" min="0" max="12" value="3" />
</div>
<div class="tool-field">
<label class="tool-label" for="op"><span data-i18n="transfer_opacity_label">Opacity</span> <output id="v-op">92</output>%</label>
<input type="range" id="op" min="20" max="100" value="92" />
</div>
<div class="tool-field">
<label class="tool-label" for="blend" data-i18n="transfer_blend_label">Blend</label>
<select id="blend">
<option value="multiply" selected data-i18n="transfer_blend_multiply">Multiply (paints onto)</option>
<option value="source-over" data-i18n="transfer_blend_normal">Normal</option>
<option value="soft-light" data-i18n="transfer_blend_soft_light">Soft light</option>
</select>
</div>
<div class="tool-field tool-field--check">
<input type="checkbox" id="useMesh" checked />
<label for="useMesh" data-i18n="transfer_use_mesh_label">Follow features (mesh)</label>
</div>
</div>

<div class="tool-actions">
<button class="btn-primary" id="run" disabled data-i18n="transfer_run_button">Transfer Ghostyle</button>
<button class="link-secondary" id="download" disabled data-i18n="transfer_download_button">Download result</button>
<button class="link-secondary" id="reset" data-i18n="transfer_reset_button">Reset</button>
<span class="tool-message" id="msg" data-i18n="transfer_msg_load_after_target">Load an after and a target, then draw a box on each face.</span>
</div>

<div class="tool-output">
<div class="tool-result" id="result">
<span class="tool-result__empty" data-i18n="transfer_result_empty">The transferred result will appear here.</span>
</div>
<aside class="tool-aside">
<section class="tool-panel">
<h2 data-i18n="transfer_matte_title">Extracted paint</h2>
<canvas id="matte" width="180" height="180"></canvas>
<p id="matteNote" data-i18n="transfer_matte_note">The isolated Ghostyle, in a normalized face frame.</p>
</section>
<section class="tool-panel">
<h2 data-i18n="transfer_mode_title">Mode</h2>
<p id="modeNote" data-i18n="transfer_mode_not_run">Not run yet.</p>
</section>
</aside>
</div>
</section>

<footer class="tool-disclaimer">
<p data-i18n="transfer_disclaimer">
Ghostmaxxing is a research and education tool. Transferring a Ghostyle onto a face is a visualization; it is not evidence that the pattern affects any recognition system. Face detectors are unreliable on stylized artwork. If the mesh cannot find a target face, the tool uses the box you drew.
</p>
</footer>
</div>
</main>

<script type="module" src="scripts/transfer.js"></script>
</body>

</html>
5 changes: 5 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ <h1 class="hero-copy__title" id="hero-title-a" data-i18n-html="hero_title_html">
<div class="hero-copy__actions">
<a class="btn-primary" href="lab.html" data-i18n-html="open_lab_link_html">Open the lab <span aria-hidden="true">↗</span></a>
</div>
<div class="hero-copy__meta" aria-label="Internal tools" data-i18n-aria-label="internal_tools_label">
<span><a href="loader.html" data-i18n="video_loader_link">Video Loader</a></span>
<span><a href="ghostyle-transfer.html" data-i18n="ghostyle_transfer_link">Ghostyle Transfer</a></span>
<span><a href="realtime.html" data-i18n="realtime_visualizer_link">Latent Space Visualizer</a></span>
</div>
<div class="hero-copy__meta" aria-label="Explore further" data-i18n-aria-label="explore_further_label">
<span><a href="/docs" data-i18n="technical_documentation_link">Technical documentation</a></span>
<span><a href="https://github.com/vecna/ghostati" data-i18n="code_link">Code</a></span>
Expand Down
Loading