11<template >
2- <div id =" box" style =" text-align : center ;" >
2+ <div id =" box" style =" height : 100 vh ; overflow : hidden ;" >
33 <Toolbar v-if =" !fromRuxailab" />
4- <v-container class =" mt-4" style =" max-height : calc (100vh - 80px ); overflow : hidden ;" >
5- <v-row justify =" center" style =" height : 100% ;" >
6- <!-- Instructions Card -->
7- <v-col cols =" 12" class =" text-center" >
8- <v-alert type =" info" outlined prominent class =" mx-auto py-2"
9- style =" max-width : 600px ; font-size : 0.85rem ;" >
10- <div class =" d-flex align-center" >
11- <v-icon medium left >mdi-information</v-icon >
12- <div class =" text-left" >
13- <strong >Camera Setup</strong >
14- <p class =" mb-0 mt-1" style =" font-size : 0.8rem ;" >Position your face within the guide.
15- Ensure both eyes are visible.</p >
4+
5+ <!-- Camera Selection Modal -->
6+ <v-dialog v-model =" showCameraModal" max-width =" 500" >
7+ <v-card class =" blue-bg" >
8+ <v-card-title class =" d-flex justify-center white--text pb-4 pt-6" >
9+ <v-icon color =" white" left size =" 32" >mdi-camera</v-icon >
10+ <span style =" font-size : 20px ;" >Select Camera</span >
11+ </v-card-title >
12+ <v-card-text class =" px-6 pb-2" >
13+ <p class =" white--text text-center mb-4" style =" font-size : 14px ;" >
14+ If you don't see your face correctly, please select the correct camera from the list below.
15+ </p >
16+ <v-select
17+ v-model =" selectedMediaDevice"
18+ :items =" mediaDevices"
19+ item-text =" label"
20+ item-value =" deviceId"
21+ label =" Available Cameras"
22+ outlined
23+ dark
24+ color =" #FF425A"
25+ item-color =" #FF425A"
26+ prepend-inner-icon =" mdi-camera"
27+ background-color =" rgba(255, 255, 255, 0.1)"
28+ ></v-select >
29+ </v-card-text >
30+ <v-card-actions class =" justify-center px-6 pb-6" >
31+ <v-btn color =" #FF425A" dark block large @click =" showCameraModal = false" >
32+ <v-icon left >mdi-check</v-icon >
33+ Close
34+ </v-btn >
35+ </v-card-actions >
36+ </v-card >
37+ </v-dialog >
38+
39+ <v-container fluid style =" height : calc (100vh - 64px ); overflow-y : auto ; overflow-x : hidden ;" >
40+ <v-row justify =" center" align =" center" style =" min-height : 100% ;" >
41+ <v-col cols =" 12" md =" 10" lg =" 8" xl =" 6" >
42+ <v-stepper v-model =" setupStep" elevation =" 0" class =" mx-auto compact-stepper" >
43+ <v-stepper-header >
44+ <v-stepper-step :complete =" setupStep > 1" step =" 1" color =" #FF425A" >
45+ Camera Setup
46+ </v-stepper-step >
47+ <v-divider ></v-divider >
48+ <v-stepper-step :complete =" setupStep > 2" step =" 2" color =" #FF425A" >
49+ Preview & Calibration
50+ </v-stepper-step >
51+ </v-stepper-header >
52+
53+ <v-stepper-items >
54+ <!-- Step 1: Instructions -->
55+ <v-stepper-content step =" 1" class =" compact-content" >
56+ <v-card flat >
57+ <v-card-text class =" text-center py-2" >
58+ <div class =" mb-2" >
59+ <v-icon size =" 64" color =" #FF425A" >mdi-camera-iris</v-icon >
60+ </div >
61+ <v-alert color =" #002D51" dark dense class =" mb-3" >
62+ <h4 class =" mb-2 text-center" >What will happen:</h4 >
63+ <div class =" text-left mx-auto" style =" max-width : 400px ; font-size : 13px ;" >
64+ <ul class =" compact-list" >
65+ <li >The system will request camera permission</li >
66+ <li >Your webcam image will appear with a face guide</li >
67+ <li >Position your face inside the mask overlay</li >
68+ <li >Make sure both eyes are clearly visible</li >
69+ </ul >
70+ </div >
71+ </v-alert >
72+ <v-alert color =" #FF425A" dark dense class =" mx-auto" style =" max-width : 400px ; font-size : 12px ;" >
73+ <strong >Important:</strong > Please allow camera access when prompted.
74+ </v-alert >
75+ </v-card-text >
76+ </v-card >
77+ <div class =" text-center pb-2" >
78+ <v-btn color =" #FF425A" dark @click =" startCameraSetup" >
79+ <v-icon left >mdi-arrow-right</v-icon >
80+ Continue
81+ </v-btn >
1682 </div >
17- </div >
18- </v-alert >
19- </v-col >
20-
21- <!-- Camera Selection -->
22- <v-col cols =" 12" lg =" 8" md =" 10" class =" px-6 py-1" >
23- <v-select v-model =" selectedMediaDevice" :items =" mediaDevices" item-text =" label"
24- item-value =" deviceId" label =" Select Camera" outlined dense
25- prepend-inner-icon =" mdi-camera" ></v-select >
26- </v-col >
27-
28- <!-- Blink Threshold Configuration -->
29- <v-col v-if =" !fromRuxailab" cols =" 12" lg =" 8" md =" 10" class =" py-1" >
30- <BlinkTresholdCard />
31- </v-col >
32-
33- <!-- Camera Preview -->
34- <v-col cols =" 12" lg =" 8" md =" 10" class =" py-1" >
35- <v-card outlined class =" camera-preview-card" style =" height : 100% ;" >
36- <v-card-title class =" justify-center py-2" style =" font-size : 0.95rem ;" >
37- <v-icon small left >mdi-camera-iris</v-icon >
38- Camera Preview
39- </v-card-title >
40- <v-card-text class =" pa-2" >
41- <div v-if =" isModelLoaded" class =" camera-wrapper" >
42- <video id =" video-tag" autoplay playsinline />
43- <canvas id =" canvas" />
44- <v-img v-if =" isCameraOn" class =" mask" src =" @/assets/mask_desktop.svg" />
45- </div >
46- <div v-else class =" loading-container" style =" min-height : 200px ;" >
47- <v-progress-circular :size =" 40" :width =" 6" color =" green"
48- indeterminate ></v-progress-circular >
49- <h4 class =" mt-2" >Loading face detection model...</h4 >
50- </div >
51- </v-card-text >
52- <v-card-actions class =" justify-center pb-2" >
53- <v-btn class =" calibration-btn" large dark color =" green" :disabled =" !isCameraOn"
54- @click =" goToCalibRecord()" >
55- <v-icon small left >mdi-play</v-icon >
56- Start Calibration
57- </v-btn >
58- </v-card-actions >
59- </v-card >
83+ </v-stepper-content >
84+
85+ <!-- Step 2: Camera Preview -->
86+ <v-stepper-content step =" 2" class =" compact-content" >
87+ <v-card flat >
88+ <!-- Blink Threshold Configuration -->
89+ <v-card-text v-if =" !fromRuxailab" class =" pb-1 pt-2 text-center" >
90+ <BlinkTresholdCard />
91+ </v-card-text >
92+
93+ <!-- Camera Preview -->
94+ <v-card-text class =" pa-2 text-center" >
95+ <div class =" d-flex justify-center mb-2" >
96+ <v-btn x-small outlined color =" #002D51" @click =" showCameraModal = true" >
97+ <v-icon left x-small >mdi-help-circle</v-icon >
98+ Camera Help
99+ </v-btn >
100+ </div >
101+ <div v-if =" isModelLoaded" class =" camera-wrapper mx-auto" style =" max-height : 400px ;" >
102+ <video id =" video-tag" autoplay playsinline />
103+ <canvas id =" canvas" />
104+ <v-img v-if =" isCameraOn" class =" mask" src =" @/assets/mask_desktop.svg" />
105+ </div >
106+ <div v-else class =" loading-container" style =" min-height : 300px ;" >
107+ <v-progress-circular :size =" 50" :width =" 6" color =" #FF425A"
108+ indeterminate ></v-progress-circular >
109+ <h4 class =" mt-3" >Loading face detection model...</h4 >
110+ </div >
111+ </v-card-text >
112+
113+ <v-card-actions class =" justify-center py-2" >
114+ <v-btn text @click =" setupStep = 1" class =" mr-2" >
115+ <v-icon left >mdi-arrow-left</v-icon >
116+ Back
117+ </v-btn >
118+ <v-btn color =" #FF425A" dark :disabled =" !isCameraOn" @click =" goToCalibRecord()" >
119+ <v-icon left >mdi-play</v-icon >
120+ Start Calibration
121+ </v-btn >
122+ </v-card-actions >
123+ </v-card >
124+ </v-stepper-content >
125+ </v-stepper-items >
126+ </v-stepper >
60127 </v-col >
61128 </v-row >
62129 </v-container >
@@ -83,7 +150,9 @@ export default {
83150 video: null ,
84151 fromRuxailab: false ,
85152 mediaDevices: [],
86- selectedMediaDevice: null
153+ selectedMediaDevice: null ,
154+ setupStep: 1 ,
155+ showCameraModal: false ,
87156 };
88157 },
89158 computed: {
@@ -121,10 +190,13 @@ export default {
121190 },
122191 },
123192 mounted () {
124- this .setupCamera ()
125193 this .verifyFromRuxailab ()
126194 },
127195 methods: {
196+ startCameraSetup () {
197+ this .setupStep = 2 ;
198+ this .setupCamera ();
199+ },
128200 async setupCamera () {
129201 // Load the faceLandmarksDetection model assets.
130202 const model = await faceLandmarksDetection .load (
@@ -334,52 +406,76 @@ export default {
334406 </script >
335407
336408<style scoped>
337- .loading-container {
338- text-align : center ;
339- margin-top : 8 rem ;
409+ /* Ruxailab Color Palette */
410+ .coral-bg {
411+ background-color : #FF425A !important ;
340412}
341413
342- .centered-canvas {
343- left : 50% ;
344- top : 50% ;
345- transform : translate (-50% , -50% );
414+ .blue-bg {
415+ background-color : #002D51 !important ;
346416}
347417
348- .calibration-btn {
349- font-weight : 600 ;
350- letter-spacing : 0.5px ;
351- text-transform : none ;
418+ /* Compact Stepper Styles */
419+ .compact-stepper {
420+ max-height : calc (100vh - 100px );
352421}
353422
354- .camera-preview-card {
355- border : 2px solid #e0e0e0 ;
356- border-radius : 12px ;
423+ .compact-content {
424+ padding : 8px 16px !important ;
425+ }
426+
427+ .compact-list {
428+ margin : 0 ;
429+ padding-left : 20px ;
430+ }
431+
432+ .compact-list li {
433+ margin-bottom : 4px ;
434+ }
435+
436+ .v-stepper__header {
437+ box-shadow : none !important ;
438+ }
439+
440+ .loading-container {
441+ display : flex ;
442+ flex-direction : column ;
443+ align-items : center ;
444+ justify-content : center ;
445+ color : #555 ;
357446}
358447
359448.camera-wrapper {
360449 position : relative ;
361- width : 500px ;
362- height : 400px ;
450+ width : 100% ;
451+ max-width : 500px ;
452+ height : auto ;
453+ aspect-ratio : 5 /4 ;
363454 margin : 0 auto ;
455+ border : 5px solid #FF425A ;
456+ border-radius : 12px ;
457+ overflow : hidden ;
364458}
365459
366460#video-tag ,
367461#canvas ,
368462.mask {
369463 position : absolute ;
370464 inset : 0 ;
371- width : 500 px ;
372- height : 400 px ;
465+ width : 100 % ;
466+ height : 100 % ;
373467 object-fit : cover ;
374468}
375469
470+ #video-tag {
471+ transform : scaleX (-1 );
472+ }
473+
474+ #canvas {
475+ transform : scaleX (-1 );
476+ }
376477
377- .loading-container {
378- display : flex ;
379- flex-direction : column ;
380- align-items : center ;
381- justify-content : center ;
382- min-height : 400px ;
383- color : #555 ;
478+ .mask {
479+ pointer-events : none ;
384480}
385481 </style >
0 commit comments