Files
Mastermind/src/components/views/OnboardingView.js
T

354 lines
11 KiB
JavaScript

import { html, css, LitElement } from '../../assets/lit-core-2.7.4.min.js';
export class OnboardingView extends LitElement {
static styles = css`
* {
font-family: var(--font);
cursor: default;
user-select: none;
margin: 0;
padding: 0;
box-sizing: border-box;
}
:host {
display: block;
height: 100%;
width: 100%;
position: fixed;
top: 0;
left: 0;
overflow: hidden;
}
.onboarding {
width: 100%;
height: 100%;
position: relative;
display: flex;
align-items: center;
justify-content: center;
border-radius: 12px;
border: 1px solid rgba(0, 0, 0, 0.08);
overflow: hidden;
background: #f0f0f0;
}
canvas.aurora {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 0;
}
canvas.dither {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
opacity: 0.12;
mix-blend-mode: overlay;
pointer-events: none;
image-rendering: pixelated;
}
.slide {
position: relative;
z-index: 2;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 400px;
padding: var(--space-xl);
gap: var(--space-md);
}
.slide-title {
font-size: 28px;
font-weight: 600;
color: #111111;
line-height: 1.2;
}
.slide-text {
font-size: 13px;
line-height: 1.5;
color: #666666;
}
.context-input {
width: 100%;
min-height: 120px;
padding: 12px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 8px;
background: rgba(255, 255, 255, 0.7);
backdrop-filter: blur(8px);
color: #111111;
font-size: 13px;
font-family: var(--font);
line-height: 1.5;
resize: vertical;
text-align: left;
}
.context-input::placeholder {
color: #999999;
}
.context-input:focus {
outline: none;
border-color: rgba(0, 0, 0, 0.3);
}
.actions {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
margin-top: 8px;
}
.btn-primary {
background: #111111;
border: none;
color: #ffffff;
padding: 10px 32px;
border-radius: 8px;
font-size: 13px;
font-weight: 500;
cursor: pointer;
transition: opacity 0.15s;
}
.btn-primary:hover {
opacity: 0.85;
}
.btn-back {
background: none;
border: none;
color: #888888;
font-size: 11px;
cursor: pointer;
padding: 4px 8px;
}
.btn-back:hover {
color: #555555;
}
`;
static properties = {
currentSlide: { type: Number },
contextText: { type: String },
onComplete: { type: Function },
};
constructor() {
super();
this.currentSlide = 0;
this.contextText = '';
this.onComplete = () => {};
this._animId = null;
this._time = 0;
}
firstUpdated() {
this._startAurora();
this._drawDither();
}
disconnectedCallback() {
super.disconnectedCallback();
if (this._animId) cancelAnimationFrame(this._animId);
}
_drawDither() {
const canvas = this.shadowRoot.querySelector('canvas.dither');
if (!canvas) return;
const blockSize = 5;
const cols = Math.ceil(canvas.offsetWidth / blockSize);
const rows = Math.ceil(canvas.offsetHeight / blockSize);
canvas.width = cols;
canvas.height = rows;
const ctx = canvas.getContext('2d');
const img = ctx.createImageData(cols, rows);
for (let i = 0; i < img.data.length; i += 4) {
const v = Math.random() > 0.5 ? 255 : 0;
img.data[i] = v;
img.data[i + 1] = v;
img.data[i + 2] = v;
img.data[i + 3] = 255;
}
ctx.putImageData(img, 0, 0);
}
_startAurora() {
const canvas = this.shadowRoot.querySelector('canvas.aurora');
if (!canvas) return;
const ctx = canvas.getContext('2d');
const scale = 0.35;
const resize = () => {
canvas.width = Math.floor(canvas.offsetWidth * scale);
canvas.height = Math.floor(canvas.offsetHeight * scale);
};
resize();
const blobs = [
{ parts: [
{ ox: 0, oy: 0, r: 1.0 },
{ ox: 0.22, oy: 0.1, r: 0.85 },
{ ox: 0.11, oy: 0.05, r: 0.5 },
], color: [180, 200, 230], x: 0.15, y: 0.2, vx: 0.35, vy: 0.25, phase: 0 },
{ parts: [
{ ox: 0, oy: 0, r: 0.95 },
{ ox: 0.18, oy: -0.08, r: 0.75 },
{ ox: 0.09, oy: -0.04, r: 0.4 },
], color: [190, 180, 220], x: 0.75, y: 0.2, vx: -0.3, vy: 0.35, phase: 1.2 },
{ parts: [
{ ox: 0, oy: 0, r: 0.9 },
{ ox: 0.24, oy: 0.12, r: 0.9 },
{ ox: 0.12, oy: 0.06, r: 0.35 },
], color: [210, 195, 215], x: 0.5, y: 0.65, vx: 0.25, vy: -0.3, phase: 2.4 },
{ parts: [
{ ox: 0, oy: 0, r: 0.8 },
{ ox: -0.15, oy: 0.18, r: 0.7 },
{ ox: -0.07, oy: 0.09, r: 0.45 },
], color: [175, 210, 210], x: 0.1, y: 0.75, vx: 0.4, vy: 0.2, phase: 3.6 },
{ parts: [
{ ox: 0, oy: 0, r: 0.75 },
{ ox: 0.12, oy: -0.15, r: 0.65 },
{ ox: 0.06, oy: -0.07, r: 0.35 },
], color: [220, 210, 195], x: 0.85, y: 0.55, vx: -0.28, vy: -0.32, phase: 4.8 },
{ parts: [
{ ox: 0, oy: 0, r: 0.95 },
{ ox: -0.2, oy: -0.12, r: 0.75 },
{ ox: -0.1, oy: -0.06, r: 0.4 },
], color: [170, 190, 225], x: 0.6, y: 0.1, vx: -0.2, vy: 0.38, phase: 6.0 },
{ parts: [
{ ox: 0, oy: 0, r: 0.85 },
{ ox: 0.17, oy: 0.15, r: 0.75 },
{ ox: 0.08, oy: 0.07, r: 0.35 },
], color: [200, 190, 220], x: 0.35, y: 0.4, vx: 0.32, vy: -0.22, phase: 7.2 },
{ parts: [
{ ox: 0, oy: 0, r: 0.75 },
{ ox: -0.13, oy: 0.18, r: 0.65 },
{ ox: -0.06, oy: 0.1, r: 0.4 },
], color: [215, 205, 200], x: 0.9, y: 0.85, vx: -0.35, vy: -0.25, phase: 8.4 },
{ parts: [
{ ox: 0, oy: 0, r: 0.7 },
{ ox: 0.16, oy: -0.1, r: 0.6 },
{ ox: 0.08, oy: -0.05, r: 0.35 },
], color: [185, 210, 205], x: 0.45, y: 0.9, vx: 0.22, vy: -0.4, phase: 9.6 },
];
const baseRadius = 0.32;
const draw = () => {
this._time += 0.012;
const w = canvas.width;
const h = canvas.height;
const dim = Math.min(w, h);
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, w, h);
for (const blob of blobs) {
const t = this._time;
const cx = (blob.x + Math.sin(t * blob.vx + blob.phase) * 0.22) * w;
const cy = (blob.y + Math.cos(t * blob.vy + blob.phase * 0.7) * 0.22) * h;
for (const part of blob.parts) {
const wobble = Math.sin(t * 2.5 + part.ox * 25 + blob.phase) * 0.02;
const px = cx + (part.ox + wobble) * dim;
const py = cy + (part.oy + wobble * 0.7) * dim;
const pr = part.r * baseRadius * dim;
const grad = ctx.createRadialGradient(px, py, 0, px, py, pr);
grad.addColorStop(0, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0.55)`);
grad.addColorStop(0.4, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0.3)`);
grad.addColorStop(0.7, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0.1)`);
grad.addColorStop(1, `rgba(${blob.color[0]}, ${blob.color[1]}, ${blob.color[2]}, 0)`);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
}
}
this._animId = requestAnimationFrame(draw);
};
draw();
}
handleContextInput(e) {
this.contextText = e.target.value;
}
async completeOnboarding() {
if (this.contextText.trim()) {
await cheatingDaddy.storage.updatePreference('customPrompt', this.contextText.trim());
}
await cheatingDaddy.storage.updateConfig('onboarded', true);
this.onComplete();
}
renderSlide() {
if (this.currentSlide === 0) {
return html`
<div class="slide">
<div class="slide-title">Mastermind</div>
<div class="slide-text">Real-time AI that listens, watches, and helps during interviews, meetings, and exams.</div>
<div class="actions">
<button class="btn-primary" @click=${() => { this.currentSlide = 1; }}>Continue</button>
</div>
</div>
`;
}
return html`
<div class="slide">
<div class="slide-title">Add context</div>
<div class="slide-text">Paste your resume or any info the AI should know. You can skip this and add it later.</div>
<textarea
class="context-input"
placeholder="Resume, job description, notes..."
.value=${this.contextText}
@input=${this.handleContextInput}
></textarea>
<div class="actions">
<button class="btn-primary" @click=${this.completeOnboarding}>Get Started</button>
<button class="btn-back" @click=${() => { this.currentSlide = 0; }}>Back</button>
</div>
</div>
`;
}
render() {
return html`
<div class="onboarding">
<canvas class="aurora"></canvas>
<canvas class="dither"></canvas>
${this.renderSlide()}
</div>
`;
}
}
customElements.define('onboarding-view', OnboardingView);