muboboev commited on
Commit
98feb93
·
verified ·
1 Parent(s): d2036ee

Подэтап 4.3 — Self-Talk Journal

Browse files

Реализовать запись 1-минутных видео.

Хранить записи в S3 / CDN.

Добавить интерфейс просмотра прошлых записей.

components/self-talk-recorder.js ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class SelfTalkRecorder extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.shadowRoot.innerHTML = `
5
+ <style>
6
+ :host {
7
+ display: block;
8
+ }
9
+ .container {
10
+ display: flex;
11
+ flex-direction: column;
12
+ gap: 1.5rem;
13
+ }
14
+ .video-preview {
15
+ width: 100%;
16
+ height: 300px;
17
+ background: #1e293b;
18
+ border-radius: 0.5rem;
19
+ display: flex;
20
+ align-items: center;
21
+ justify-content: center;
22
+ position: relative;
23
+ overflow: hidden;
24
+ }
25
+ video {
26
+ width: 100%;
27
+ height: 100%;
28
+ object-fit: cover;
29
+ }
30
+ .placeholder {
31
+ text-align: center;
32
+ color: #64748b;
33
+ }
34
+ .controls {
35
+ display: flex;
36
+ gap: 1rem;
37
+ }
38
+ button {
39
+ flex: 1;
40
+ padding: 0.75rem;
41
+ border-radius: 0.5rem;
42
+ font-weight: 500;
43
+ display: flex;
44
+ align-items: center;
45
+ justify-content: center;
46
+ gap: 0.5rem;
47
+ cursor: pointer;
48
+ transition: all 0.2s;
49
+ }
50
+ .record-btn {
51
+ background: #7c3aed;
52
+ color: white;
53
+ border: none;
54
+ }
55
+ .record-btn:hover {
56
+ background: #6d28d9;
57
+ }
58
+ .record-btn.recording {
59
+ background: #dc2626;
60
+ animation: pulse 1.5s infinite;
61
+ }
62
+ .stop-btn {
63
+ background: #1e293b;
64
+ color: white;
65
+ border: 1px solid #334155;
66
+ }
67
+ .stop-btn:hover {
68
+ background: #334155;
69
+ }
70
+ .timer {
71
+ font-size: 1.25rem;
72
+ font-weight: 600;
73
+ color: #7c3aed;
74
+ text-align: center;
75
+ }
76
+ @keyframes pulse {
77
+ 0% { opacity: 1; }
78
+ 50% { opacity: 0.7; }
79
+ 100% { opacity: 1; }
80
+ }
81
+ </style>
82
+ <div class="container">
83
+ <h2 class="text-xl font-bold gradient-text mb-2">Record Your Self-Talk</h2>
84
+ <p class="text-slate-300 mb-4">Speak freely for 1 minute about your thoughts, feelings, or affirmations.</p>
85
+
86
+ <div class="video-preview">
87
+ <video id="videoPreview" autoplay muted></video>
88
+ <div class="placeholder" id="videoPlaceholder">
89
+ <i data-feather="video" class="w-12 h-12 mx-auto mb-2"></i>
90
+ <p>Video preview will appear here</p>
91
+ </div>
92
+ </div>
93
+
94
+ <div class="timer" id="timer">01:00</div>
95
+
96
+ <div class="controls">
97
+ <button class="record-btn" id="recordBtn">
98
+ <i data-feather="mic"></i> Start Recording
99
+ </button>
100
+ <button class="stop-btn" id="stopBtn" disabled>
101
+ <i data-feather="square"></i> Stop
102
+ </button>
103
+ </div>
104
+ </div>
105
+ `;
106
+
107
+ this.mediaRecorder = null;
108
+ this.recordedChunks = [];
109
+ this.countdownInterval = null;
110
+ this.timeLeft = 60;
111
+
112
+ this.setupEventListeners();
113
+ feather.replace();
114
+ }
115
+
116
+ setupEventListeners() {
117
+ const recordBtn = this.shadowRoot.getElementById('recordBtn');
118
+ const stopBtn = this.shadowRoot.getElementById('stopBtn');
119
+ const videoPreview = this.shadowRoot.getElementById('videoPreview');
120
+ const videoPlaceholder = this.shadowRoot.getElementById('videoPlaceholder');
121
+ const timer = this.shadowRoot.getElementById('timer');
122
+
123
+ recordBtn.addEventListener('click', async () => {
124
+ try {
125
+ const stream = await navigator.mediaDevices.getUserMedia({
126
+ video: true,
127
+ audio: true
128
+ });
129
+
130
+ videoPreview.srcObject = stream;
131
+ videoPlaceholder.style.display = 'none';
132
+ videoPreview.style.display = 'block';
133
+
134
+ this.mediaRecorder = new MediaRecorder(stream);
135
+ this.recordedChunks = [];
136
+
137
+ this.mediaRecorder.ondataavailable = event => {
138
+ if (event.data.size > 0) {
139
+ this.recordedChunks.push(event.data);
140
+ }
141
+ };
142
+
143
+ this.mediaRecorder.onstop = () => {
144
+ const blob = new Blob(this.recordedChunks, { type: 'video/webm' });
145
+ this.saveRecording(blob);
146
+ };
147
+
148
+ this.mediaRecorder.start(100);
149
+ recordBtn.classList.add('recording');
150
+ recordBtn.disabled = true;
151
+ stopBtn.disabled = false;
152
+
153
+ // Start countdown
154
+ this.timeLeft = 60;
155
+ this.countdownInterval = setInterval(() => {
156
+ this.timeLeft--;
157
+ const minutes = Math.floor(this.timeLeft / 60);
158
+ const seconds = this.timeLeft % 60;
159
+ timer.textContent = `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
160
+
161
+ if (this.timeLeft <= 0) {
162
+ this.stopRecording();
163
+ }
164
+ }, 1000);
165
+ } catch (error) {
166
+ console.error('Error accessing media devices:', error);
167
+ alert('Could not access camera/microphone. Please check permissions.');
168
+ }
169
+ });
170
+
171
+ stopBtn.addEventListener('click', () => this.stopRecording());
172
+ }
173
+
174
+ stopRecording() {
175
+ if (this.countdownInterval) {
176
+ clearInterval(this.countdownInterval);
177
+ this.countdownInterval = null;
178
+ }
179
+
180
+ if (this.mediaRecorder && this.mediaRecorder.state !== 'inactive') {
181
+ this.mediaRecorder.stop();
182
+
183
+ const videoPreview = this.shadowRoot.getElementById('videoPreview');
184
+ const stream = videoPreview.srcObject;
185
+ stream.getTracks().forEach(track => track.stop());
186
+
187
+ this.shadowRoot.getElementById('recordBtn').classList.remove('recording');
188
+ this.shadowRoot.getElementById('recordBtn').disabled = false;
189
+ this.shadowRoot.getElementById('stopBtn').disabled = true;
190
+ this.shadowRoot.getElementById('timer').textContent = '01:00';
191
+ }
192
+ }
193
+
194
+ async saveRecording(blob) {
195
+ // In a real app, this would upload to S3/CDN via your backend
196
+ console.log('Recording saved:', blob);
197
+ alert('Recording saved successfully! It will appear in your journal entries.');
198
+
199
+ // Simulate adding to entries list
200
+ const entriesContainer = document.getElementById('entriesContainer');
201
+ if (entriesContainer) {
202
+ const newEntry = {
203
+ id: Date.now().toString(),
204
+ date: new Date().toISOString().split('T')[0],
205
+ duration: '1:00',
206
+ thumbnail: 'http://static.photos/people/320x240/' + Math.floor(Math.random() * 10),
207
+ mood: 'New'
208
+ };
209
+
210
+ entriesContainer.insertAdjacentHTML('afterbegin', `
211
+ <div class="glass-card entry-card p-6 cursor-pointer">
212
+ <div class="relative pb-[56.25%] mb-4 overflow-hidden rounded-lg">
213
+ <img src="${newEntry.thumbnail}" alt="Entry thumbnail" class="absolute h-full w-full object-cover">
214
+ <div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-3">
215
+ <div class="text-white font-medium">${newEntry.duration}</div>
216
+ </div>
217
+ </div>
218
+ <div class="flex justify-between items-center">
219
+ <div>
220
+ <h3 class="font-bold">${newEntry.date}</h3>
221
+ <p class="text-sm text-slate-400">${newEntry.mood}</p>
222
+ </div>
223
+ <button class="text-indigo-400 hover:text-indigo-300">
224
+ <i data-feather="play"></i>
225
+ </button>
226
+ </div>
227
+ </div>
228
+ `);
229
+
230
+ feather.replace();
231
+ }
232
+ }
233
+ }
234
+
235
+ customElements.define('self-talk-recorder', SelfTalkRecorder);
data-architecture.html CHANGED
@@ -56,6 +56,7 @@
56
  <a href="auth.html" class="hover:text-indigo-400 transition-colors">Login</a>
57
  <a href="select-role.html" class="hover:text-indigo-400 transition-colors">Roles</a>
58
  <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
 
59
  </div>
60
  </nav>
61
 
 
56
  <a href="auth.html" class="hover:text-indigo-400 transition-colors">Login</a>
57
  <a href="select-role.html" class="hover:text-indigo-400 transition-colors">Roles</a>
58
  <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
59
+ <a href="self-talk-journal.html" class="hover:text-indigo-400 transition-colors">Self-Talk</a>
60
  </div>
61
  </nav>
62
 
index.html CHANGED
@@ -54,6 +54,7 @@
54
  <a href="auth.html" class="hover:text-indigo-400 transition-colors">Login</a>
55
  <a href="select-role.html" class="hover:text-indigo-400 transition-colors">Roles</a>
56
  <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
 
57
  </div>
58
  <button class="md:hidden">
59
  <i data-feather="menu"></i>
 
54
  <a href="auth.html" class="hover:text-indigo-400 transition-colors">Login</a>
55
  <a href="select-role.html" class="hover:text-indigo-400 transition-colors">Roles</a>
56
  <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
57
+ <a href="self-talk-journal.html" class="hover:text-indigo-400 transition-colors">Self-Talk</a>
58
  </div>
59
  <button class="md:hidden">
60
  <i data-feather="menu"></i>
learning-engine.html CHANGED
@@ -63,6 +63,7 @@
63
  <a href="auth.html" class="hover:text-indigo-400 transition-colors">Login</a>
64
  <a href="select-role.html" class="hover:text-indigo-400 transition-colors">Roles</a>
65
  <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
 
66
  </div>
67
  </nav>
68
 
 
63
  <a href="auth.html" class="hover:text-indigo-400 transition-colors">Login</a>
64
  <a href="select-role.html" class="hover:text-indigo-400 transition-colors">Roles</a>
65
  <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
66
+ <a href="self-talk-journal.html" class="hover:text-indigo-400 transition-colors">Self-Talk</a>
67
  </div>
68
  </nav>
69
 
progress-report.html CHANGED
@@ -62,7 +62,8 @@
62
  <a href="data-architecture.html" class="hover:text-indigo-400 transition-colors">Data</a>
63
  <a href="learning-engine.html" class="hover:text-indigo-400 transition-colors">API</a>
64
  <a href="progress-report.html" class="text-indigo-400">Progress</a>
65
- </div>
 
66
  </nav>
67
 
68
  <main class="container mx-auto px-4 py-16">
 
62
  <a href="data-architecture.html" class="hover:text-indigo-400 transition-colors">Data</a>
63
  <a href="learning-engine.html" class="hover:text-indigo-400 transition-colors">API</a>
64
  <a href="progress-report.html" class="text-indigo-400">Progress</a>
65
+ <a href="self-talk-journal.html" class="hover:text-indigo-400 transition-colors">Self-Talk</a>
66
+ </div>
67
  </nav>
68
 
69
  <main class="container mx-auto px-4 py-16">
self-talk-journal.html ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Self-Talk Journal | QuantumCode</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://unpkg.com/feather-icons"></script>
10
+ <script src="components/self-talk-recorder.js"></script>
11
+ <style>
12
+ @import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
13
+
14
+ body {
15
+ font-family: 'Space Grotesk', sans-serif;
16
+ background-color: #0f172a;
17
+ color: #e2e8f0;
18
+ }
19
+
20
+ .gradient-text {
21
+ background: linear-gradient(90deg, #7c3aed 0%, #2563eb 100%);
22
+ -webkit-background-clip: text;
23
+ background-clip: text;
24
+ color: transparent;
25
+ }
26
+
27
+ .glass-card {
28
+ background: rgba(15, 23, 42, 0.7);
29
+ backdrop-filter: blur(10px);
30
+ border: 1px solid rgba(255, 255, 255, 0.1);
31
+ border-radius: 1rem;
32
+ }
33
+
34
+ .entry-card {
35
+ transition: all 0.3s ease;
36
+ border: 1px solid transparent;
37
+ }
38
+
39
+ .entry-card:hover {
40
+ transform: translateY(-3px);
41
+ border-color: #7c3aed;
42
+ }
43
+ </style>
44
+ </head>
45
+ <body class="min-h-screen">
46
+ <nav class="px-6 py-4 flex justify-between items-center">
47
+ <div class="flex items-center space-x-2">
48
+ <i data-feather="cpu" class="text-indigo-500"></i>
49
+ <span class="text-xl font-bold gradient-text">QuantumCode</span>
50
+ </div>
51
+ <div class="flex items-center space-x-2">
52
+ <a href="i18n-setup.html" class="flex items-center text-sm hover:text-indigo-400 transition-colors">
53
+ <i data-feather="globe" class="w-4 h-4 mr-1"></i>
54
+ <span id="currentLang">EN</span>
55
+ </a>
56
+ </div>
57
+ <div class="hidden md:flex space-x-8">
58
+ <a href="index.html" class="hover:text-indigo-400 transition-colors">Home</a>
59
+ <a href="progress-report.html" class="hover:text-indigo-400 transition-colors">Progress</a>
60
+ <a href="self-talk-journal.html" class="text-indigo-400">Self-Talk Journal</a>
61
+ </div>
62
+ </nav>
63
+
64
+ <main class="container mx-auto px-4 py-16">
65
+ <section class="max-w-6xl mx-auto mb-12">
66
+ <h1 class="text-4xl md:text-5xl font-bold mb-6">
67
+ <span class="gradient-text">Self-Talk Journal</span>
68
+ </h1>
69
+ <p class="text-xl text-slate-300 mb-10">
70
+ Record and reflect on your daily self-talk patterns to build confidence and awareness.
71
+ </p>
72
+
73
+ <div class="glass-card p-8 mb-10">
74
+ <self-talk-recorder></self-talk-recorder>
75
+ </div>
76
+
77
+ <h2 class="text-3xl font-bold gradient-text mb-6">Past Entries</h2>
78
+ <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-6" id="entriesContainer">
79
+ <!-- Entries will be loaded here -->
80
+ </div>
81
+ </section>
82
+ </main>
83
+
84
+ <script>
85
+ // Load entries from storage
86
+ async function loadEntries() {
87
+ // In a real app, this would fetch from your backend/API
88
+ const mockEntries = [
89
+ {
90
+ id: '1',
91
+ date: '2023-06-15',
92
+ duration: '1:02',
93
+ thumbnail: 'http://static.photos/people/320x240/1',
94
+ mood: 'Confident'
95
+ },
96
+ {
97
+ id: '2',
98
+ date: '2023-06-14',
99
+ duration: '0:58',
100
+ thumbnail: 'http://static.photos/people/320x240/2',
101
+ mood: 'Thoughtful'
102
+ },
103
+ {
104
+ id: '3',
105
+ date: '2023-06-13',
106
+ duration: '1:05',
107
+ thumbnail: 'http://static.photos/people/320x240/3',
108
+ mood: 'Nervous'
109
+ }
110
+ ];
111
+
112
+ const container = document.getElementById('entriesContainer');
113
+ container.innerHTML = mockEntries.map(entry => `
114
+ <div class="glass-card entry-card p-6 cursor-pointer">
115
+ <div class="relative pb-[56.25%] mb-4 overflow-hidden rounded-lg">
116
+ <img src="${entry.thumbnail}" alt="Entry thumbnail" class="absolute h-full w-full object-cover">
117
+ <div class="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/80 to-transparent p-3">
118
+ <div class="text-white font-medium">${entry.duration}</div>
119
+ </div>
120
+ </div>
121
+ <div class="flex justify-between items-center">
122
+ <div>
123
+ <h3 class="font-bold">${entry.date}</h3>
124
+ <p class="text-sm text-slate-400">${entry.mood}</p>
125
+ </div>
126
+ <button class="text-indigo-400 hover:text-indigo-300">
127
+ <i data-feather="play"></i>
128
+ </button>
129
+ </div>
130
+ </div>
131
+ `).join('');
132
+
133
+ feather.replace();
134
+ }
135
+
136
+ loadEntries();
137
+
138
+ // Set initial language
139
+ document.getElementById('currentLang').textContent =
140
+ document.cookie.match('(^|;)\\s*NEXT_LOCALE\\s*=\\s*([^;]+)')?.pop() || 'EN';
141
+
142
+ feather.replace();
143
+ </script>
144
+ </body>
145
+ </html>