skopix 2.0.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/web/index.html ADDED
@@ -0,0 +1,644 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Skopix — Record. Replay. Ship with confidence.</title>
7
+ <meta name="description" content="Skopix is a browser-based QA tool that records your test flows and replays them deterministically. AI stabilises selectors, generates Playwright code, and catches regressions before your users do.">
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=DM+Mono:ital,wght@0,300;0,400;0,500;1,300&family=Syne:wght@400;500;600;700;800&display=swap" rel="stylesheet">
11
+ <style>
12
+ :root {
13
+ --bg: #080810;
14
+ --surface: #0d0d1a;
15
+ --surface2: #12121f;
16
+ --border: rgba(255,255,255,0.06);
17
+ --border-bright: rgba(0,212,255,0.2);
18
+ --cyan: #00d4ff;
19
+ --cyan-dim: rgba(0,212,255,0.12);
20
+ --cyan-mid: rgba(0,212,255,0.4);
21
+ --purple: #7c3aed;
22
+ --purple-dim: rgba(124,58,237,0.12);
23
+ --amber: #f59e0b;
24
+ --green: #22c55e;
25
+ --red: #ef4444;
26
+ --text: #e8eaf0;
27
+ --muted: #5a6180;
28
+ --muted2: #3a3f5c;
29
+ --white: #ffffff;
30
+ --mono: 'DM Mono', monospace;
31
+ --display: 'Syne', sans-serif;
32
+ }
33
+
34
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
35
+ html { scroll-behavior: smooth; overflow-x: hidden; }
36
+ body { background: var(--bg); color: var(--text); font-family: var(--display); overflow-x: hidden; cursor: none; }
37
+
38
+ .cursor { position: fixed; width: 8px; height: 8px; background: var(--cyan); border-radius: 50%; pointer-events: none; z-index: 9999; transition: transform 0.1s ease; mix-blend-mode: screen; }
39
+ .cursor-ring { position: fixed; width: 32px; height: 32px; border: 1px solid var(--cyan-mid); border-radius: 50%; pointer-events: none; z-index: 9998; transition: transform 0.15s ease, width 0.2s ease, height 0.2s ease, opacity 0.2s ease; }
40
+ .cursor.hover { transform: scale(3); }
41
+ .cursor-ring.hover { width: 50px; height: 50px; opacity: 0.5; }
42
+
43
+ body::before { content: ''; position: fixed; inset: 0; background-image: linear-gradient(rgba(0,212,255,0.03) 1px, transparent 1px), linear-gradient(90deg, rgba(0,212,255,0.03) 1px, transparent 1px); background-size: 60px 60px; pointer-events: none; z-index: 0; }
44
+
45
+ nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; padding: 20px 48px; display: flex; align-items: center; justify-content: space-between; background: linear-gradient(to bottom, rgba(8,8,16,0.95), transparent); backdrop-filter: blur(12px); border-bottom: 1px solid transparent; transition: border-color 0.3s ease; }
46
+ nav.scrolled { border-bottom-color: var(--border); }
47
+ .nav-logo { font-family: var(--mono); font-size: 18px; font-weight: 500; color: var(--cyan); letter-spacing: 0.15em; text-decoration: none; }
48
+ .nav-logo span { color: var(--muted); }
49
+ .nav-links { display: flex; align-items: center; gap: 36px; list-style: none; }
50
+ .nav-links a { font-family: var(--mono); font-size: 12px; color: var(--muted); text-decoration: none; letter-spacing: 0.08em; transition: color 0.2s; }
51
+ .nav-links a:hover { color: var(--white); }
52
+ .nav-cta { font-family: var(--mono); font-size: 12px; color: var(--cyan) !important; border: 1px solid var(--border-bright); padding: 8px 18px; border-radius: 4px; transition: background 0.2s !important; }
53
+ .nav-cta:hover { background: var(--cyan-dim) !important; }
54
+
55
+ section { position: relative; z-index: 1; }
56
+
57
+ /* Hero */
58
+ .hero { min-height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 140px 48px; position: relative; overflow: hidden; }
59
+ .hero-glow { position: absolute; width: 700px; height: 700px; background: radial-gradient(circle, rgba(0,212,255,0.07) 0%, transparent 70%); top: 50%; left: 50%; transform: translate(-50%, -50%); pointer-events: none; animation: pulse-glow 4s ease-in-out infinite; }
60
+ .hero-glow2 { position: absolute; width: 400px; height: 400px; background: radial-gradient(circle, rgba(124,58,237,0.06) 0%, transparent 70%); top: 40%; left: 30%; transform: translate(-50%, -50%); pointer-events: none; animation: pulse-glow 6s ease-in-out infinite reverse; }
61
+ @keyframes pulse-glow { 0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(1); } 50% { opacity: 1; transform: translate(-50%, -50%) scale(1.1); } }
62
+
63
+ .badge { display: inline-flex; align-items: center; gap: 8px; font-family: var(--mono); font-size: 11px; letter-spacing: 0.12em; color: var(--cyan); border: 1px solid var(--border-bright); padding: 6px 14px; border-radius: 100px; background: var(--cyan-dim); margin-bottom: 32px; }
64
+ .badge-dot { width: 6px; height: 6px; background: var(--cyan); border-radius: 50%; animation: pulse-glow 2s ease-in-out infinite; }
65
+
66
+ .hero-title { font-family: var(--display); font-size: clamp(48px, 7vw, 96px); font-weight: 800; line-height: 1.0; color: var(--white); max-width: 900px; margin-bottom: 28px; letter-spacing: -0.02em; }
67
+ .hero-title .accent { color: var(--cyan); }
68
+ .hero-title .dim { color: var(--muted); }
69
+
70
+ .hero-sub { font-family: var(--mono); font-size: 16px; line-height: 1.8; color: var(--muted); max-width: 560px; margin-bottom: 48px; }
71
+
72
+ .hero-actions { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-bottom: 80px; }
73
+ .btn-primary { font-family: var(--mono); font-size: 13px; background: var(--cyan); color: #000; padding: 14px 28px; border-radius: 6px; text-decoration: none; font-weight: 500; transition: opacity 0.2s, transform 0.2s; letter-spacing: 0.05em; }
74
+ .btn-primary:hover { opacity: 0.85; transform: translateY(-1px); }
75
+ .btn-ghost { font-family: var(--mono); font-size: 13px; background: transparent; color: var(--text); padding: 14px 28px; border-radius: 6px; text-decoration: none; border: 1px solid var(--border); transition: border-color 0.2s, background 0.2s; letter-spacing: 0.05em; }
76
+ .btn-ghost:hover { border-color: var(--cyan-mid); background: var(--cyan-dim); }
77
+
78
+ .hero-terminal { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 0; max-width: 700px; width: 100%; overflow: hidden; text-align: left; }
79
+ .terminal-bar { background: var(--surface2); padding: 12px 16px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid var(--border); }
80
+ .terminal-dot { width: 12px; height: 12px; border-radius: 50%; }
81
+ .terminal-body { padding: 24px 28px; font-family: var(--mono); font-size: 13px; line-height: 2; }
82
+ .t-cmd { color: var(--cyan); }
83
+ .t-out { color: var(--muted); font-size: 12px; padding-left: 16px; }
84
+ .t-pass { color: var(--green); }
85
+ .t-warn { color: var(--amber); }
86
+ .t-step { color: var(--text); }
87
+
88
+ /* Observe animation */
89
+ .observe, .step-item, .feature-card, .provider-card, .stat-item { opacity: 0; transform: translateY(24px); transition: opacity 0.6s ease, transform 0.6s ease; }
90
+ .observe.visible, .step-item.visible, .feature-card.visible, .provider-card.visible, .stat-item.visible { opacity: 1; transform: translateY(0); }
91
+
92
+ /* Section shared */
93
+ .section-inner { max-width: 1100px; margin: 0 auto; padding: 120px 48px; }
94
+ .section-label { font-family: var(--mono); font-size: 11px; letter-spacing: 0.2em; color: var(--cyan); text-transform: uppercase; margin-bottom: 20px; }
95
+ .section-title { font-family: var(--display); font-size: clamp(36px, 5vw, 60px); font-weight: 800; color: var(--white); line-height: 1.1; letter-spacing: -0.02em; margin-bottom: 20px; }
96
+ .section-sub { font-family: var(--mono); font-size: 14px; color: var(--muted); line-height: 1.8; max-width: 560px; }
97
+
98
+ /* How it works */
99
+ .how-bg { background: linear-gradient(to bottom, transparent, var(--surface) 20%, var(--surface) 80%, transparent); }
100
+ .how-inner { max-width: 1100px; margin: 0 auto; padding: 120px 48px; }
101
+ .steps-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 2px; margin-top: 64px; background: var(--border); border-radius: 14px; overflow: hidden; }
102
+ .step-item { background: var(--surface); padding: 40px 36px; }
103
+ .step-num { font-family: var(--mono); font-size: 11px; color: var(--cyan); letter-spacing: 0.15em; margin-bottom: 20px; }
104
+ .step-icon { font-size: 32px; margin-bottom: 16px; }
105
+ .step-title { font-family: var(--display); font-size: 22px; font-weight: 700; color: var(--white); margin-bottom: 12px; }
106
+ .step-desc { font-family: var(--mono); font-size: 13px; color: var(--muted); line-height: 1.7; }
107
+ .step-tag { display: inline-block; font-family: var(--mono); font-size: 10px; color: var(--amber); border: 1px solid rgba(245,158,11,0.3); padding: 3px 8px; border-radius: 4px; margin-top: 12px; background: rgba(245,158,11,0.08); }
108
+
109
+ /* Features */
110
+ .features-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(320px, 1fr)); gap: 16px; margin-top: 64px; }
111
+ .feature-card { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 32px; transition: border-color 0.2s, transform 0.2s; }
112
+ .feature-card:hover { border-color: var(--border-bright); transform: translateY(-2px); }
113
+ .feature-icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-size: 20px; margin-bottom: 20px; }
114
+ .feature-title { font-family: var(--display); font-size: 18px; font-weight: 700; color: var(--white); margin-bottom: 10px; }
115
+ .feature-desc { font-family: var(--mono); font-size: 12px; color: var(--muted); line-height: 1.7; }
116
+
117
+ /* AI section */
118
+ .ai-section { background: var(--surface); border-top: 1px solid var(--border); border-bottom: 1px solid var(--border); }
119
+ .provider-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 16px; margin-top: 48px; }
120
+ .provider-card { background: var(--bg); border: 1px solid var(--border); border-radius: 12px; padding: 28px; transition: border-color 0.2s; }
121
+ .provider-card:hover { border-color: var(--border-bright); }
122
+ .provider-name { font-family: var(--mono); font-size: 14px; font-weight: 500; color: var(--white); margin-bottom: 8px; }
123
+ .provider-model { font-family: var(--mono); font-size: 11px; color: var(--cyan); margin-bottom: 12px; }
124
+ .provider-desc { font-family: var(--mono); font-size: 12px; color: var(--muted); line-height: 1.6; }
125
+ .provider-local { border-color: rgba(245,158,11,0.3); }
126
+ .provider-local .provider-name { color: var(--amber); }
127
+
128
+ /* Modes */
129
+ .modes-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; margin-top: 64px; }
130
+ @media(max-width:700px) { .modes-grid { grid-template-columns: 1fr; } }
131
+ .mode-card { border-radius: 14px; padding: 40px; border: 1px solid var(--border); }
132
+ .mode-solo { background: linear-gradient(135deg, var(--surface) 0%, var(--surface2) 100%); }
133
+ .mode-team { background: linear-gradient(135deg, rgba(124,58,237,0.08) 0%, var(--surface) 100%); border-color: rgba(124,58,237,0.2); }
134
+ .mode-label { font-family: var(--mono); font-size: 11px; letter-spacing: 0.15em; margin-bottom: 16px; }
135
+ .mode-title { font-family: var(--display); font-size: 28px; font-weight: 800; color: var(--white); margin-bottom: 14px; }
136
+ .mode-desc { font-family: var(--mono); font-size: 13px; color: var(--muted); line-height: 1.7; margin-bottom: 24px; }
137
+ .mode-code { background: var(--bg); border: 1px solid var(--border); border-radius: 8px; padding: 18px 20px; font-family: var(--mono); font-size: 12px; line-height: 1.9; }
138
+
139
+ /* Assertions */
140
+ .assert-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; margin-top: 48px; }
141
+ .assert-pill { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 16px 20px; font-family: var(--mono); font-size: 12px; }
142
+ .assert-type { color: var(--cyan); font-size: 11px; letter-spacing: 0.08em; margin-bottom: 6px; }
143
+ .assert-example { color: var(--muted); font-size: 11px; }
144
+
145
+ /* Install */
146
+ .install-section { background: linear-gradient(to bottom, transparent, var(--surface) 30%, var(--surface) 70%, transparent); }
147
+ .install-inner { max-width: 900px; margin: 0 auto; padding: 120px 48px; }
148
+ .code-block { background: var(--bg); border: 1px solid var(--border); border-radius: 10px; padding: 24px 28px; font-family: var(--mono); font-size: 13px; line-height: 2; margin: 24px 0; position: relative; }
149
+ .code-label { font-family: var(--mono); font-size: 10px; letter-spacing: 0.15em; color: var(--muted2); text-transform: uppercase; margin-bottom: 8px; }
150
+ .code-comment { color: var(--muted); }
151
+ .code-cmd { color: var(--cyan); }
152
+ .code-val { color: #a78bfa; }
153
+ .code-out { color: var(--muted); font-size: 11px; padding-left: 16px; }
154
+
155
+ /* FAQ */
156
+ .faq-list { display: grid; gap: 12px; margin-top: 64px; }
157
+ details { background: var(--surface); border: 1px solid var(--border); border-radius: 12px; padding: 24px 28px; }
158
+ details[open] { border-color: var(--border-bright); }
159
+ summary { font-family: var(--display); font-size: 18px; font-weight: 600; color: var(--white); list-style: none; cursor: pointer; display: flex; justify-content: space-between; align-items: center; }
160
+ summary::-webkit-details-marker { display: none; }
161
+ .faq-icon { color: var(--cyan); font-family: var(--mono); transition: transform 0.2s; }
162
+ details[open] .faq-icon { transform: rotate(45deg); }
163
+ .faq-body { margin-top: 16px; color: var(--muted); font-family: var(--mono); font-size: 13px; line-height: 1.8; }
164
+ .faq-body code { color: var(--cyan); background: var(--cyan-dim); padding: 2px 6px; border-radius: 4px; font-size: 12px; }
165
+ .faq-body strong { color: var(--text); }
166
+
167
+ /* CTA */
168
+ .cta-section { padding: 140px 48px; text-align: center; }
169
+ .cta-title { font-family: var(--display); font-size: clamp(48px, 7vw, 80px); font-weight: 800; color: var(--white); line-height: 1.05; letter-spacing: -0.02em; margin-bottom: 24px; }
170
+ .cta-sub { font-family: var(--mono); font-size: 15px; color: var(--muted); line-height: 1.8; max-width: 500px; margin: 0 auto 48px; }
171
+
172
+ /* Footer */
173
+ footer { border-top: 1px solid var(--border); padding: 48px; display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 24px; }
174
+ .footer-brand { font-family: var(--mono); font-size: 16px; font-weight: 500; color: var(--cyan); letter-spacing: 0.2em; }
175
+ .footer-links { display: flex; gap: 28px; list-style: none; }
176
+ .footer-links a { font-family: var(--mono); font-size: 12px; color: var(--muted); text-decoration: none; transition: color 0.2s; }
177
+ .footer-links a:hover { color: var(--white); }
178
+ .footer-copy { font-family: var(--mono); font-size: 11px; color: var(--muted2); }
179
+
180
+ @media(max-width:768px) {
181
+ nav { padding: 16px 24px; }
182
+ .nav-links { display: none; }
183
+ .hero { padding: 120px 24px 80px; }
184
+ .section-inner, .how-inner, .install-inner { padding: 80px 24px; }
185
+ .steps-grid { grid-template-columns: 1fr; }
186
+ footer { flex-direction: column; align-items: flex-start; padding: 32px 24px; }
187
+ }
188
+ </style>
189
+ </head>
190
+ <body>
191
+ <div class="cursor" id="cursor"></div>
192
+ <div class="cursor-ring" id="cursorRing"></div>
193
+
194
+ <!-- NAV -->
195
+ <nav id="nav">
196
+ <a href="#" class="nav-logo">SKOP<span>IX</span></a>
197
+ <ul class="nav-links">
198
+ <li><a href="#how">How it works</a></li>
199
+ <li><a href="#ai">AI providers</a></li>
200
+ <li><a href="#modes">Solo & team</a></li>
201
+ <li><a href="#install">Install</a></li>
202
+ <li><a href="#faq">FAQ</a></li>
203
+ <li><a href="https://github.com/x444dyx/skopix" class="nav-cta">GitHub →</a></li>
204
+ </ul>
205
+ </nav>
206
+
207
+ <!-- HERO -->
208
+ <section class="hero">
209
+ <div class="hero-glow"></div>
210
+ <div class="hero-glow2"></div>
211
+
212
+ <div class="badge"><span class="badge-dot"></span>Record · Replay · Regression-proof</div>
213
+
214
+ <h1 class="hero-title">
215
+ QA that works<br>
216
+ <span class="accent">like you do.</span>
217
+ </h1>
218
+
219
+ <p class="hero-sub">
220
+ Use your app normally. Skopix records every action. AI stabilises the selectors, writes the Playwright code, and replays it all — deterministically — every time.
221
+ </p>
222
+
223
+ <div class="hero-actions">
224
+ <a href="#install" class="btn-primary">Get started free →</a>
225
+ <a href="#how" class="btn-ghost">See how it works</a>
226
+ <a href="https://github.com/x444dyx/skopix" class="btn-ghost">View on GitHub</a>
227
+ </div>
228
+
229
+ <div class="hero-terminal observe">
230
+ <div class="terminal-bar">
231
+ <div class="terminal-dot" style="background:#ff5f57"></div>
232
+ <div class="terminal-dot" style="background:#febc2e"></div>
233
+ <div class="terminal-dot" style="background:#28c840"></div>
234
+ <span style="font-family:var(--mono);font-size:11px;color:var(--muted);margin-left:8px">skopix dashboard</span>
235
+ </div>
236
+ <div class="terminal-body">
237
+ <div><span class="t-cmd">$</span> SKOPIX_SECRET_KEY="..." skopix dashboard --team</div>
238
+ <div class="t-out">✔ Dashboard running at http://localhost:9000</div>
239
+ <div class="t-out">✔ Team mode active · 3 users online</div>
240
+ <div style="margin-top:12px;color:var(--muted2)">── Replaying: Create A Bar Chart ──────────────────</div>
241
+ <div><span class="t-pass">✓</span> <span class="t-step">[1/14] CLICK — Click username input field</span></div>
242
+ <div><span class="t-pass">✓</span> <span class="t-step">[2/14] TYPE — Type 'admin' into username field</span></div>
243
+ <div><span class="t-pass">✓</span> <span class="t-step">[5/14] CLICK — Click the plus icon to add chart</span></div>
244
+ <div><span class="t-pass">✓</span> <span class="t-step">[13/14] ASSERT — Chart is visible</span></div>
245
+ <div><span class="t-pass">✓</span> <span class="t-step">[14/14] ASSERT — Title attribute contains "Bar chart"</span></div>
246
+ <div style="margin-top:8px"><span class="t-pass">✔ PASSED</span> <span style="color:var(--muted)">· 14 steps · 9.4s</span></div>
247
+ </div>
248
+ </div>
249
+ </section>
250
+
251
+ <!-- HOW IT WORKS -->
252
+ <section class="how-bg" id="how">
253
+ <div class="how-inner">
254
+ <div class="section-label observe">How it works</div>
255
+ <h2 class="section-title observe">Record once.<br>Replay forever.</h2>
256
+ <p class="section-sub observe">No scripting. No selectors. Just use your app — Skopix captures everything and turns it into a stable, repeatable test.</p>
257
+
258
+ <div class="steps-grid">
259
+ <div class="step-item observe">
260
+ <div class="step-num">01 — RECORD</div>
261
+ <div class="step-icon">⏺</div>
262
+ <div class="step-title">Use your app normally</div>
263
+ <div class="step-desc">Click Record in the dashboard. A real Chrome window opens. Use your app exactly as a user would — click, type, navigate, scroll. Every action is captured in real time. Add assertions by clicking "+ Assert" in the floating toolbar — pick any element, choose what to check (text, visibility, count, attribute), done.</div>
264
+ <span class="step-tag">No code required</span>
265
+ </div>
266
+
267
+ <div class="step-item observe">
268
+ <div class="step-num">02 — AI PROCESSING</div>
269
+ <div class="step-icon">◆</div>
270
+ <div class="step-title">AI stabilises your steps</div>
271
+ <div class="step-desc">After you stop, Skopix sends your raw steps through an LLM. It rewrites fragile positional selectors into stable ones (data-testid, aria-label, semantic HTML). It generates human-readable descriptions for each step. It produces complete Playwright test code in both JavaScript and TypeScript — ready to drop into your CI pipeline.</div>
272
+ <span class="step-tag">Gemini · OpenAI · Ollama</span>
273
+ </div>
274
+
275
+ <div class="step-item observe">
276
+ <div class="step-num">03 — REPLAY</div>
277
+ <div class="step-icon">▶</div>
278
+ <div class="step-title">Run it whenever you want</div>
279
+ <div class="step-desc">Click replay from All Tests, run a suite in parallel, or trigger from CI. Skopix replays every step deterministically in a headed browser. Smart click disambiguation matches elements by their recorded coordinates when multiple match. Failed steps stop the test and tell you exactly which step and why.</div>
280
+ <span class="step-tag">Deterministic · No flakiness</span>
281
+ </div>
282
+
283
+ <div class="step-item observe">
284
+ <div class="step-num">04 — DEBUG</div>
285
+ <div class="step-icon">⏸</div>
286
+ <div class="step-title">Fix tests at any step</div>
287
+ <div class="step-desc">Click the ⏺ button on any step to set a debug point. Skopix replays up to that step automatically — navigating, logging in, filling forms — then hands control back to you in the browser. Record new steps from exactly that state. New steps get AI-processed and inserted back into your test.</div>
288
+ <span class="step-tag">No re-recording from scratch</span>
289
+ </div>
290
+
291
+ <div class="step-item observe">
292
+ <div class="step-num">05 — REUSE</div>
293
+ <div class="step-icon">♻</div>
294
+ <div class="step-title">Share setup flows</div>
295
+ <div class="step-desc">Mark any test as Reusable. Other tests can reference it as their Setup — Skopix runs the setup steps first in the same browser session, then hands off to the main test. Record your login flow once. Every other test that needs authentication just references it. No duplication, no maintenance headache.</div>
296
+ <span class="step-tag">Setup · Teardown · Reuse</span>
297
+ </div>
298
+
299
+ <div class="step-item observe">
300
+ <div class="step-num">06 — REPORT</div>
301
+ <div class="step-icon">📋</div>
302
+ <div class="step-title">Full session reports</div>
303
+ <div class="step-desc">Every replay generates a full report: two-column layout with video on the left, clickable step list on the right. Click any step to see the screenshot at that point. Sessions are stored in All Sessions. Suite runs aggregate results across all tests. Audit log tracks who ran what and when.</div>
304
+ <span class="step-tag">Video · Screenshots · HTML report</span>
305
+ </div>
306
+ </div>
307
+ </div>
308
+ </section>
309
+
310
+ <!-- ASSERTIONS -->
311
+ <section>
312
+ <div class="section-inner">
313
+ <div class="section-label observe">Assertions</div>
314
+ <h2 class="section-title observe">Assert anything.<br>Without writing code.</h2>
315
+ <p class="section-sub observe">During recording, click "+ Assert" in the toolbar, pick any element on the page, and choose what to verify. Smart auto-suggestion picks the most appropriate type based on the element.</p>
316
+
317
+ <div class="assert-grid">
318
+ <div class="assert-pill observe">
319
+ <div class="assert-type">VISIBLE</div>
320
+ <div class="assert-example">Element is present and visible on screen</div>
321
+ </div>
322
+ <div class="assert-pill observe">
323
+ <div class="assert-type">TEXT CONTAINS</div>
324
+ <div class="assert-example">Element's text content includes a substring</div>
325
+ </div>
326
+ <div class="assert-pill observe">
327
+ <div class="assert-type">TEXT EQUALS</div>
328
+ <div class="assert-example">Element's text matches exactly</div>
329
+ </div>
330
+ <div class="assert-pill observe">
331
+ <div class="assert-type">URL CONTAINS</div>
332
+ <div class="assert-example">Current page URL includes a path or query string</div>
333
+ </div>
334
+ <div class="assert-pill observe">
335
+ <div class="assert-type">ELEMENT COUNT</div>
336
+ <div class="assert-example">Exactly N elements match the selector</div>
337
+ </div>
338
+ <div class="assert-pill observe">
339
+ <div class="assert-type">ATTRIBUTE CONTAINS</div>
340
+ <div class="assert-example">title, alt, aria-label, data-* attribute includes value</div>
341
+ </div>
342
+ </div>
343
+
344
+ <div style="margin-top:48px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:32px;font-family:var(--mono);font-size:12px;line-height:1.8;max-width:700px" class="observe">
345
+ <div style="color:var(--muted2);font-size:10px;letter-spacing:0.15em;margin-bottom:16px">GENERATED PLAYWRIGHT CODE</div>
346
+ <div><span style="color:var(--purple)">// Assert chart renders</span></div>
347
+ <div><span style="color:var(--cyan)">await</span> expect(page.locator(<span style="color:#a78bfa">'.highcharts-container'</span>)).toBeVisible();</div>
348
+ <div style="margin-top:8px"><span style="color:var(--purple)">// Assert top value is correct</span></div>
349
+ <div><span style="color:var(--cyan)">await</span> expect(page.locator(<span style="color:#a78bfa">"span:has-text('60.00')"</span>)).toContainText(<span style="color:#a78bfa">'60.00'</span>);</div>
350
+ <div style="margin-top:8px"><span style="color:var(--purple)">// Assert tooltip explains SVG behaviour</span></div>
351
+ <div><span style="color:var(--cyan)">await</span> expect(page.locator(<span style="color:#a78bfa">'i.fa-question'</span>)).toHaveAttribute(<span style="color:#a78bfa">'title'</span>, <span style="color:#a78bfa">/SVG equivalent/</span>);</div>
352
+ </div>
353
+ </div>
354
+ </section>
355
+
356
+ <!-- AI PROVIDERS -->
357
+ <section class="ai-section" id="ai">
358
+ <div class="section-inner">
359
+ <div class="section-label observe">AI providers</div>
360
+ <h2 class="section-title observe">Bring your own AI.<br>Or run it locally.</h2>
361
+ <p class="section-sub observe">Skopix uses AI at one point only — processing your raw recording into stable selectors and Playwright code. Replay is fully deterministic, no LLM involved. Choose whatever fits your setup.</p>
362
+
363
+ <div class="provider-grid">
364
+ <div class="provider-card observe">
365
+ <div class="provider-name">Google Gemini</div>
366
+ <div class="provider-model">gemini-2.5-flash (default)</div>
367
+ <div class="provider-desc">Fast, affordable, excellent at code generation. The recommended default. Free tier available via Google AI Studio. Set <code style="color:var(--cyan)">GEMINI_API_KEY</code> in config.</div>
368
+ </div>
369
+ <div class="provider-card observe">
370
+ <div class="provider-name">OpenAI</div>
371
+ <div class="provider-model">gpt-4o-mini · gpt-4o</div>
372
+ <div class="provider-desc">Reliable choice if you already have OpenAI credits. gpt-4o-mini is the cost-effective option. Set <code style="color:var(--cyan)">OPENAI_API_KEY</code> in config.</div>
373
+ </div>
374
+ <div class="provider-card provider-local observe">
375
+ <div class="provider-name">🖥 Ollama (local)</div>
376
+ <div class="provider-model">llama3.1 · qwen2.5-coder · any model</div>
377
+ <div class="provider-desc">Run AI completely on your machine. No API key, no cost, no data leaving your network. Ideal for air-gapped environments or privacy-sensitive codebases. Requires <a href="https://ollama.ai" style="color:var(--amber)">Ollama</a> running locally.</div>
378
+ </div>
379
+ </div>
380
+
381
+ <div style="margin-top:48px;background:var(--bg);border:1px solid var(--border);border-radius:12px;padding:32px;max-width:860px" class="observe">
382
+ <div style="font-family:var(--mono);font-size:10px;color:var(--muted2);letter-spacing:0.15em;margin-bottom:24px">CONFIGURE VIA skopix init OR .skopix.env</div>
383
+ <div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:24px;font-family:var(--mono);font-size:12px;line-height:1.9">
384
+ <div>
385
+ <div style="color:var(--muted);margin-bottom:8px"># Gemini (default)</div>
386
+ <div style="color:var(--cyan)">SKOPIX_PROVIDER=gemini</div>
387
+ <div style="color:var(--text)">GEMINI_API_KEY=AIza...</div>
388
+ </div>
389
+ <div>
390
+ <div style="color:var(--muted);margin-bottom:8px"># OpenAI</div>
391
+ <div style="color:var(--cyan)">SKOPIX_PROVIDER=openai</div>
392
+ <div style="color:var(--text)">OPENAI_API_KEY=sk-...</div>
393
+ </div>
394
+ <div>
395
+ <div style="color:var(--muted);margin-bottom:8px"># Ollama (local)</div>
396
+ <div style="color:var(--cyan)">SKOPIX_PROVIDER=ollama</div>
397
+ <div style="color:var(--text)">OLLAMA_MODEL=llama3.1</div>
398
+ <div style="color:var(--muted);font-size:11px">OLLAMA_BASE_URL=http://localhost:11434</div>
399
+ </div>
400
+ </div>
401
+ </div>
402
+
403
+ <div style="margin-top:24px;font-family:var(--mono);font-size:12px;color:var(--muted);max-width:700px;line-height:1.7" class="observe">
404
+ <strong style="color:var(--amber)">💡 Privacy note:</strong> AI is only called when you finish a recording. Your test steps (selectors, values, URLs) are sent to the LLM to generate stable selectors and Playwright code. During replay, zero AI calls are made — it's pure deterministic browser automation. If you use Ollama, nothing ever leaves your machine.
405
+ </div>
406
+ </div>
407
+ </section>
408
+
409
+ <!-- SOLO & TEAM MODES -->
410
+ <section id="modes">
411
+ <div class="section-inner">
412
+ <div class="section-label observe">Deployment modes</div>
413
+ <h2 class="section-title observe">Solo or as a team.<br>Same install.</h2>
414
+ <p class="section-sub observe">One flag changes everything. Run locally for personal use, or flip to team mode and share with your entire QA team — no separate server software needed.</p>
415
+
416
+ <div class="modes-grid">
417
+ <div class="mode-card mode-solo observe">
418
+ <div class="mode-label" style="color:var(--cyan)">⬡ SOLO MODE</div>
419
+ <div class="mode-title">Just you</div>
420
+ <div class="mode-desc">Run the dashboard on your machine. Open at localhost:9000. No authentication, no accounts. Perfect for personal projects, freelance work, or trying Skopix out.</div>
421
+ <div class="mode-code">
422
+ <div style="color:var(--muted)">$ npm install -g skopix</div>
423
+ <div style="color:var(--muted)">$ skopix init</div>
424
+ <div style="color:var(--cyan)">$ skopix dashboard</div>
425
+ <div style="color:var(--muted);font-size:11px;margin-top:8px">→ Open http://localhost:9000</div>
426
+ </div>
427
+ </div>
428
+
429
+ <div class="mode-card mode-team observe">
430
+ <div class="mode-label" style="color:var(--purple)">⬡ TEAM MODE</div>
431
+ <div class="mode-title">You and your team</div>
432
+ <div class="mode-desc">Adds login, user accounts, roles (Admin/Tester/Viewer), audit log, and shared test libraries. Teammates connect via your IP or a tunnel like <a href="https://portix.ayteelabs.com" style="color:var(--cyan)">Portix</a>. All test data lives on the host machine.</div>
433
+ <div class="mode-code">
434
+ <div><span style="color:var(--purple)">SKOPIX_SECRET_KEY</span>=<span style="color:var(--text)">"long-random-string"</span> \</div>
435
+ <div style="padding-left:16px"><span style="color:var(--cyan)">skopix dashboard --team --host 0.0.0.0</span></div>
436
+ <div style="color:var(--muted);font-size:11px;margin-top:8px">→ Visit /setup to create the first admin</div>
437
+ <div style="color:var(--muted);font-size:11px">→ Invite teammates from the Users tab</div>
438
+ </div>
439
+ </div>
440
+ </div>
441
+
442
+ <div style="margin-top:32px;background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:32px;" class="observe">
443
+ <div style="font-family:var(--mono);font-size:11px;color:var(--muted2);letter-spacing:0.15em;margin-bottom:20px">SHARING WITH REMOTE TEAMMATES</div>
444
+ <div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(220px,1fr));gap:24px;font-family:var(--mono);font-size:12px;line-height:1.7">
445
+ <div>
446
+ <div style="color:var(--cyan);margin-bottom:6px">Same office</div>
447
+ <div style="color:var(--muted)">Teammates connect at <code style="color:var(--text)">http://your-ip:9000</code> on the same Wi-Fi. Find your IP with <code style="color:var(--text)">ipconfig getifaddr en0</code>.</div>
448
+ </div>
449
+ <div>
450
+ <div style="color:var(--cyan);margin-bottom:6px">Remote · Portix tunnel</div>
451
+ <div style="color:var(--muted)">Run <code style="color:var(--text)">portix 9000 --name skopix</code> for an instant public URL. Teammates open it from anywhere.</div>
452
+ </div>
453
+ <div>
454
+ <div style="color:var(--cyan);margin-bottom:6px">Always-on · Dedicated machine</div>
455
+ <div style="color:var(--muted)">Leave a Mac or Linux desktop running as a shared server. Recording needs a real display — Docker/cloud VMs won't work for that.</div>
456
+ </div>
457
+ </div>
458
+ </div>
459
+ </div>
460
+ </section>
461
+
462
+ <!-- INSTALL & QUICKSTART -->
463
+ <section class="install-section" id="install">
464
+ <div class="install-inner">
465
+ <div class="section-label observe">Installation</div>
466
+ <h2 class="section-title observe">Up in under<br>a minute.</h2>
467
+
468
+ <div class="code-label observe">1. INSTALL</div>
469
+ <div class="code-block observe">
470
+ <div><span class="code-cmd">$</span> npm install -g skopix</div>
471
+ <div><span class="code-cmd">$</span> npx playwright install chromium</div>
472
+ <div class="code-out">→ Installs Skopix CLI and the Chromium browser for recording and replay</div>
473
+ </div>
474
+
475
+ <div class="code-label observe">2. CONFIGURE YOUR AI PROVIDER</div>
476
+ <div class="code-block observe">
477
+ <div><span class="code-cmd">$</span> skopix init</div>
478
+ <div class="code-out">→ Choose your provider: gemini / openai / ollama</div>
479
+ <div class="code-out">→ Enter your API key (or skip for Ollama)</div>
480
+ <div class="code-out">→ Settings saved to ~/.skopix.env</div>
481
+ </div>
482
+
483
+ <div class="code-label observe">3A. SOLO MODE — JUST YOU</div>
484
+ <div class="code-block observe">
485
+ <div><span class="code-cmd">$</span> skopix dashboard</div>
486
+ <div class="code-out">→ Open http://localhost:9000 in your browser</div>
487
+ <div class="code-out">→ Click "Record test" to start your first recording</div>
488
+ </div>
489
+
490
+ <div class="code-label observe">3B. TEAM MODE — SHARE WITH YOUR TEAM</div>
491
+ <div class="code-block observe">
492
+ <div><span class="code-cmd">$</span> <span class="code-val">SKOPIX_SECRET_KEY</span>="your-long-secret" skopix dashboard --team --host 0.0.0.0</div>
493
+ <div class="code-out">→ Visit http://localhost:9000/setup to create the first admin account</div>
494
+ <div class="code-out">→ Invite teammates from the Users tab</div>
495
+ <div class="code-out">→ Share your IP or use "portix 9000" for remote access</div>
496
+ </div>
497
+
498
+ <div class="code-label observe">USING OLLAMA (LOCAL AI — NO API KEY NEEDED)</div>
499
+ <div class="code-block observe">
500
+ <div class="code-comment"># Install Ollama from https://ollama.ai, then:</div>
501
+ <div><span class="code-cmd">$</span> ollama pull llama3.1</div>
502
+ <div class="code-comment"># In .skopix.env:</div>
503
+ <div><span class="code-val">SKOPIX_PROVIDER</span>=ollama</div>
504
+ <div><span class="code-val">OLLAMA_MODEL</span>=llama3.1</div>
505
+ <div class="code-out">→ AI runs entirely on your machine. No data leaves. No cost.</div>
506
+ </div>
507
+
508
+ <div class="code-label observe">RECOMMENDED: RUN ON YOUR OWN MACHINE</div>
509
+ <div class="code-block observe">
510
+ <div class="code-comment"># Recording and replay open a real Chrome window — run on a Mac or Windows machine</div>
511
+ <div class="code-comment"># Teammates connect via browser — they don't need anything installed</div>
512
+ <div><span class="code-cmd">$</span> <span class="code-val">SKOPIX_SECRET_KEY</span>="long-random-string" skopix dashboard --team --host 0.0.0.0</div>
513
+ <div class="code-out">→ Share your IP or use "portix 9000" for remote access</div>
514
+ <div class="code-out">→ All recording, replay and debug runs on your machine with a real browser</div>
515
+ </div>
516
+ </div>
517
+ </section>
518
+
519
+ <!-- FAQ -->
520
+ <section id="faq">
521
+ <div class="section-inner">
522
+ <div class="section-label observe">FAQ</div>
523
+ <h2 class="section-title observe">Questions.</h2>
524
+
525
+ <div class="faq-list">
526
+
527
+ <details class="observe">
528
+ <summary>Do I need to write any code? <span class="faq-icon">+</span></summary>
529
+ <div class="faq-body">No. You use your app, Skopix records it. The AI generates Playwright code automatically after each recording — you can download the <code>.spec.js</code> or <code>.spec.ts</code> file if you want to run it in CI directly. But you never have to write a selector or a test script yourself.</div>
530
+ </details>
531
+
532
+ <details class="observe">
533
+ <summary>How is this different from Playwright's own recorder? <span class="faq-icon">+</span></summary>
534
+ <div class="faq-body">Playwright's recorder generates raw selectors as-is — often fragile positional ones. Skopix uses an LLM to <strong>stabilise</strong> those selectors into semantic, stable equivalents. It also adds a full dashboard, team mode, setup/teardown flows, debug recording, credential management, suite running, and session history with video. It's a complete QA platform, not just a code generator.</div>
535
+ </details>
536
+
537
+ <details class="observe">
538
+ <summary>What if a selector breaks after a UI change? <span class="faq-icon">+</span></summary>
539
+ <div class="faq-body">Open the step editor (✏ on any test), find the failing step, and update the selector directly. Or use the ⏺ debug button on that step — Skopix replays everything up to that point in a real browser, then lets you record the correct action from scratch. New steps are AI-processed and inserted automatically.</div>
540
+ </details>
541
+
542
+ <details class="observe">
543
+ <summary>How does the login/setup flow work? <span class="faq-icon">+</span></summary>
544
+ <div class="faq-body">Record your login flow once and mark it as <strong>Reusable</strong>. In any other test's editor, set that login test as the <strong>Setup</strong>. When that test runs, Skopix runs the login steps first in the same browser session — so the test starts already authenticated. No duplicating login steps across every test.</div>
545
+ </details>
546
+
547
+ <details class="observe">
548
+ <summary>Can I store credentials securely? <span class="faq-icon">+</span></summary>
549
+ <div class="faq-body">Yes. Go to Credentials in the dashboard, create a named credential set (e.g. <code>panintelligence</code>) with username and password. Attach it to a test via the step editor. During replay, Skopix substitutes the stored password into any <code>isPassword: true</code> step — so the real password is never stored in the test YAML, and teammates with different credentials can run the same test.</div>
550
+ </details>
551
+
552
+ <details class="observe">
553
+ <summary>Can I use Ollama or a local LLM? <span class="faq-icon">+</span></summary>
554
+ <div class="faq-body">Yes. Install <a href="https://ollama.ai" style="color:var(--cyan)">Ollama</a>, pull any model (<code>ollama pull llama3.1</code> or <code>ollama pull qwen2.5-coder</code>), then set <code>SKOPIX_PROVIDER=ollama</code> and <code>OLLAMA_MODEL=llama3.1</code> in your <code>.skopix.env</code>. AI runs entirely on your machine — no data leaves, no API costs, works offline. Note: smaller models may produce less stable selectors than Gemini or GPT-4o.</div>
555
+ </details>
556
+
557
+ <details class="observe">
558
+ <summary>How do remote teammates connect? <span class="faq-icon">+</span></summary>
559
+ <div class="faq-body"><strong>Same office:</strong> connect at <code>http://your-ip:9000</code> on the same network. <strong>Remote:</strong> run <code>portix 9000 --name skopix</code> (using <a href="https://portix.ayteelabs.com" style="color:var(--cyan)">Portix</a>) for an instant public URL — teammates open it from anywhere. <strong>Always-on:</strong> deploy to a cloud VM with Docker (~£4/mo at Hetzner), point your domain at it, done.</div>
560
+ </details>
561
+
562
+ <details class="observe">
563
+ <summary>Can I run tests in CI? <span class="faq-icon">+</span></summary>
564
+ <div class="faq-body">Yes — download the generated Playwright code. Every recording produces a <code>.spec.js</code> and <code>.spec.ts</code> file you can grab from the "View code" button on any test. Drop those into your existing Playwright CI setup and run with <code>npx playwright test</code>. That's the recommended CI path — the generated code is clean, stable, and has no Skopix dependency.</div>
565
+ </details>
566
+
567
+ <details class="observe">
568
+ <summary>Does replay open a real browser? <span class="faq-icon">+</span></summary>
569
+ <div class="faq-body">Yes — Skopix opens a real headed Chrome window for recording and replay. This means it needs to run on a machine with a display (your Mac, Windows PC, or Linux desktop). <strong>Docker containers don't work</strong> for recording or replay because they have no display. The recommended setup is to run Skopix on one team member's machine and have everyone else connect via their browser — no installation required on their end.</div>
570
+ </details>
571
+
572
+ <details class="observe">
573
+ <summary>How do I update Skopix? <span class="faq-icon">+</span></summary>
574
+ <div class="faq-body"><code>npm install -g skopix@latest</code> then restart the dashboard. Docker: <code>docker compose pull && docker compose up -d</code>. Your test data in <code>~/.skopix/</code> (or the mounted volume) is never touched during updates.</div>
575
+ </details>
576
+
577
+ </div>
578
+ </div>
579
+ </section>
580
+
581
+ <!-- CTA -->
582
+ <section class="cta-section">
583
+ <div class="cta-title observe">Start recording.<br><span style="color:var(--cyan)">Stop scripting.</span></div>
584
+ <p class="cta-sub observe">Free. Self-hosted. Your tests, your data, your machine. One command to get started.</p>
585
+ <div style="display:flex;gap:16px;justify-content:center;flex-wrap:wrap" class="observe">
586
+ <a href="#install" class="btn-primary">Install now →</a>
587
+ <a href="https://github.com/x444dyx/skopix" class="btn-ghost">GitHub</a>
588
+ <a href="https://portix.ayteelabs.com" class="btn-ghost">Share with Portix</a>
589
+ </div>
590
+ </section>
591
+
592
+ <!-- FOOTER -->
593
+ <footer>
594
+ <div class="footer-brand">SKOPIX</div>
595
+ <ul class="footer-links">
596
+ <li><a href="#how">How it works</a></li>
597
+ <li><a href="#ai">AI providers</a></li>
598
+ <li><a href="#install">Install</a></li>
599
+ <li><a href="#faq">FAQ</a></li>
600
+ <li><a href="https://github.com/x444dyx/skopix">GitHub</a></li>
601
+ <li><a href="https://ayteelabs.com">Aytee Labs</a></li>
602
+ </ul>
603
+ <div class="footer-copy">© 2026 Aytee Labs. Built by Adil.</div>
604
+ </footer>
605
+
606
+ <script>
607
+ const cursor = document.getElementById('cursor');
608
+ const ring = document.getElementById('cursorRing');
609
+ let mx = 0, my = 0, rx = 0, ry = 0;
610
+
611
+ document.addEventListener('mousemove', e => {
612
+ mx = e.clientX; my = e.clientY;
613
+ cursor.style.left = mx - 4 + 'px';
614
+ cursor.style.top = my - 4 + 'px';
615
+ });
616
+
617
+ function animRing() {
618
+ rx += (mx - rx - 16) * 0.15;
619
+ ry += (my - ry - 16) * 0.15;
620
+ ring.style.left = rx + 'px';
621
+ ring.style.top = ry + 'px';
622
+ requestAnimationFrame(animRing);
623
+ }
624
+ animRing();
625
+
626
+ document.querySelectorAll('a, button, details, .step-item, .feature-card, .provider-card').forEach(el => {
627
+ el.addEventListener('mouseenter', () => { cursor.classList.add('hover'); ring.classList.add('hover'); });
628
+ el.addEventListener('mouseleave', () => { cursor.classList.remove('hover'); ring.classList.remove('hover'); });
629
+ });
630
+
631
+ window.addEventListener('scroll', () => {
632
+ document.getElementById('nav').classList.toggle('scrolled', window.scrollY > 60);
633
+ });
634
+
635
+ const obs = new IntersectionObserver((entries) => {
636
+ entries.forEach((e, i) => {
637
+ if (e.isIntersecting) setTimeout(() => e.target.classList.add('visible'), i * 80);
638
+ });
639
+ }, { threshold: 0.1 });
640
+
641
+ document.querySelectorAll('.observe, .step-item, .feature-card, .provider-card, .stat-item').forEach(el => obs.observe(el));
642
+ </script>
643
+ </body>
644
+ </html>