@jjlmoya/utils-audiovisual 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +60 -0
- package/src/category/i18n/en.ts +198 -0
- package/src/category/i18n/es.ts +198 -0
- package/src/category/i18n/fr.ts +198 -0
- package/src/category/index.ts +17 -0
- package/src/category/seo.astro +15 -0
- package/src/components/PreviewNavSidebar.astro +116 -0
- package/src/components/PreviewToolbar.astro +143 -0
- package/src/data.ts +4 -0
- package/src/env.d.ts +5 -0
- package/src/index.ts +32 -0
- package/src/layouts/PreviewLayout.astro +117 -0
- package/src/pages/[locale]/[slug].astro +146 -0
- package/src/pages/[locale].astro +251 -0
- package/src/pages/index.astro +4 -0
- package/src/tests/faq_count.test.ts +19 -0
- package/src/tests/locale_completeness.test.ts +42 -0
- package/src/tests/mocks/astro_mock.js +2 -0
- package/src/tests/no_h1_in_components.test.ts +48 -0
- package/src/tests/seo_length.test.ts +22 -0
- package/src/tests/tool_validation.test.ts +17 -0
- package/src/tool/chromaticLens/bibliography.astro +17 -0
- package/src/tool/chromaticLens/component.astro +178 -0
- package/src/tool/chromaticLens/i18n/en.ts +246 -0
- package/src/tool/chromaticLens/i18n/es.ts +244 -0
- package/src/tool/chromaticLens/i18n/fr.ts +244 -0
- package/src/tool/chromaticLens/index.ts +43 -0
- package/src/tool/chromaticLens/logic.ts +87 -0
- package/src/tool/chromaticLens/seo.astro +15 -0
- package/src/tool/chromaticLens/style.css +308 -0
- package/src/tool/chromaticLens/ui.ts +109 -0
- package/src/tool/collageMaker/bibliography.astro +17 -0
- package/src/tool/collageMaker/component.astro +302 -0
- package/src/tool/collageMaker/i18n/en.ts +233 -0
- package/src/tool/collageMaker/i18n/es.ts +231 -0
- package/src/tool/collageMaker/i18n/fr.ts +231 -0
- package/src/tool/collageMaker/index.ts +51 -0
- package/src/tool/collageMaker/logic.ts +134 -0
- package/src/tool/collageMaker/seo.astro +15 -0
- package/src/tool/collageMaker/style.css +386 -0
- package/src/tool/exifCleaner/bibliography.astro +18 -0
- package/src/tool/exifCleaner/component.astro +162 -0
- package/src/tool/exifCleaner/i18n/en.ts +277 -0
- package/src/tool/exifCleaner/i18n/es.ts +277 -0
- package/src/tool/exifCleaner/i18n/fr.ts +277 -0
- package/src/tool/exifCleaner/index.ts +57 -0
- package/src/tool/exifCleaner/logic.ts +135 -0
- package/src/tool/exifCleaner/seo.astro +18 -0
- package/src/tool/exifCleaner/style.css +289 -0
- package/src/tool/exifCleaner/ui.ts +117 -0
- package/src/tool/imageCompressor/bibliography.astro +17 -0
- package/src/tool/imageCompressor/component.astro +262 -0
- package/src/tool/imageCompressor/i18n/en.ts +232 -0
- package/src/tool/imageCompressor/i18n/es.ts +230 -0
- package/src/tool/imageCompressor/i18n/fr.ts +230 -0
- package/src/tool/imageCompressor/index.ts +50 -0
- package/src/tool/imageCompressor/logic.ts +79 -0
- package/src/tool/imageCompressor/seo.astro +15 -0
- package/src/tool/imageCompressor/style.css +503 -0
- package/src/tool/printQualityCalculator/bibliography.astro +18 -0
- package/src/tool/printQualityCalculator/component.astro +318 -0
- package/src/tool/printQualityCalculator/i18n/en.ts +247 -0
- package/src/tool/printQualityCalculator/i18n/es.ts +245 -0
- package/src/tool/printQualityCalculator/i18n/fr.ts +245 -0
- package/src/tool/printQualityCalculator/index.ts +56 -0
- package/src/tool/printQualityCalculator/logic.ts +53 -0
- package/src/tool/printQualityCalculator/seo.astro +18 -0
- package/src/tool/printQualityCalculator/style.css +491 -0
- package/src/tool/printQualityCalculator/ui.ts +122 -0
- package/src/tool/privacyBlur/bibliography.astro +17 -0
- package/src/tool/privacyBlur/component.astro +230 -0
- package/src/tool/privacyBlur/i18n/en.ts +238 -0
- package/src/tool/privacyBlur/i18n/es.ts +236 -0
- package/src/tool/privacyBlur/i18n/fr.ts +236 -0
- package/src/tool/privacyBlur/index.ts +49 -0
- package/src/tool/privacyBlur/logic.ts +249 -0
- package/src/tool/privacyBlur/seo.astro +15 -0
- package/src/tool/privacyBlur/style.css +332 -0
- package/src/tool/privacyBlur/ui.ts +124 -0
- package/src/tool/subtitleSync/bibliography.astro +17 -0
- package/src/tool/subtitleSync/component.astro +187 -0
- package/src/tool/subtitleSync/i18n/en.ts +241 -0
- package/src/tool/subtitleSync/i18n/es.ts +241 -0
- package/src/tool/subtitleSync/i18n/fr.ts +241 -0
- package/src/tool/subtitleSync/index.ts +49 -0
- package/src/tool/subtitleSync/logic.ts +91 -0
- package/src/tool/subtitleSync/seo.astro +15 -0
- package/src/tool/subtitleSync/style.css +325 -0
- package/src/tool/subtitleSync/ui.ts +152 -0
- package/src/tool/timelapseCalculator/bibliography.astro +15 -0
- package/src/tool/timelapseCalculator/component.astro +148 -0
- package/src/tool/timelapseCalculator/i18n/en.ts +169 -0
- package/src/tool/timelapseCalculator/i18n/es.ts +169 -0
- package/src/tool/timelapseCalculator/i18n/fr.ts +169 -0
- package/src/tool/timelapseCalculator/index.ts +52 -0
- package/src/tool/timelapseCalculator/logic.ts +46 -0
- package/src/tool/timelapseCalculator/seo.astro +18 -0
- package/src/tool/timelapseCalculator/style.css +285 -0
- package/src/tool/tvDistance/bibliography.astro +17 -0
- package/src/tool/tvDistance/component.astro +178 -0
- package/src/tool/tvDistance/i18n/en.ts +223 -0
- package/src/tool/tvDistance/i18n/es.ts +223 -0
- package/src/tool/tvDistance/i18n/fr.ts +223 -0
- package/src/tool/tvDistance/index.ts +49 -0
- package/src/tool/tvDistance/logic.ts +47 -0
- package/src/tool/tvDistance/seo.astro +15 -0
- package/src/tool/tvDistance/style.css +435 -0
- package/src/tool/tvDistance/ui.ts +66 -0
- package/src/tool/videoFrameExtractor/bibliography.astro +17 -0
- package/src/tool/videoFrameExtractor/component.astro +285 -0
- package/src/tool/videoFrameExtractor/i18n/en.ts +235 -0
- package/src/tool/videoFrameExtractor/i18n/es.ts +235 -0
- package/src/tool/videoFrameExtractor/i18n/fr.ts +235 -0
- package/src/tool/videoFrameExtractor/index.ts +53 -0
- package/src/tool/videoFrameExtractor/logic.ts +49 -0
- package/src/tool/videoFrameExtractor/seo.astro +15 -0
- package/src/tool/videoFrameExtractor/style.css +426 -0
- package/src/tool/videoFrameExtractor/ui.ts +179 -0
- package/src/tools.ts +25 -0
- package/src/types.ts +72 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { PrivacyBlurUI } from './index';
|
|
3
|
+
import './style.css';
|
|
4
|
+
|
|
5
|
+
interface Props {
|
|
6
|
+
ui: PrivacyBlurUI;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const { ui } = Astro.props;
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
<div class="pb-root" id="privacy-blur-root" data-ui={JSON.stringify(ui)}>
|
|
13
|
+
|
|
14
|
+
<div class="pb-toolbar">
|
|
15
|
+
<div class="pb-tool-selector">
|
|
16
|
+
<button class="pb-tool-btn pb-tool-btn-active" data-tool="pixel" title={ui.toolPixel}>
|
|
17
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="currentColor">
|
|
18
|
+
<path d="M3 3h8v8H3zm0 10h8v8H3zm10-10h8v8h-8zm0 10h8v8h-8z"/>
|
|
19
|
+
</svg>
|
|
20
|
+
<span>{ui.toolPixel}</span>
|
|
21
|
+
</button>
|
|
22
|
+
<button class="pb-tool-btn" data-tool="blur" title={ui.toolBlur}>
|
|
23
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="none" stroke="currentColor" stroke-width="1.5">
|
|
24
|
+
<circle cx="12" cy="12" r="2" fill="currentColor" stroke="none"/>
|
|
25
|
+
<circle cx="12" cy="12" r="5"/>
|
|
26
|
+
<circle cx="12" cy="12" r="9"/>
|
|
27
|
+
</svg>
|
|
28
|
+
<span>{ui.toolBlur}</span>
|
|
29
|
+
</button>
|
|
30
|
+
<button class="pb-tool-btn" data-tool="solid" title={ui.toolSolid}>
|
|
31
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="currentColor">
|
|
32
|
+
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
|
33
|
+
</svg>
|
|
34
|
+
<span>{ui.toolSolid}</span>
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
<div class="pb-settings-row">
|
|
39
|
+
<div class="pb-intensity-wrap">
|
|
40
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="currentColor">
|
|
41
|
+
<path d="M3 6h18v2H3zm3-4v4M12 4v4M21 6h-3M9 11v4M15 11v4M3 12h6m6 0h6M3 18h18v2H3zm3-4v4m6-4v4m6-4v4"/>
|
|
42
|
+
</svg>
|
|
43
|
+
<input type="range" id="pb-intensity" min="1" max="50" value="10" class="pb-slider" />
|
|
44
|
+
</div>
|
|
45
|
+
<button id="pb-auto-face" class="pb-auto-btn">
|
|
46
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="currentColor">
|
|
47
|
+
<path d="M9 11.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm9 0a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zM12 18c-2 0-3.5-1-4-2.5h8c-.5 1.5-2 2.5-4 2.5zM1 1v6h2V3h4V1H1zm18 0v2h4v4h2V1h-6zM1 17v6h6v-2H3v-4H1zm20 4v-4h2v6h-6v-2h4z"/>
|
|
48
|
+
</svg>
|
|
49
|
+
<span>{ui.autoDetectFaces}</span>
|
|
50
|
+
</button>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
<div class="pb-action-group">
|
|
54
|
+
<button id="pb-undo" class="pb-undo-btn" disabled title={ui.undoButton}>
|
|
55
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="currentColor">
|
|
56
|
+
<path d="M12.5 8c-2.65 0-5.05.99-6.9 2.6L2 7v9h9l-3.62-3.62A6.89 6.89 0 0 1 12.5 10c3.54 0 6.55 2.31 7.6 5.5l2.37-.78C21.08 11.03 17.15 8 12.5 8z"/>
|
|
57
|
+
</svg>
|
|
58
|
+
</button>
|
|
59
|
+
<button id="pb-download" class="pb-download-btn" disabled>
|
|
60
|
+
<svg viewBox="0 0 24 24" class="pb-icon" aria-hidden="true" fill="currentColor">
|
|
61
|
+
<path d="M5 20h14v-2H5v2zM19 9h-4V3H9v6H5l7 7 7-7z"/>
|
|
62
|
+
</svg>
|
|
63
|
+
<span>{ui.downloadButton}</span>
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<div id="pb-workspace" class="pb-workspace">
|
|
69
|
+
<input type="file" id="pb-file-input" accept="image/*" class="pb-hidden" />
|
|
70
|
+
|
|
71
|
+
<div id="pb-empty" class="pb-empty">
|
|
72
|
+
<div class="pb-upload-icon">
|
|
73
|
+
<svg viewBox="0 0 24 24" class="pb-icon-lg" aria-hidden="true" fill="currentColor">
|
|
74
|
+
<path d="M19.35 10.04A7.49 7.49 0 0 0 12 4C9.11 4 6.6 5.64 5.35 8.04A5.994 5.994 0 0 0 0 14c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96zM14 13v4h-4v-4H7l5-5 5 5h-3z"/>
|
|
75
|
+
</svg>
|
|
76
|
+
</div>
|
|
77
|
+
<h2 class="pb-empty-title">{ui.dropTitle}</h2>
|
|
78
|
+
<p class="pb-empty-sub">{ui.dropSubtitle}</p>
|
|
79
|
+
<div class="pb-badges">
|
|
80
|
+
<div class="pb-badge">
|
|
81
|
+
<svg viewBox="0 0 24 24" class="pb-badge-icon" aria-hidden="true" fill="currentColor">
|
|
82
|
+
<path d="M12 1L3 5v6c0 5.55 3.84 10.74 9 12 5.16-1.26 9-6.45 9-12V5l-9-4zm-2 16l-4-4 1.41-1.41L10 14.17l6.59-6.59L18 9l-8 8z"/>
|
|
83
|
+
</svg>
|
|
84
|
+
<span>{ui.privacySecureLabel}</span>
|
|
85
|
+
</div>
|
|
86
|
+
<div class="pb-badge">
|
|
87
|
+
<svg viewBox="0 0 24 24" class="pb-badge-icon" aria-hidden="true" fill="currentColor">
|
|
88
|
+
<path d="M7 2v11h3v9l7-12h-4l4-8z"/>
|
|
89
|
+
</svg>
|
|
90
|
+
<span>{ui.offlineLabel}</span>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
|
|
95
|
+
<div id="pb-loader" class="pb-loader pb-hidden">
|
|
96
|
+
<div class="pb-spinner"></div>
|
|
97
|
+
<p id="pb-loader-text" class="pb-loader-text"></p>
|
|
98
|
+
</div>
|
|
99
|
+
|
|
100
|
+
<div id="pb-canvas-wrap" class="pb-canvas-wrap pb-hidden">
|
|
101
|
+
<canvas id="pb-canvas"></canvas>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<script is:inline src="https://cdn.jsdelivr.net/npm/face-api.js@0.22.2/dist/face-api.min.js"></script>
|
|
108
|
+
|
|
109
|
+
<script>
|
|
110
|
+
import { PrivacyBlurEngine } from './logic';
|
|
111
|
+
import type { ToolType } from './logic';
|
|
112
|
+
import type { PrivacyBlurUI } from './index';
|
|
113
|
+
|
|
114
|
+
function init() {
|
|
115
|
+
const root = document.getElementById('privacy-blur-root');
|
|
116
|
+
if (!root) return;
|
|
117
|
+
|
|
118
|
+
const labels = JSON.parse(root.dataset.ui ?? '{}') as PrivacyBlurUI;
|
|
119
|
+
const canvas = root.querySelector('#pb-canvas') as HTMLCanvasElement;
|
|
120
|
+
const engine = new PrivacyBlurEngine(canvas);
|
|
121
|
+
|
|
122
|
+
const fileInput = root.querySelector('#pb-file-input') as HTMLInputElement;
|
|
123
|
+
const emptyState = root.querySelector('#pb-empty') as HTMLElement;
|
|
124
|
+
const canvasWrap = root.querySelector('#pb-canvas-wrap') as HTMLElement;
|
|
125
|
+
const undoBtn = root.querySelector('#pb-undo') as HTMLButtonElement;
|
|
126
|
+
const downloadBtn = root.querySelector('#pb-download') as HTMLButtonElement;
|
|
127
|
+
const intensityRange = root.querySelector('#pb-intensity') as HTMLInputElement;
|
|
128
|
+
const autoFaceBtn = root.querySelector('#pb-auto-face') as HTMLButtonElement;
|
|
129
|
+
const loader = root.querySelector('#pb-loader') as HTMLElement;
|
|
130
|
+
const loaderText = root.querySelector('#pb-loader-text') as HTMLElement;
|
|
131
|
+
const toolBtns = root.querySelectorAll('.pb-tool-btn') as NodeListOf<HTMLButtonElement>;
|
|
132
|
+
|
|
133
|
+
function updateControls() {
|
|
134
|
+
undoBtn.disabled = !engine.hasLayers();
|
|
135
|
+
downloadBtn.disabled = !engine.hasLayers();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function handleFile(file: File) {
|
|
139
|
+
const reader = new FileReader();
|
|
140
|
+
reader.onload = (e) => {
|
|
141
|
+
const img = new Image();
|
|
142
|
+
img.onload = () => {
|
|
143
|
+
engine.setImage(img);
|
|
144
|
+
emptyState.classList.add('pb-hidden');
|
|
145
|
+
canvasWrap.classList.remove('pb-hidden');
|
|
146
|
+
updateControls();
|
|
147
|
+
};
|
|
148
|
+
img.src = e.target?.result as string;
|
|
149
|
+
};
|
|
150
|
+
reader.readAsDataURL(file);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
fileInput.addEventListener('change', () => {
|
|
154
|
+
if (fileInput.files?.[0]) handleFile(fileInput.files[0]);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
emptyState.addEventListener('click', () => fileInput.click());
|
|
158
|
+
|
|
159
|
+
root.addEventListener('dragover', (e) => {
|
|
160
|
+
e.preventDefault();
|
|
161
|
+
root.classList.add('pb-dragging');
|
|
162
|
+
});
|
|
163
|
+
root.addEventListener('dragleave', () => root.classList.remove('pb-dragging'));
|
|
164
|
+
root.addEventListener('drop', (e) => {
|
|
165
|
+
e.preventDefault();
|
|
166
|
+
root.classList.remove('pb-dragging');
|
|
167
|
+
if (e.dataTransfer?.files[0]) handleFile(e.dataTransfer.files[0]);
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
toolBtns.forEach(btn => {
|
|
171
|
+
btn.addEventListener('click', () => {
|
|
172
|
+
toolBtns.forEach(b => b.classList.remove('pb-tool-btn-active'));
|
|
173
|
+
btn.classList.add('pb-tool-btn-active');
|
|
174
|
+
engine.updateLastLayerTool(btn.dataset.tool as ToolType);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
intensityRange.addEventListener('input', () => {
|
|
179
|
+
engine.setIntensity(parseInt(intensityRange.value));
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
undoBtn.addEventListener('click', () => {
|
|
183
|
+
engine.undo();
|
|
184
|
+
updateControls();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
downloadBtn.addEventListener('click', () => {
|
|
188
|
+
const a = document.createElement('a');
|
|
189
|
+
a.download = `privacy_protected_${Date.now()}.webp`;
|
|
190
|
+
a.href = engine.getCanvasData();
|
|
191
|
+
a.click();
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
autoFaceBtn.addEventListener('click', async () => {
|
|
195
|
+
loader.classList.remove('pb-hidden');
|
|
196
|
+
try {
|
|
197
|
+
const found = await engine.detectFaces((text) => {
|
|
198
|
+
loaderText.textContent = text;
|
|
199
|
+
});
|
|
200
|
+
if (!found) alert(labels.noFacesDetected);
|
|
201
|
+
updateControls();
|
|
202
|
+
} finally {
|
|
203
|
+
loader.classList.add('pb-hidden');
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
canvas.addEventListener('mousedown', (e) => engine.startDragging(e.clientX, e.clientY));
|
|
208
|
+
window.addEventListener('mousemove', (e) => engine.updateDragging(e.clientX, e.clientY));
|
|
209
|
+
window.addEventListener('mouseup', () => {
|
|
210
|
+
if (engine.endDragging()) updateControls();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
canvas.addEventListener('touchstart', (e) => {
|
|
214
|
+
e.preventDefault();
|
|
215
|
+
engine.startDragging(e.touches[0]!.clientX, e.touches[0]!.clientY);
|
|
216
|
+
}, { passive: false });
|
|
217
|
+
canvas.addEventListener('touchmove', (e) => {
|
|
218
|
+
e.preventDefault();
|
|
219
|
+
engine.updateDragging(e.touches[0]!.clientX, e.touches[0]!.clientY);
|
|
220
|
+
}, { passive: false });
|
|
221
|
+
canvas.addEventListener('touchend', () => {
|
|
222
|
+
if (engine.endDragging()) updateControls();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
updateControls();
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
init();
|
|
229
|
+
document.addEventListener('astro:page-load', init);
|
|
230
|
+
</script>
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import type { WithContext, FAQPage, HowTo, SoftwareApplication } from 'schema-dts';
|
|
2
|
+
import type { PrivacyBlurUI, PrivacyBlurLocaleContent } from '../index';
|
|
3
|
+
|
|
4
|
+
const slug = 'online-privacy-editor-pixelate-blur-faces-photos';
|
|
5
|
+
const title = 'Online Privacy Editor - Pixelate and hide faces in photos';
|
|
6
|
+
const description = 'Protect your identity by censoring sensitive areas of your photos. Pixelate faces, blur documents, or cover private information 100% locally.';
|
|
7
|
+
|
|
8
|
+
const ui: PrivacyBlurUI = {
|
|
9
|
+
toolPixel: "Pixelate",
|
|
10
|
+
toolBlur: "Blur",
|
|
11
|
+
toolSolid: "Solid",
|
|
12
|
+
intensityLabel: "Intensity",
|
|
13
|
+
undoButton: "Undo",
|
|
14
|
+
downloadButton: "Save",
|
|
15
|
+
dropTitle: "Privacy Editor",
|
|
16
|
+
dropSubtitle: "Drag your image here or click to start",
|
|
17
|
+
privacySecureLabel: "100% Local",
|
|
18
|
+
offlineLabel: "Offline",
|
|
19
|
+
autoDetectFaces: "Auto Detect",
|
|
20
|
+
loadingModels: "Loading models...",
|
|
21
|
+
noFacesDetected: "No faces automatically detected.",
|
|
22
|
+
faqTitle: "Frequently Asked Questions",
|
|
23
|
+
bibliographyTitle: "References"
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
const faq: PrivacyBlurLocaleContent['faq'] = [
|
|
27
|
+
{
|
|
28
|
+
question: "Are my photos uploaded to any server?",
|
|
29
|
+
answer: "No. The privacy editor works entirely in your browser. Pixels are modified locally and nothing is sent outside of your device.",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
question: "How does automatic face detection work?",
|
|
33
|
+
answer: "We use a lightweight neural network (TinyFaceDetector) that runs in your browser to identify facial features without needing an external connection.",
|
|
34
|
+
},
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
const howTo: PrivacyBlurLocaleContent['howTo'] = [
|
|
38
|
+
{
|
|
39
|
+
name: "Upload your photo",
|
|
40
|
+
text: "Drag or select the image that contains sensitive information you wish to hide.",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
name: "Choose the tool",
|
|
44
|
+
text: "Select between pixelate, blur, or solid cover depending on the level of privacy you need.",
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "Mark the area",
|
|
48
|
+
text: "Click and drag over the zone you want to protect (faces, license plates, names, etc.).",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "Save the result",
|
|
52
|
+
text: "Download the processed image with the security that the original data is inaccessible.",
|
|
53
|
+
},
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
const bibliography: PrivacyBlurLocaleContent['bibliography'] = [
|
|
57
|
+
{
|
|
58
|
+
name: "Privacy by Design (PbD) - AGPD",
|
|
59
|
+
url: "https://www.aepd.es/es/guias-y-herramientas/guias/guia-de-privacidad-desde-el-diseno",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
const seo: PrivacyBlurLocaleContent['seo'] = [
|
|
64
|
+
{
|
|
65
|
+
type: 'summary',
|
|
66
|
+
title: 'Privacy Editor: Pixelate, Blur, and Hide',
|
|
67
|
+
items: [
|
|
68
|
+
'Three editing tools: Pixelate, Blur, Solid Cover',
|
|
69
|
+
'Automatic face detection with AI (TinyFaceDetector)',
|
|
70
|
+
'100% local processing - your photos never leave the browser',
|
|
71
|
+
'No watermarks, no limits, completely free'
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{ type: 'title', text: 'Digital Privacy: How to Protect Your Visual Data', level: 2 },
|
|
75
|
+
{ type: 'paragraph', html: 'In the era of social media, sharing photos without control can expose sensitive personal data. Our tool allowed you to hide critical information (faces, license plates, names, addresses) before uploading them to the Internet, ensuring that your privacy remains under your full control.' },
|
|
76
|
+
|
|
77
|
+
{ type: 'stats', items: [
|
|
78
|
+
{ value: '3', label: 'Hiding Methods', icon: 'mdi:tools' },
|
|
79
|
+
{ value: '100%', label: 'Local Privacy', icon: 'mdi:shield-check' },
|
|
80
|
+
{ value: 'AI', label: 'Face Detection', icon: 'mdi:brain' }
|
|
81
|
+
], columns: 3 },
|
|
82
|
+
|
|
83
|
+
{ type: 'title', text: 'Three Hiding Methods Explained', level: 3 },
|
|
84
|
+
{ type: 'comparative', items: [
|
|
85
|
+
{
|
|
86
|
+
title: 'Pixelate',
|
|
87
|
+
description: 'Divides the area into squares, impossible to recognize',
|
|
88
|
+
icon: 'mdi:blur',
|
|
89
|
+
points: [
|
|
90
|
+
'Maximum irreversible obfuscation',
|
|
91
|
+
'More secure against facial recognition',
|
|
92
|
+
'Visible, clear that something was hidden',
|
|
93
|
+
'Ideal: faces in public photos'
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
title: 'Blur',
|
|
98
|
+
description: 'Gaussian Smoothing - more natural look',
|
|
99
|
+
icon: 'mdi:blur-off',
|
|
100
|
+
points: [
|
|
101
|
+
'More elegant visual appearance',
|
|
102
|
+
'Maintains some tone coherence',
|
|
103
|
+
'Mathematically reversible (theoretically)',
|
|
104
|
+
'Ideal: less sensitive information'
|
|
105
|
+
],
|
|
106
|
+
highlight: true
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
title: 'Solid Cover',
|
|
110
|
+
description: 'Opaque color block - maximum privacy',
|
|
111
|
+
icon: 'mdi:rectangle',
|
|
112
|
+
points: [
|
|
113
|
+
'Visible, obvious hiding',
|
|
114
|
+
'Maximum legal security/privacy',
|
|
115
|
+
'Changes visual composition',
|
|
116
|
+
'Ideal: documents, sensitive data'
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
], columns: 3 },
|
|
120
|
+
|
|
121
|
+
{ type: 'title', text: 'Automatic Face Detection with AI', level: 3 },
|
|
122
|
+
{ type: 'paragraph', html: 'Our tool uses TinyFaceDetector, a compact neural network that runs directly in your browser to identify faces automatically:' },
|
|
123
|
+
{ type: 'list', items: [
|
|
124
|
+
'<strong>100% Local:</strong> The AI model runs on your GPU/CPU, not on remote servers.',
|
|
125
|
+
'<strong>No Internet:</strong> After the initial download, it works completely offline.',
|
|
126
|
+
'<strong>Privacy Guaranteed:</strong> No one sees the faces: not Google, not OpenAI, nor us.',
|
|
127
|
+
'<strong>Automatic One-Click:</strong> Detects faces and lets you choose to hide with one click.'
|
|
128
|
+
], icon: 'mdi:check' },
|
|
129
|
+
|
|
130
|
+
{ type: 'card', title: 'Privacy by Design', html: 'By processing images using your browser\'s local GPU and CPU, we guarantee that original photos never leave your device. Even if you change your mind, nothing was transmitted. This is the maximum standard of digital privacy.' },
|
|
131
|
+
|
|
132
|
+
{ type: 'title', text: 'Privacy Use Cases', level: 3 },
|
|
133
|
+
{ type: 'table', headers: ['Sensitive Information', 'Recommended Method', 'Urgency'], rows: [
|
|
134
|
+
['People\'s faces', 'Pixelate or Blur', 'Critical'],
|
|
135
|
+
['Vehicle license plates', 'Pixelate (irreversible)', 'Critical'],
|
|
136
|
+
['Identity documents', 'Solid Cover or Pixelate', 'Critical'],
|
|
137
|
+
['Written names/addresses', 'Solid Cover or Pixelate', 'High'],
|
|
138
|
+
['Phone numbers', 'Pixelate or Cover', 'High'],
|
|
139
|
+
['Medical information', 'Solid Cover', 'Critical'],
|
|
140
|
+
['Visible WiFi signals', 'Pixelate', 'Medium']
|
|
141
|
+
] },
|
|
142
|
+
|
|
143
|
+
{ type: 'proscons', items: [
|
|
144
|
+
{
|
|
145
|
+
pro: 'Total privacy: 100% local processing, no servers, no storage',
|
|
146
|
+
con: 'Requires modern browser with Canvas and WebGL support'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
pro: 'Automatic face detection saves manual time',
|
|
150
|
+
con: 'AI is not perfect - profile or partial faces may not be detected'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
pro: 'Three methods allow choosing security vs aesthetics',
|
|
154
|
+
con: 'No advanced options (smart warp, context fill)'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
pro: 'Completely free, no advertising, no limits',
|
|
158
|
+
con: 'Not equivalent to professional software like Photoshop'
|
|
159
|
+
}
|
|
160
|
+
], proTitle: 'Advantages', conTitle: 'Limitations' },
|
|
161
|
+
|
|
162
|
+
{ type: 'diagnostic', variant: 'warning', title: 'Warning: Blurring is NOT 100% Secure', icon: 'mdi:alert', badge: 'Security', html: 'Gaussian blur is mathematically reversible through sophisticated inverse algorithms. If information is CRITICAL (legal documents, identity), use <strong>Pixelate or Solid Cover</strong>. Blur is aesthetically better but less secure.' },
|
|
163
|
+
|
|
164
|
+
{ type: 'glossary', items: [
|
|
165
|
+
{
|
|
166
|
+
term: 'Pixelation',
|
|
167
|
+
definition: 'Reducing resolution by dividing area into uniform color blocks. Irreversible. Maximum security against AI facial recognition.'
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
term: 'Gaussian Blur',
|
|
171
|
+
definition: 'Mathematical smoothing based on normal distribution. Theoretically reversible through deconvolution, but practically very difficult.'
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
term: 'Solid Cover',
|
|
175
|
+
definition: 'Opaque block of uniform color. Maximum security, maximum legal privacy, less visually elegant.'
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
term: 'TinyFaceDetector',
|
|
179
|
+
definition: 'Lightweight convolutional neural network (CNN) for detecting faces. Runs locally in browser without needing an external server.'
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
term: 'Privacy by Design (PbD)',
|
|
183
|
+
definition: 'Approach where privacy is integrated starting from system design, not added later. Our local approach is Privacy by Design.'
|
|
184
|
+
}
|
|
185
|
+
] },
|
|
186
|
+
|
|
187
|
+
{ type: 'message', title: 'Privacy in Your Control', ariaLabel: 'Information about privacy protection', html: 'We do not store or process your photos on remote servers. There are no tracking cookies. No editing histories. We don\'t know what you hide because we never see your images. Full control, full privacy, full freedom.' },
|
|
188
|
+
|
|
189
|
+
{ type: 'title', text: 'Share Securely on Social Networks', level: 3 },
|
|
190
|
+
{ type: 'paragraph', html: 'Before posting any photo on the Internet, ask yourself: is there information I would prefer not to be public? Children\'s faces, license plates, addresses, document numbers. A 2-minute privacy session now avoids years of problems.' }
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
const faqSchema: WithContext<FAQPage> = {
|
|
194
|
+
'@context': 'https://schema.org',
|
|
195
|
+
'@type': 'FAQPage',
|
|
196
|
+
mainEntity: faq.map((item) => ({
|
|
197
|
+
'@type': 'Question',
|
|
198
|
+
name: item.question,
|
|
199
|
+
acceptedAnswer: { '@type': 'Answer', text: item.answer },
|
|
200
|
+
})),
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const howToSchema: WithContext<HowTo> = {
|
|
204
|
+
'@context': 'https://schema.org',
|
|
205
|
+
'@type': 'HowTo',
|
|
206
|
+
name: title,
|
|
207
|
+
description,
|
|
208
|
+
step: howTo.map((step) => ({
|
|
209
|
+
'@type': 'HowToStep',
|
|
210
|
+
name: step.name,
|
|
211
|
+
text: step.text,
|
|
212
|
+
})),
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
const appSchema: WithContext<SoftwareApplication> = {
|
|
216
|
+
'@context': 'https://schema.org',
|
|
217
|
+
'@type': 'SoftwareApplication',
|
|
218
|
+
name: title,
|
|
219
|
+
description,
|
|
220
|
+
applicationCategory: 'UtilitiesApplication',
|
|
221
|
+
operatingSystem: 'Web',
|
|
222
|
+
offers: { '@type': 'Offer', price: '0', priceCurrency: 'EUR' },
|
|
223
|
+
inLanguage: 'en',
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
export const content: PrivacyBlurLocaleContent = {
|
|
227
|
+
slug,
|
|
228
|
+
title,
|
|
229
|
+
description,
|
|
230
|
+
ui,
|
|
231
|
+
seo,
|
|
232
|
+
faqTitle: "Frequently Asked Questions",
|
|
233
|
+
faq,
|
|
234
|
+
bibliographyTitle: "References",
|
|
235
|
+
bibliography,
|
|
236
|
+
howTo,
|
|
237
|
+
schemas: [faqSchema as any, howToSchema as any, appSchema],
|
|
238
|
+
};
|