PageSourceSearch

https://elviajedelili.netlify.app/idevices/relate/relate.js

js elviajedelili.netlify.app collected 2026-10-03 10:41:53 UTC 63,376 bytes, 1,629 lines download raw bytes

1/**
2 * Relaciona activity (Export)
3 *
4 * Released under Attribution-ShareAlike 4.0 International License.
5 * Author: Manuel Narváez Martínez
6 * License: http://creativecommons.org/licenses/by-sa/4.0/
7 *
8 */
9var $eXeRelaciona = {
10    idevicePath: '',
11    borderColors: $exeDevices.iDevice.gamification.colors.borderColors,
12    colors: $exeDevices.iDevice.gamification.colors.backColor,
13    options: [],
14    hasSCORMbutton: false,
15    isInExe: false,
16    userName: '',
17    previousScore: '',
18    initialScore: '',
19    version: 3,
20    scormAPIwrapper: 'libs/SCORM_API_wrapper.js',
21    scormFunctions: 'libs/SCOFunctions.js',
22    mScorm: null,
23    jqueryui: 1,
24
25    init: function () {
26        $exeDevices.iDevice.gamification.initGame(
27            this,
28            'Relate',
29            'relate',
30            'relaciona-IDevice'
31        );
32    },
33
34    enable: function () {
35        $eXeRelaciona.loadGame();
36    },
37
38    loadGame: function () {
39        $eXeRelaciona.options = [];
40
41        $eXeRelaciona.activities.each(function (i) {
42            const dl = $('.relaciona-DataGame', this);
43            const mOption = $eXeRelaciona.loadDataGame(dl, this);
44
45            mOption.scorerp = 0;
46            mOption.idevicePath = $eXeRelaciona.idevicePath;
47            mOption.main = 'rlcMainContainer-' + i;
48            mOption.idevice = 'relaciona-IDevice';
49
50            $eXeRelaciona.options.push(mOption);
51
52            const rlc = $eXeRelaciona.createInterfaceCards(i);
53
54            dl.before(rlc).remove();
55            $('#rlcGameMinimize-' + i).hide();
56            $('#rlcGameContainer-' + i).hide();
57            $('#rlcCubierta-' + i).hide();
58            if (mOption.showMinimize) {
59                $('#rlcGameMinimize-' + i)
60                    .css({ cursor: 'pointer' })
61                    .show();
62            } else {
63                $('#rlcGameContainer-' + i).show();
64            }
65
66            $eXeRelaciona.createCards(i);
67
68            $eXeRelaciona.addEvents(i);
69            if (mOption.type == 2 && mOption.time > 0) {
70                $('#rlcImgTime-' + i).show();
71                $('#rlcPTime-' + i).show();
72                $('#rlcStartGame-' + i).show();
73                $('#rlcMessage-' + i).hide();
74                $eXeRelaciona.updateTime(mOption.time * 60, i);
75            }
76        });
77
78        let node = document.querySelector('.page-content');
79        if (this.isInExe) {
80            node = document.getElementById('node-content');
81        }
82        if (node)
83            $exeDevices.iDevice.gamification.observers.observeResize(
84                $eXeRelaciona,
85                node
86            );
87
88        const relateHtml = $('.relaciona-IDevice').html();
89        if ($exeDevices.iDevice.gamification.math.hasLatex(relateHtml)) {
90            $exeDevices.iDevice.gamification.math.updateLatex(
91                '.relaciona-IDevice'
92            );
93        }
94    },
95
96    loadDataGame: function (data, sthis) {
97        const json = data.text(),
98            mOptions =
99                $exeDevices.iDevice.gamification.helpers.isJsonString(json),
100            $imagesLink = $('.relaciona-LinkImages', sthis),
101            $audiosLink = $('.relaciona-LinkAudios', sthis),
102            $imagesLinkBack = $('.relaciona-LinkImagesBack', sthis),
103            $audiosLinkBack = $('.relaciona-LinkAudiosBack', sthis);
104
105        mOptions.playerAudio = '';
106        mOptions.gameStarted = false;
107        mOptions.typeDrag = 0;
108
109        $imagesLink.each(function () {
110            const iq = parseInt($(this).text());
111            if (!isNaN(iq) && iq < mOptions.cardsGame.length) {
112                const flipcard = mOptions.cardsGame[iq];
113                flipcard.url = $(this).attr('href');
114                if (flipcard.url.length < 4) {
115                    flipcard.url = '';
116                }
117            }
118        });
119
120        $audiosLink.each(function () {
121            const iqa = parseInt($(this).text());
122            if (!isNaN(iqa) && iqa < mOptions.cardsGame.length) {
123                const flipcard = mOptions.cardsGame[iqa];
124                flipcard.audio = $(this).attr('href');
125                if (flipcard.audio.length < 4) {
126                    flipcard.audio = '';
127                }
128            }
129        });
130
131        $imagesLinkBack.each(function () {
132            const iq = parseInt($(this).text());
133            if (!isNaN(iq) && iq < mOptions.cardsGame.length) {
134                const flipcard = mOptions.cardsGame[iq];
135                flipcard.urlBk = $(this).attr('href');
136                if (flipcard.urlBk.length < 4) {
137                    flipcard.urlBk = '';
138                }
139            }
140        });
141
142        $audiosLinkBack.each(function () {
143            const iqa = parseInt($(this).text());
144            if (!isNaN(iqa) && iqa < mOptions.cardsGame.length) {
145                const flipcard = mOptions.cardsGame[iqa];
146                flipcard.audioBk = $(this).attr('href');
147                if (flipcard.audioBk.length < 4) {
148                    flipcard.audioBk = '';
149                }
150            }
151        });
152        mOptions.currentWordDiv = null;
153        mOptions.linesMap = new Map();
154        mOptions.lines = [];
155        mOptions.permitirErrores = mOptions.type > 0;
156        mOptions.time =
157            typeof mOptions.time === 'undefined' ? 0 : mOptions.time;
158        mOptions.evaluation =
159            typeof mOptions.evaluation === 'undefined'
160                ? false
161                : mOptions.evaluation;
162        mOptions.evaluationID =
163            typeof mOptions.evaluationID === 'undefined'
164                ? ''
165                : mOptions.evaluationID;
166        mOptions.hits = 0;
167        mOptions.errors = 0;
168        mOptions.score = 0;
169        mOptions.active = 0;
170        mOptions.obtainedClue = false;
171
172        mOptions.cardsGame =
173            $exeDevices.iDevice.gamification.helpers.getQuestions(
174                mOptions.cardsGame,
175                mOptions.percentajeCards
176            );
177        for (let i = 0; i < mOptions.cardsGame.length; i++) {
178            mOptions.cardsGame[i].id = i;
179            mOptions.cardsGame[i].eText = $eXeRelaciona.decodeURIComponentSafe(
180                mOptions.cardsGame[i].eText
181            );
182            mOptions.cardsGame[i].eTextBk =
183                $eXeRelaciona.decodeURIComponentSafe(
184                    mOptions.cardsGame[i].eTextBk
185                );
186            const id = $eXeRelaciona.getID();
187            mOptions.cardsGame[i].lineindex = id;
188        }
189
190        mOptions.numberCards = mOptions.cardsGame.length;
191        mOptions.realNumberCards = mOptions.numberCards;
192        mOptions.fullscreen = false;
193        return mOptions;
194    },
195
196    decodeURIComponentSafe: function (s) {
197        if (!s) return s;
198        return decodeURIComponent(s).replace('&percnt;', '%');
199    },
200
201    updateTime: function (tiempo, instance) {
202        const mOptions = $eXeRelaciona.options[instance],
203            mTime =
204                $exeDevices.iDevice.gamification.helpers.getTimeToString(
205                    tiempo
206                );
207        if (mOptions.time < 0) return;
208        $(`#rlcPTime-${instance}`).text(mTime);
209    },
210
211    clearHtml: function (htmlString) {
212        const tempDiv = $('<div>').html(htmlString);
213        return tempDiv.text();
214    },
215
216    createCards: function (instance) {
217        const mOptions = $eXeRelaciona.options[instance],
218            shuffledWords = $exeDevices.iDevice.gamification.helpers.shuffleAds(
219                [...mOptions.cardsGame]
220            ),
221            shuffledDefinitions =
222                $exeDevices.iDevice.gamification.helpers.shuffleAds([
223                    ...mOptions.cardsGame,
224                ]);
225
226        $('#rlcContainerWords-' + instance).empty();
227        $('#rlcContainerDefinitions-' + instance).empty();
228
229        shuffledWords.forEach((card, index) => {
230            let imgV = card.url.length > 3 ? 'block' : 'none';
231            let txtV = card.eText.length > 0 ? 'flex' : 'none';
232            let imgW = card.eText.length == 0 ? '100%' : '30%';
233
234            imgW = card.url.length < 3 ? '0%' : imgW;
235
236            let txtW = card.url.length < 3 ? '100%' : '70%';
237            txtW = card.eText.length == 0 ? '0%' : txtW;
238            let color = card.color.length > 2 ? card.color : 'inherit';
239            let bkcolor =
240                card.backcolor.length > 2 ? card.backcolor : 'trasparent';
241            let audio = card.audio.length > 3 ? 'block' : 'none';
242            let author = card.author.length > 3 ? 'block' : 'none';
243            let audioCls =
244                card.url.length < 3 && card.eText.length == 0
245                    ? 'RLCP-LinkAudioBig'
246                    : 'RLCP-LinkAudio';
247
248            const fullimage =
249                card.url.length > 3
250                    ? `<a href="#" class="RLCP-FullLinkImage" data-url="${card.url}"" title="${mOptions.msgs.msgFullScreen}">
251                    <strong><span class="sr-av">${mOptions.msgs.msgFullScreen}:</span></strong>
252                    <div  class="exeQuextIcons exeQuextIcons-FullImage RLCP-Activo"></div>
253                </a>`
254                    : '';
255
256            const wordDiv =
257                $(`<div class="RLCP-Word RLCP-NoSelect" data-id="${card.id}" data-lineindex="${card.lineindex}">
258                            <div class="RLCP-ContainerData">
259                              <div class="RLCP-EText" style="display:${txtV}; width:${txtW}; color:${color}; background-color:${bkcolor};"><div class="RLCP-ETextDinamyc">${card.eText}</div></div>    
260                              <div class="RLCP-ImageContain" style="display:${imgV}; width:${imgW};">
261                                  <img src="${card.url}" class="RLCP-Image" data-url="${card.url}" data-x="${card.x}" data-y="${card.y}" alt="${card.alt}" />
262                                  <div class="RLCP-Author RLCP-AuthorWord" data-author="${card.author}"  style="display: ${author};" alt="${card.author}" title="${mOptions.msgs.msgAuthor + ': ' + $eXeRelaciona.clearHtml(card.author)}"><img src="${$eXeRelaciona.idevicePath}exequextcopyright.png"  /></div>
263                                  ${fullimage}
264                                </div>
265                            </div>
266                            <div style="display:${audio}" data-audio="${card.audio}" class="RLCP-TAudio ${audioCls}"  title="Audio"><img src="${$eXeRelaciona.idevicePath}exequextplayaudio.svg" class="RLCP-Audio"  alt="Audio"></div>
267                        </div>`);
268            $('#rlcContainerWords-' + instance).append(wordDiv);
269        });
270
271        shuffledDefinitions.forEach((card, index) => {
272            let imgV = card.urlBk.length > 3 ? 'block' : 'none';
273            let txtV = card.eTextBk.length > 0 ? 'flex' : 'none';
274            let imgW = card.eTextBk.length == 0 ? '100%' : '30%';
275            imgW = card.urlBk.length < 3 ? '0%' : imgW;
276            let txtW = card.urlBk.length < 3 ? '100%' : '70%';
277            txtW = card.eTextBk.length == 0 ? '0%' : txtW;
278            let color = card.colorBk.length > 2 ? card.colorBk : 'inherit';
279            let bkcolor =
280                card.backcolorBk.length > 2 ? card.backcolorBk : 'trasparent';
281            let audio = card.audioBk.length > 3 ? 'block' : 'none';
282            let author = card.authorBk.length > 0 ? 'block' : 'none';
283            let audioCls =
284                card.urlBk.length < 3 && card.eTextBk.length == 0
285                    ? 'RLCP-LinkAudioBig'
286                    : 'RLCP-LinkAudio';
287            const fullimage =
288                card.urlBk.length > 3
289                    ? `<a href="#" class="RLCP-FullLinkImage" data-url="${card.urlBk}"" title="${mOptions.msgs.msgFullScreen}">
290                <strong><span class="sr-av">${mOptions.msgs.msgFullScreen}:</span></strong>
291                <div  class="exeQuextIcons exeQuextIcons-FullImage RLCP-Activo"></div>
292            </a>`
293                    : '';
294            const definitionDiv =
295                $(`<div class="RLCP-Definition RLCP-NoSelect" data-id="${card.id}"  data-lineindex="0" >
296                                <div class="RLCP-ContainerData">
297                                  <div class="RLCP-ImageContain" style="display:${imgV}; width:${imgW};">
298                                    <img src="${card.urlBk}" class="RLCP-Image" data-url="${card.urlBk}" data-x="${card.x}" data-y="${card.y}" alt="${card.altBk}" />
299                                    <div class="RLCP-Author RLCP-AuthorDef" style="display: ${author};" data-author="${card.authorBk}" alt="${card.authorBk}" title="${mOptions.msgs.msgAuthor + ': ' + $eXeRelaciona.clearHtml(card.authorBk)}"><img src="${$eXeRelaciona.idevicePath}exequextcopyright.png"  /></div>
300                                    ${fullimage}
301                                    </div>
302                                  <div class="RLCP-EText" style="display:${txtV}; width:${txtW}; color:${color}; background-color:${bkcolor};"><div class="RLCP-ETextDinamyc">${card.eTextBk}</div></div>
303                                </div>
304                              <div data-audio="${card.audioBk}" style="display:${audio}" class="RLCP-TAudio ${audioCls}" title="Audio"><img src="${$eXeRelaciona.idevicePath}exequextplayaudio.svg" class="FLCDSP-RLCP"  alt="Audio"></div>
305                            </div>`);
306            $('#rlcContainerDefinitions-' + instance).append(definitionDiv);
307        });
308    },
309
310    getID: function () {
311        const randomNumber1 = Math.floor(1000 + Math.random() * 9000),
312            randomNumber2 = Math.floor(1000 + Math.random() * 9000),
313            timestamp = Date.now();
314        return `${randomNumber1}${timestamp}${randomNumber2}`;
315    },
316
317    startGame: function (instance) {
318        let mOptions = $eXeRelaciona.options[instance];
319
320        if (mOptions.gameStarted) return;
321
322        $('#rlcContainerGame-' + instance).show();
323        $(`#rlcImgTime-${instance}`).hide();
324        $(`#rlcPTime-${instance}`).hide();
325        $(`#rlcButtons-${instance}`).hide();
326        $(`#rlcResetButton-${instance}`).hide();
327
328        if (mOptions.type > 0) {
329            $(`#rlcButtons-${instance}`).css('display', 'flex');
330            $(`#rlcCheckButton-${instance}`).show();
331        }
332        mOptions.gameStarted = true;
333        mOptions.solveds = [];
334        mOptions.selecteds = [];
335        mOptions.hits = 0;
336        mOptions.errors = 0;
337        mOptions.score = 0;
338        mOptions.counter = mOptions.time * 60;
339        mOptions.gameOver = false;
340        mOptions.gameStarted = false;
341        mOptions.obtainedClue = false;
342
343        $('#rlcPShowClue-' + instance).text('');
344        $('#rlcShowClue-' + instance).hide();
345        $('#rlcPHits-' + instance).text(mOptions.hits);
346        $('#rlcPErrors-' + instance).text(mOptions.errors);
347        $('#rlcCubierta-' + instance).hide();
348        $('#rlcStartGame-' + instance).hide();
349        $('#rlcMessage-' + instance).hide();
350
351        if (
352            typeof mOptions != 'undefined' &&
353            mOptions.type == 2 &&
354            mOptions.time > 0
355        ) {
356            $(`#rlcPTime-${instance}`).show();
357            $(`#rlcImgTime-${instance}`).show();
358            let $node = $('#rlcMainContainer-' + instance);
359            let $content = $('#node-content');
360            mOptions.counterClock = setInterval(function () {
361                if (
362                    !$node.length ||
363                    ($content.length && $content.attr('mode') === 'edition')
364                ) {
365                    clearInterval(mOptions.counterClock);
366                    return;
367                }
368                if (typeof mOptions != 'undefined' && mOptions.gameStarted) {
369                    mOptions.counter--;
370                    $eXeRelaciona.updateTime(mOptions.counter, instance);
371                    if (mOptions.counter <= 0) {
372                        $eXeRelaciona.gameOver(instance);
373                        return;
374                    }
375                }
376            }, 1000);
377            $eXeRelaciona.updateTime(mOptions.time * 60, instance);
378        }
379
380        mOptions.gameStarted = true;
381    },
382    redibujarLineas: function (instance, isMoving) {
383        const mOptions = $eXeRelaciona.options[instance];
384        if (!mOptions) return;
385        mOptions._moving = !!isMoving;
386        $eXeRelaciona.requestRedraw(instance);
387    },
388
389    requestRedraw: function (instance) {
390        const mOptions = $eXeRelaciona.options[instance];
391        if (!mOptions || !mOptions.canvas || !mOptions.contexto) return;
392        if (mOptions._rafPending) return;
393        mOptions._rafPending = true;
394        const raf =
395            typeof requestAnimationFrame !== 'undefined'
396                ? requestAnimationFrame
397                : function (cb) {
398                      return setTimeout(cb, 16);
399                  };
400        raf(() => {
401            mOptions._rafPending = false;
402            $eXeRelaciona._drawLines(instance);
403        });
404    },
405
406    _drawLines: function (instance) {
407        const mOptions = $eXeRelaciona.options[instance];
408        if (!mOptions || !mOptions.canvas || !mOptions.contexto) return;
409        const ctx = mOptions.contexto;
410        const canvasRect = mOptions.canvas.getBoundingClientRect();
411        const dpr = mOptions._dpr || Math.max(window.devicePixelRatio || 1, 1);
412        const cssW = mOptions._cssW
413            ? mOptions._cssW
414            : mOptions.canvas.width / dpr;
415        const cssH = mOptions._cssH
416            ? mOptions._cssH
417            : mOptions.canvas.height / dpr;
418        ctx.clearRect(0, 0, cssW, cssH);
419
420        ctx.lineWidth = $eXeRelaciona.isMobile() ? 3 : 5;
421
422        mOptions.linesMap.forEach((line) => {
423            const startRect = line.start[0].getBoundingClientRect();
424            const endRect = line.end[0].getBoundingClientRect();
425            const x1 = startRect.right - canvasRect.left;
426            const y1 = startRect.top + startRect.height / 2 - canvasRect.top;
427            const x2 = endRect.left - canvasRect.left;
428            const y2 = endRect.top + endRect.height / 2 - canvasRect.top;
429            $eXeRelaciona.dibujaLineaCurva(ctx, x1, y1, x2, y2, line.color);
430        });
431
432        if (mOptions._moving && mOptions.currentWordDiv) {
433            const startRect =
434                mOptions.currentWordDiv[0].getBoundingClientRect();
435            const x1 = startRect.right - canvasRect.left;
436            const y1 = startRect.top + startRect.height / 2 - canvasRect.top;
437            const x2 =
438                typeof mOptions._tempX === 'number'
439                    ? mOptions._tempX - canvasRect.left
440                    : x1;
441            const y2 =
442                typeof mOptions._tempY === 'number'
443                    ? mOptions._tempY - canvasRect.top
444                    : y1;
445            $eXeRelaciona.dibujaLineaCurva(
446                ctx,
447                x1,
448                y1,
449                x2,
450                y2,
451                $eXeRelaciona.borderColors.blue
452            );
453        }
454    },
455
456    createInterfaceCards: function (instance) {
457        const path = $eXeRelaciona.idevicePath,
458            msgs = $eXeRelaciona.options[instance].msgs,
459            mOptions = $eXeRelaciona.options[instance],
460            html = `
461        <div class="RLCP-MainContainer" id="rlcMainContainer-${instance}">
462            <div class="RLCP-GameMinimize" id="rlcGameMinimize-${instance}">
463                <a href="#" class="RLCP-LinkMaximize" id="rlcLinkMaximize-${instance}" title="${msgs.msgMaximize}">
464                    <img src="${path}relacionaIcon.png" class="RLCP-IconMinimize RLCP-Activo" alt="">
465                    <div class="RLCP-MessageMaximize" id="rlcMessageMaximize-${instance}">${msgs.msgPlayStart}</div>
466                </a>
467            </div>
468            <div class="RLCP-GameContainer" id="rlcGameContainer-${instance}">
469                <div class="RLCP-GameScoreBoard" id="rlcGameScoreBoard-${instance}">
470                    <div class="RLCP-GameScores" id="rlcGameScores-${instance}">
471                        <div class="exeQuextIcons exeQuextIcons-Number" id="rlcPNumberIcon-${instance}" title="${msgs.msgNumQuestions}"></div>
472                        <p><span class="sr-av">${msgs.msgNumQuestions}: </span><span id="rlcPNumber-${instance}">0</span></p>
473                        <div class="exeQuextIcons exeQuextIcons-Hit" title="${msgs.msgHits}"></div>
474                        <p><span class="sr-av">${msgs.msgHits}: </span><span id="rlcPHits-${instance}">0</span></p>
475                        <div class="exeQuextIcons exeQuextIcons-Error" title="${msgs.msgErrors}"></div>
476                        <p><span class="sr-av">${msgs.msgErrors}: </span><span id="rlcPErrors-${instance}">0</span></p>
477                        <div class="exeQuextIcons exeQuextIcons-Score" title="${msgs.msgScore}"></div>
478                        <p><span class="sr-av">${msgs.msgScore}: </span><span id="rlcPScore-${instance}">0</span></p>
479                    </div>
480                    <div class="RLCP-Info" id="rlcInfo-${instance}"></div>
481                    <div class="RLCP-TimeNumber">
482                        <strong><span class="sr-av">${msgs.msgTime}:</span></strong>
483                        <div class="exeQuextIcons exeQuextIcons-Time" style="display:none" id="rlcImgTime-${instance}" title="${msgs.msgTime}"></div>
484                        <p id="rlcPTime-${instance}" style="display:none" class="RLCP-PTime">00:00</p>
485                        <a href="#" class="RLCP-LinkMinimize" id="rlcLinkMinimize-${instance}" title="${msgs.msgMinimize}">
486                            <strong><span class="sr-av">${msgs.msgMinimize}:</span></strong>
487                            <div class="exeQuextIcons exeQuextIcons-Minimize RLCP-Activo"></div>
488                        </a>
489                        <a href="#" class="RLCP-LinkFullScreen" id="rlcLinkFullScreen-${instance}" title="${msgs.msgFullScreen}">
490                            <strong><span class="sr-av">${msgs.msgFullScreen}:</span></strong>
491                            <div class="exeQuextIcons exeQuextIcons-FullScreen RLCP-Activo" id="rlcFullScreen-${instance}"></div>
492                        </a>
493                    </div>
494                </div>
495                <div class="RLCP-Information">
496                    <a href="#" style="display:none" id="rlcStartGame-${instance}">${msgs.msgPlayStart}</a>
497                    <p class="RLCP-Message" id="rlcMessage-${instance}"></p>
498                </div>
499                <div id="rlcButtons-${instance}" class="RLCP-Buttons">
500                    <a href="#" class="RLCP-ResetButton" id="rlcResetButton-${instance}">${msgs.msgRestart}</a>
501                    <a href="#" class="RLCP-CheckButton" id="rlcCheckButton-${instance}">${msgs.msgCheck}</a>
502                </div>
503                <div class="RLCP-Multimedia" id="rlcMultimedia-${instance}">
504                    ${$eXeRelaciona.getMainHtml(instance)}                        
505                </div>
506                <div class="RLCP-AuthorGame" id="rlcAuthorGame-${instance}"></div>
507            </div>
508            <div class="RLCP-Cover" id="rlcCubierta-${instance}">
509                <div class="RLCP-CodeAccessDiv" id="rlcCodeAccessDiv-${instance}">
510                    <div class="RLCP-MessageCodeAccessE" id="rlcMesajeAccesCodeE-${instance}"></div>
511                    <div class="RLCP-DataCodeAccessE">
512                        <label class="sr-av">${msgs.msgCodeAccess}:</label>
513                        <input type="text" class="RLCP-CodeAccessE form-control" id="rlcCodeAccessE-${instance}" placeholder="${msgs.msgCodeAccess}">
514                        <a href="#" id="rlcCodeAccessButton-${instance}" title="${msgs.msgSubmit}">
515                            <strong><span class="sr-av">${msgs.msgSubmit}</span></strong>
516                            <div class="exeQuextIcons-Submit RLCP-Activo"></div>
517                        </a>
518                    </div>
519                </div>
520                <div class="RLCP-ShowClue" id="rlcShowClue-${instance}">
521                    <p class="sr-av">${msgs.msgClue}</p>
522                    <p class="RLCP-PShowClue" id="rlcPShowClue-${instance}"></p>
523                    <a href="#" class="RLCP-ClueBotton" id="rlcClueButton-${instance}" title="${msgs.msgClose}">${msgs.msgClose}</a>
524                </div>
525            </div>
526        </div>
527        ${$exeDevices.iDevice.gamification.scorm.addButtonScoreNew(mOptions, this.isInExe)}
528    `;
529        return html;
530    },
531
532    getMainHtml: function (instance) {
533        let html = `<div class="RLCP-Main">
534                <div id="rlcContainerGame-${instance}" class="RLCP-ContainerGame">
535                    <div id="rlcContainerWords-${instance}" class="RLCP-ContainerWords"></div>
536                    <div id="rlcContainerDefinitions-${instance}" class="RLCP-ContainerDefinitions"></div>
537                </div>
538                <canvas id="rlcCanvas-${instance}" width="800" height="600" class="RLCP-Canvas"></canvas>
539            </div>`;
540        return html;
541    },
542
543    hexToRgba: function (hex, opacity) {
544        const r = parseInt(hex.slice(1, 3), 16),
545            g = parseInt(hex.slice(3, 5), 16),
546            b = parseInt(hex.slice(5, 7), 16),
547            opacity1 = Math.min(Math.max(opacity, 0), 1);
548        return `rgba(${r}, ${g}, ${b}, ${opacity1})`;
549    },
550
551    shuffleElements: function (parentElement) {
552        const children = parentElement.children().get(),
553            shuffledChildren =
554                $exeDevices.iDevice.gamification.helpers.shuffleAds(children);
555        parentElement.empty().append(shuffledChildren);
556    },
557
558    gameOver: function (instance) {
559        $eXeRelaciona.checkState(instance);
560    },
561
562    showScoreFooter: function (instance) {
563        const mOptions = $eXeRelaciona.options[instance],
564            score = (mOptions.hits * 10) / mOptions.realNumberCards,
565            formattedScore = Number(score).toFixed(2);
566
567        $(`#rlcRepeatActivity-${instance}`).text(
568            `${mOptions.msgs.msgYouScore}: ${formattedScore}`
569        );
570
571        return formattedScore;
572    },
573
574    showScoreGame: function (instance) {
575        const mOptions = $eXeRelaciona.options[instance],
576            msgs = mOptions.msgs,
577            score = ((mOptions.hits * 10) / mOptions.numberCards).toFixed(2);
578        let message = msgs.msgEndGameM.replace('%s', score),
579            messageColor = 0,
580            clueMessage = '';
581
582        $eXeRelaciona.showMessage(messageColor, message, instance, true);
583
584        if (mOptions.itinerary.showClue) {
585            if (score * 100 > mOptions.itinerary.percentageClue) {
586                clueMessage = mOptions.itinerary.clueGame;
587            } else {
588                clueMessage = msgs.msgTryAgain.replace(
589                    '%s',
590                    mOptions.itinerary.percentageClue
591                );
592            }
593            $eXeRelaciona.showMessage(3, clueMessage, instance, true);
594        }
595
596        const sscore = (
597            (mOptions.hits * 10) /
598            mOptions.cardsGame.length
599        ).toFixed(2);
600        $(`#rlcPScore-${instance}`).text(sscore);
601        $(`#rlcPHits-${instance}`).text(mOptions.hits);
602        $(`#rlcPErrors-${instance}`).text(mOptions.errors);
603        $(`#rlcPNumber-${instance}`).text(
604            mOptions.realNumberCards - mOptions.hits - mOptions.errors
605        );
606    },
607
608    showClue: function (instance) {
609        const mOptions = $eXeRelaciona.options[instance],
610            percentageHits = (mOptions.hits * 10) / mOptions.cardsGame.length;
611        if (mOptions.itinerary.showClue) {
612            if (percentageHits >= mOptions.itinerary.percentageClue) {
613                if (!mOptions.obtainedClue) {
614                    mOptions.obtainedClue = true;
615                    const msg = `${mOptions.msgs.msgInformation}: ${mOptions.itinerary.clueGame}`;
616                    $(`#rlcPShowClue-${instance}`).text(msg);
617                    $(`#rlcShowClue-${instance}`).show();
618                    $(`#rlcCubierta-${instance}`).show();
619                }
620            }
621        }
622    },
623
624    reboot: function (instance) {
625        const mOptions = $eXeRelaciona.options[instance];
626        mOptions.hits = 0;
627        mOptions.errors = 0;
628        mOptions.score = 0;
629        mOptions.active = 0;
630        mOptions.obtainedClue = false;
631        $('#rlcButtons-' + instance).hide();
632        $eXeRelaciona.rebootCards(instance);
633        $eXeRelaciona.showScoreGame(instance);
634        mOptions.gameStarted = true;
635        mOptions.gameOver = false;
636
637        $('#rlcMessage-' + instance).hide();
638    },
639
640    rebootCards: function (instance) {
641        const mOptions = $eXeRelaciona.options[instance];
642
643        const $container = $(`#rlcGameContainer-${instance}`);
644        $container
645            .find('.RLCP-Word, .RLCP-Definition')
646            .removeClass('RLCP-Connected')
647            .removeData('lineindex');
648
649        mOptions.linesMap.clear();
650        mOptions.currentWordDiv = null;
651        if (mOptions.tempEnd) {
652            mOptions.tempEnd.remove();
653            mOptions.tempEnd = null;
654        }
655
656        $eXeRelaciona.ajustarCanvas(instance);
657        $eXeRelaciona.redibujarLineas(instance, false);
658
659        $eXeRelaciona.createCards(instance);
660        $eXeRelaciona.showScoreGame(instance);
661
662        if (mOptions.type == 2) {
663            mOptions.counter = mOptions.time * 60;
664            $eXeRelaciona.startGame(instance);
665        }
666    },
667
668    checkState: function (instance) {
669        const mOptions = $eXeRelaciona.options[instance];
670
671        if (typeof mOptions === 'undefined') return;
672
673        clearInterval(mOptions.counterClock);
674        mOptions.gameOver = true;
675        mOptions.gameStarted = false;
676        $eXeRelaciona.checkStateArrows(instance);
677
678        $(`#rlcCheckButton-${instance}`).hide();
679        $(`#rlcButtons-${instance}`).css('display', 'flex');
680        $(`#rlcResetButton-${instance}`).show();
681
682        $exeDevices.iDevice.gamification.media.stopSound(mOptions);
683        $eXeRelaciona.showScoreGame(instance);
684        $eXeRelaciona.saveEvaluation(instance, true);
685
686        if (mOptions.isScorm === 1) {
687            $eXeRelaciona.sendScore(true, instance);
688            $eXeRelaciona.initialScore =
689                $eXeRelaciona.showScoreFooter(instance);
690        }
691    },
692
693    checkStateArrows: function (instance) {
694        const mOptions = $eXeRelaciona.options[instance];
695        mOptions.hits = 0;
696        mOptions.errors = 0;
697
698        mOptions.linesMap.forEach((value, key) => {
699            if (value.correct) {
700                mOptions.hits++;
701                value.color = $eXeRelaciona.borderColors.green;
702            } else {
703                value.color = $eXeRelaciona.borderColors.red;
704                mOptions.errors++;
705            }
706        });
707        $eXeRelaciona.redibujarLineas(instance, false);
708    },
709
710    removeEvents: function (instance) {
711        $(`#rlcLinkMaximize-${instance}`).off('click touchstart');
712        $(`#rlcLinkMinimize-${instance}`).off('click touchstart');
713        $(`#rlcCodeAccessButton-${instance}`).off('click touchstart');
714        $(`#rlcCodeAccessE-${instance}`).off('keydown');
715        $(`#rlcSendScore-${instance}`).off('click');
716        $('#rlcMainContainer-' + instance)
717            .closest('.idevice_node')
718            .off('click', '.Games-SendScore');
719
720        $(window).off('unload.eXeRelaciona beforeunload.eXeRelaciona');
721
722        $(document).off('mousemove.eXeRlc' + instance);
723        $(document).off('mouseup.eXeRlc' + instance);
724
725        $(`#rlcClueButton-${instance}`).off('click');
726        $(`#rlcStartGame-${instance}`).off('click');
727        $(`#rlcLinkFullScreen-${instance}`).off('click touchstart');
728        $(`#rlcResetButton-${instance}`).off('click');
729        $(`#rlcCheckButton-${instance}`).off('click');
730        $(`#rlcGameContainer-${instance}`).off(
731            'mouseenter touchstart',
732            '.RLCP-Author'
733        );
734        $(`#rlcGameContainer-${instance}`).off(
735            'mouseleave touchend touchcancel',
736            '.RLCP-Author'
737        );
738        $(`#rlcGameContainer-${instance}`).off(
739            'click touchstart',
740            '.RLCP-TAudio'
741        );
742    },
743
744    addEvents: function (instance) {
745        const mOptions = $eXeRelaciona.options[instance];
746        const $rlcGameContainer = $('#rlcGameContainer-' + instance);
747
748        $eXeRelaciona.removeEvents(instance);
749
750        $('#rlcMainContainer-' + instance)
751            .closest('.idevice_node')
752            .on('click', '.Games-SendScore', function (e) {
753                e.preventDefault();
754                $eXeRelaciona.sendScore(false, instance);
755                $eXeRelaciona.saveEvaluation(instance);
756            });
757
758        $('#rlcLinkMaximize-' + instance).on('click touchstart', function (e) {
759            e.preventDefault();
760            $rlcGameContainer.show();
761            $('#rlcGameMinimize-' + instance).hide();
762        });
763
764        $('#rlcLinkMinimize-' + instance).on('click touchstart', function (e) {
765            e.preventDefault();
766            $rlcGameContainer.hide();
767            $('#rlcGameMinimize-' + instance)
768                .css('visibility', 'visible')
769                .show();
770        });
771
772        $('#rlcCubierta-' + instance).hide();
773        $('#rlcCodeAccessDiv-' + instance).hide();
774
775        $('#rlcCodeAccessButton-' + instance).on(
776            'click touchstart',
777            function (e) {
778                e.preventDefault();
779                $eXeRelaciona.enterCodeAccess(instance);
780            }
781        );
782
783        $('#rlcCodeAccessE-' + instance).on('keydown', function (event) {
784            if (event.which == 13 || event.keyCode == 13) {
785                $eXeRelaciona.enterCodeAccess(instance);
786                return false;
787            }
788            return true;
789        });
790
791        $('#rlcPNumber-' + instance).text(mOptions.realNumberCards);
792
793        $(window).on(
794            'unload.eXeRelaciona beforeunload.eXeRelaciona',
795            function () {
796                if ($eXeRelaciona.mScorm) {
797                    $exeDevices.iDevice.gamification.scorm.endScorm(
798                        $eXeRelaciona.mScorm
799                    );
800                }
801            }
802        );
803
804        $('#rlcSendScore-' + instance).click(function (e) {
805            e.preventDefault();
806            $eXeRelaciona.sendScore(false, instance);
807            $eXeRelaciona.saveEvaluation(instance, true);
808        });
809
810        $('#rlcClueButton-' + instance).on('click', function (e) {
811            e.preventDefault();
812            $('#rlcShowClue-' + instance).hide();
813            $('#rlcCubierta-' + instance).fadeOut();
814        });
815
816        $('#rlcPErrors-' + instance).text(mOptions.errors);
817        if (mOptions.author.trim().length > 0 && !mOptions.fullscreen) {
818            $('#rlcAuthorGame-' + instance).html(
819                mOptions.msgs.msgAuthor + ': ' + mOptions.author
820            );
821            $('#rlcAuthorGame-' + instance).show();
822        }
823
824        if (mOptions.isScorm > 0) {
825            $exeDevices.iDevice.gamification.scorm.registerActivity(mOptions);
826        }
827
828        $('#rlcStartGame-' + instance).on('click', function (e) {
829            e.preventDefault();
830            $eXeRelaciona.startGame(instance);
831        });
832
833        $('#rlcLinkFullScreen-' + instance).on(
834            'click touchstart',
835            function (e) {
836                e.preventDefault();
837                const element = document.getElementById(
838                    'rlcGameContainer-' + instance
839                );
840                $exeDevices.iDevice.gamification.helpers.toggleFullscreen(
841                    element
842                );
843            }
844        );
845
846        $exeDevices.iDevice.gamification.report.updateEvaluationIcon(
847            mOptions,
848            this.isInExe
849        );
850
851        $('#rlcResetButton-' + instance).on('click', function (e) {
852            e.preventDefault();
853            $eXeRelaciona.reboot(instance);
854        });
855
856        $('#rlcCheckButton-' + instance).on('click', function (e) {
857            e.preventDefault();
858            $eXeRelaciona.checkState(instance);
859        });
860
861        $eXeRelaciona.ajustarCanvas(instance);
862
863        $rlcGameContainer.on(
864            'mouseenter touchstart',
865            '.RLCP-Author',
866            function () {
867                $eXeRelaciona.showAutorImage($(this), instance);
868            }
869        );
870
871        $rlcGameContainer.on(
872            'mouseleave touchend touchcancel',
873            '.RLCP-Author',
874            function () {
875                $eXeRelaciona.hideAutorImage($(this), instance);
876            }
877        );
878
879        $rlcGameContainer.on('click touchstart', '.RLCP-TAudio', function () {
880            if (!mOptions.gameStarted || mOptions.gameOver) return;
881            const audio = $(this).data('audio');
882            if (audio && audio.length > 3)
883                $exeDevices.iDevice.gamification.media.playSound(
884                    audio,
885                    mOptions
886                );
887        });
888
889        $eXeRelaciona.setupEventHandlers(instance);
890        $eXeRelaciona.setupEventHandlersMovil(instance);
891
892        $rlcGameContainer.on('click', '.RLCP-FullLinkImage', function (e) {
893            e.preventDefault();
894            e.stopPropagation();
895            const largeImageSrc = $(this).data('url');
896            if (largeImageSrc && largeImageSrc.length > 3) {
897                $exeDevices.iDevice.gamification.helpers.showFullscreenImage(
898                    largeImageSrc,
899                    $rlcGameContainer
900                );
901            }
902        });
903        $eXeRelaciona.refreshGame(instance);
904        $(`#rlcContainerGame-${instance}`).hide();
905        if (mOptions.itinerary.showCodeAccess) {
906            $('#rlcMesajeAccesCodeE-' + instance).text(
907                mOptions.itinerary.messageCodeAccess
908            );
909            $('#rlcCodeAccessDiv-' + instance).show();
910            $('#rlcShowClue-' + instance).hide();
911            $('#rlcCubierta-' + instance).show();
912        }
913        mOptions.gameOver = false;
914        if (mOptions.type < 2 && !mOptions.itinerary.showCodeAccess) {
915            $eXeRelaciona.startGame(instance);
916        }
917    },
918
919    setFontSize: function (instance) {
920        const $words = $('#rlcGameContainer-' + instance).find('.RLCP-Word');
921        $words.each(function () {
922            const $card = $(this),
923                $text = $card.find('.RLCP-EText');
924            $eXeRelaciona.adjustFontSize($text);
925        });
926
927        const $definition = $('#rlcGameContainer-' + instance).find(
928            '.RLCP-Definition'
929        );
930        $definition.each(function () {
931            const $card = $(this),
932                $text = $card.find('.RLCP-EText');
933            $eXeRelaciona.adjustFontSize($text);
934        });
935    },
936
937    refreshGame: function (instance) {
938        const mOptions = $eXeRelaciona.options[instance];
939        if (!mOptions) return;
940        if (mOptions.refreshGame) return;
941        mOptions.refreshGame = true;
942        let element = document.getElementById('rlcGameContainer-' + instance);
943        element = element || document.documentElement;
944        mOptions.fullscreen = !(
945            !document.fullscreenElement &&
946            !document.mozFullScreenElement &&
947            !document.webkitFullscreenElement &&
948            !document.msFullscreenElement
949        );
950        $eXeRelaciona.setFontSize(instance);
951        $eXeRelaciona.ajustarCanvas(instance);
952        mOptions.refreshGame = false;
953    },
954
955    removeEventHandlers: function (instance) {
956        const $gameContainer = $(`#rlcGameContainer-${instance}`);
957        $gameContainer.off('mousedown', '.RLCP-Word');
958    },
959
960    setupEventHandlers: function (instance) {
961        this.removeEventHandlers(instance);
962
963        const mOptions = $eXeRelaciona.options[instance],
964            dragThreshold = 5,
965            $gameContainer = $(`#rlcContainerGame-${instance}`);
966        let isDragging = false;
967
968        $gameContainer.on('mousedown', '.RLCP-Word', function (e) {
969            e.preventDefault();
970            if (
971                typeof mOptions == 'undefined' ||
972                !mOptions.gameStarted ||
973                mOptions.gameOver
974            )
975                return;
976
977            const targetWord = $(e.target).closest('.RLCP-Word'),
978                sound = $(this).find('.RLCP-TAudio').data('audio');
979
980            if (targetWord.hasClass('RLCP-Connected')) {
981                $eXeRelaciona.removeConnection(targetWord, instance);
982            }
983
984            $gameContainer.find('.RLCP-Word').removeClass('RLCP-Selected');
985            targetWord.addClass('RLCP-Selected');
986            mOptions.currentWordDiv = targetWord;
987            mOptions.canvas = $(`#rlcCanvas-${instance}`)[0];
988            mOptions.contexto = mOptions.canvas.getContext('2d');
989            mOptions.canvasRect = mOptions.canvas.getBoundingClientRect();
990
991            mOptions.tempEnd = $('<div>')
992                .css({
993                    position: 'absolute',
994                    top: e.clientY - mOptions.canvasRect.top,
995                    left: e.clientX - mOptions.canvasRect.left,
996                    width: 0,
997                    height: 0,
998                    padding: 0,
999                    margin: 0,
1000                    'border-radius': '50%',
1001                    'background-color': 'red',
1002                    'z-index': 9,
1003                })
1004                .appendTo(`#rlcContainerGame-${instance}`);
1005
1006            mOptions.lineindex = mOptions.currentWordDiv.data('lineindex');
1007            mOptions.startX = e.clientX - mOptions.canvasRect.left;
1008            mOptions.startY = e.clientY - mOptions.canvasRect.top;
1009            isDragging = false;
1010
1011            if (sound && sound.length > 4) {
1012                $exeDevices.iDevice.gamification.media.playSound(
1013                    sound,
1014                    mOptions
1015                );
1016            }
1017        });
1018
1019        $(document).on('mousemove.eXeRlc' + instance, (e) => {
1020            if (
1021                typeof mOptions == 'undefined' ||
1022                !mOptions.gameStarted ||
1023                mOptions.gameOver ||
1024                !mOptions.currentWordDiv ||
1025                !mOptions.tempEnd
1026            )
1027                return;
1028
1029            const dx = Math.abs(e.clientX - mOptions.startX),
1030                dy = Math.abs(e.clientY - mOptions.startY);
1031
1032            if (dx > dragThreshold || dy > dragThreshold) {
1033                mOptions.canvasRect = mOptions.canvas.getBoundingClientRect();
1034                mOptions.tempEnd.css({
1035                    top: e.clientY - mOptions.canvasRect.top,
1036                    left: e.clientX - mOptions.canvasRect.left,
1037                });
1038                // Guardar coords absolutas para el repintado
1039                mOptions._tempX = e.clientX;
1040                mOptions._tempY = e.clientY;
1041
1042                const buffer = 40,
1043                    scrollSpeed = 10;
1044
1045                if (e.clientY < buffer) {
1046                    window.scrollBy(0, -scrollSpeed);
1047                } else if (window.innerHeight - e.clientY < buffer) {
1048                    window.scrollBy(0, scrollSpeed);
1049                }
1050                isDragging = true;
1051                $eXeRelaciona.redibujarLineas(instance, isDragging);
1052            }
1053        });
1054
1055        $(document).on('mouseup.eXeRlc' + instance, (e) => {
1056            e.preventDefault();
1057            if (
1058                typeof mOptions == 'undefined' ||
1059                !mOptions.gameStarted ||
1060                mOptions.gameOver ||
1061                !mOptions.currentWordDiv
1062            )
1063                return;
1064
1065            const definitionDiv = $(e.target).closest('.RLCP-Definition'),
1066                sound = definitionDiv.find('.RLCP-TAudio').data('audio');
1067
1068            if (
1069                definitionDiv.length > 0 &&
1070                !definitionDiv.hasClass('RLCP-Connected')
1071            ) {
1072                if (
1073                    !(
1074                        mOptions.type === 0 &&
1075                        mOptions.currentWordDiv.data('id') !==
1076                            definitionDiv.data('id')
1077                    )
1078                ) {
1079                    if (isDragging || mOptions.currentWordDiv) {
1080                        $eXeRelaciona.addOrUpdateLine(
1081                            mOptions.lineindex,
1082                            mOptions.currentWordDiv,
1083                            definitionDiv,
1084                            $eXeRelaciona.borderColors.blue,
1085                            instance
1086                        );
1087                        $eXeRelaciona.checkAllConnected(instance);
1088                        mOptions.currentWordDiv = null;
1089                    }
1090                }
1091                if (sound && sound.length > 4) {
1092                    $exeDevices.iDevice.gamification.media.playSound(
1093                        sound,
1094                        mOptions
1095                    );
1096                }
1097            }
1098            mOptions._moving = false;
1099            $eXeRelaciona.requestRedraw(instance);
1100            isDragging = false;
1101            if (mOptions.tempEnd) {
1102                mOptions.tempEnd.remove();
1103                mOptions.tempEnd = null;
1104            }
1105            mOptions._tempX = undefined;
1106            mOptions._tempY = undefined;
1107        });
1108    },
1109
1110    showAutorImage: function ($this, instance) {
1111        const mOptions = $eXeRelaciona.options[instance],
1112            author = $this.data('author'),
1113            $rlcAuthorGameSelector = `#rlcAuthorGame-${instance}`;
1114        if (author && author.length > 0) {
1115            $($rlcAuthorGameSelector)
1116                .html(`${mOptions.msgs.msgAuthor}: ${author}`)
1117                .show();
1118        }
1119    },
1120
1121    hideAutorImage: function ($this, instance) {
1122        const mOptions = $eXeRelaciona.options[instance],
1123            author = $this.data('author'),
1124            $rlcAuthorGameSelector = `#rlcAuthorGame-${instance}`;
1125        if (mOptions.author && mOptions.author.length > 0) {
1126            $($rlcAuthorGameSelector)
1127                .html(`${mOptions.msgs.msgAuthor}: ${author}`)
1128                .show();
1129        } else {
1130            $($rlcAuthorGameSelector).hide();
1131        }
1132    },
1133
1134    removeEventHandlersMovil: function (instance) {
1135        const containerGame = document.querySelector(
1136            `#rlcGameContainer-${instance}`
1137        );
1138
1139        if (containerGame) {
1140            containerGame.removeEventListener(
1141                'touchstart',
1142                this.touchStartHandler,
1143                { passive: false }
1144            );
1145            containerGame.removeEventListener(
1146                'touchmove',
1147                this.touchMoveHandler,
1148                { passive: false }
1149            );
1150            containerGame.removeEventListener(
1151                'touchend',
1152                this.touchEndHandler,
1153                { passive: false }
1154            );
1155        }
1156    },
1157
1158    setupEventHandlersMovil: function (instance) {
1159        $eXeRelaciona.removeEventHandlersMovil(instance);
1160
1161        const mOptions = $eXeRelaciona.options[instance],
1162            $gameContainer = $(`#rlcGameContainer-${instance}`);
1163
1164        let isDragging = false,
1165            dragThreshold = 5;
1166
1167        this.touchStartHandler = function (e) {
1168            e.preventDefault();
1169            if (
1170                !mOptions.gameStarted ||
1171                mOptions.gameOver ||
1172                !e.touches ||
1173                e.touches.length === 0
1174            ) {
1175                return;
1176            }
1177            const touch = e.touches[0],
1178                target = touch.target,
1179                touchedWord = $(target).closest('.RLCP-Word');
1180
1181            if (touchedWord.length === 0) return;
1182
1183            mOptions.targetWord = touchedWord;
1184
1185            if (mOptions.targetWord.hasClass('RLCP-Connected')) {
1186                $eXeRelaciona.removeConnection(mOptions.targetWord, instance);
1187            }
1188
1189            $gameContainer.find('.RLCP-Word').removeClass('RLCP-Selected');
1190            mOptions.targetWord.addClass('RLCP-Selected');
1191            mOptions.currentWordDiv = mOptions.targetWord;
1192            mOptions.canvas = document.getElementById(`rlcCanvas-${instance}`);
1193            mOptions.contexto = mOptions.canvas.getContext('2d');
1194            mOptions.canvasRect = mOptions.canvas.getBoundingClientRect();
1195            mOptions.tempEnd = $('<div>')
1196                .css({
1197                    position: 'absolute',
1198                    top: touch.clientY - mOptions.canvasRect.top,
1199                    left: touch.clientX - mOptions.canvasRect.left,
1200                    width: 10,
1201                    height: 10,
1202                    padding: 0,
1203                    margin: 0,
1204                    'border-radius': '50%',
1205                    'background-color': 'red',
1206                    'z-index': 9,
1207                })
1208                .appendTo(`#rlcContainerGame-${instance}`);
1209            mOptions.lineindex = mOptions.currentWordDiv.data('lineindex');
1210            mOptions.startX = touch.clientX;
1211            mOptions.startY = touch.clientY;
1212            isDragging = false;
1213        };
1214
1215        this.touchMoveHandler = function (e) {
1216            if (
1217                !mOptions.gameStarted ||
1218                mOptions.gameOver ||
1219                !mOptions.currentWordDiv ||
1220                !mOptions.tempEnd ||
1221                !e.touches ||
1222                e.touches.length === 0
1223            ) {
1224                return;
1225            }
1226            const touch = e.touches[0],
1227                dx = Math.abs(touch.clientX - mOptions.startX),
1228                dy = Math.abs(touch.clientY - mOptions.startY);
1229
1230            if (dx > dragThreshold || dy > dragThreshold) {
1231                mOptions.tempEnd.css({
1232                    top: touch.clientY - mOptions.canvasRect.top,
1233                    left: touch.clientX - mOptions.canvasRect.left,
1234                });
1235                mOptions._tempX = touch.clientX;
1236                mOptions._tempY = touch.clientY;
1237                const buffer = 40,
1238                    scrollSpeed = 10;
1239                if (touch.clientY < buffer) {
1240                    window.scrollBy(0, -scrollSpeed);
1241                } else if (window.innerHeight - touch.clientY < buffer) {
1242                    window.scrollBy(0, scrollSpeed);
1243                }
1244                isDragging = true;
1245                $eXeRelaciona.redibujarLineas(instance, isDragging);
1246            }
1247        };
1248
1249        this.touchEndHandler = function (e) {
1250            e.preventDefault();
1251            if (
1252                !mOptions.gameStarted ||
1253                mOptions.gameOver ||
1254                !e.changedTouches ||
1255                e.changedTouches.length === 0 ||
1256                !mOptions.currentWordDiv
1257            ) {
1258                return;
1259            }
1260            const touch = e.changedTouches[0],
1261                definitionDiv = $(
1262                    document.elementFromPoint(touch.clientX, touch.clientY)
1263                ).closest('.RLCP-Definition');
1264
1265            if (
1266                definitionDiv.length > 0 &&
1267                !definitionDiv.hasClass('RLCP-Connected')
1268            ) {
1269                if (
1270                    !(
1271                        mOptions.type === 0 &&
1272                        mOptions.currentWordDiv.data('id') !==
1273                            definitionDiv.data('id')
1274                    )
1275                ) {
1276                    if (isDragging || mOptions.currentWordDiv) {
1277                        $eXeRelaciona.addOrUpdateLine(
1278                            mOptions.lineindex,
1279                            mOptions.currentWordDiv,
1280                            definitionDiv,
1281                            $eXeRelaciona.borderColors.blue,
1282                            instance
1283                        );
1284                        $eXeRelaciona.checkAllConnected(instance);
1285                        mOptions.currentWordDiv = null;
1286                    }
1287                }
1288            }
1289            mOptions._moving = false;
1290            $eXeRelaciona.requestRedraw(instance);
1291            isDragging = false;
1292            if (mOptions.tempEnd) {
1293                mOptions.tempEnd.remove();
1294                mOptions.tempEnd = null;
1295            }
1296            mOptions._tempX = undefined;
1297            mOptions._tempY = undefined;
1298        };
1299
1300        const containerGame = document.querySelector(
1301            `#rlcGameContainer-${instance}`
1302        );
1303        if (containerGame) {
1304            containerGame.addEventListener(
1305                'touchstart',
1306                this.touchStartHandler,
1307                { passive: false }
1308            );
1309            containerGame.addEventListener('touchmove', this.touchMoveHandler, {
1310                passive: false,
1311            });
1312            containerGame.addEventListener('touchend', this.touchEndHandler, {
1313                passive: false,
1314            });
1315        }
1316    },
1317
1318    removeLine: function (lineindex, instance) {
1319        const mOptions = $eXeRelaciona.options[instance];
1320        mOptions.linesMap.delete(lineindex);
1321    },
1322
1323    updateColorLine: function (lineindex, color, instance) {
1324        const mOptions = $eXeRelaciona.options[instance];
1325        let line;
1326        if (mOptions.linesMap.has(lineindex)) {
1327            line = mOptions.linesMap.get(lineindex);
1328            line.color = color;
1329        }
1330    },
1331
1332    dibujaLineaTemporal: function (instance) {
1333        const mOptions = $eXeRelaciona.options[instance],
1334            startRect = mOptions.currentWordDiv[0].getBoundingClientRect(),
1335            endRect = mOptions.tempEnd[0].getBoundingClientRect(),
1336            x1 = startRect.right - mOptions.canvasRect.left,
1337            y1 = startRect.top + startRect.height / 2 - mOptions.canvasRect.top,
1338            x2 = endRect.left - mOptions.canvasRect.left,
1339            y2 = endRect.top + endRect.height / 2 - mOptions.canvasRect.top;
1340
1341        $eXeRelaciona.dibujaLineaCurva(
1342            mOptions.contexto,
1343            x1,
1344            y1,
1345            x2,
1346            y2,
1347            $eXeRelaciona.borderColors.blue
1348        );
1349    },
1350
1351    addOrUpdateLine: function (lineindex, $word, $definition, color, instance) {
1352        const mOptions = $eXeRelaciona.options[instance];
1353        let line,
1354            correct = false;
1355
1356        if ($word && $definition) {
1357            correct = $word.data('id') === $definition.data('id');
1358        }
1359
1360        $word.removeClass('RLCP-Selected');
1361        $definition.data('lineindex', lineindex).addClass('RLCP-Connected');
1362        $word.addClass('RLCP-Connected');
1363
1364        if (mOptions.linesMap.has(lineindex)) {
1365            line = mOptions.linesMap.get(lineindex);
1366            Object.assign(line, {
1367                start: $word,
1368                end: $definition,
1369                correct: correct,
1370                color: color,
1371            });
1372        } else {
1373            line = {
1374                start: $word,
1375                end: $definition,
1376                color: color,
1377                correct: correct,
1378            };
1379            mOptions.linesMap.set(lineindex, line);
1380        }
1381        return line;
1382    },
1383
1384    adjustFontSize: function ($container) {
1385        const $text = $container.find('.RLCP-ETextDinamyc').eq(0),
1386            minFontSize = 10,
1387            maxFontSize = 22,
1388            widthc = $container.innerWidth(),
1389            heightc = $container.innerHeight();
1390
1391        let fontSize = maxFontSize;
1392
1393        $text.css('font-size', fontSize + 'px');
1394
1395        while (
1396            ($text.outerWidth() > widthc || $text.outerHeight() > heightc) &&
1397            fontSize > minFontSize
1398        ) {
1399            fontSize--;
1400            $text.css('font-size', fontSize + 'px');
1401        }
1402
1403        while (
1404            $text.outerWidth() < widthc &&
1405            $text.outerHeight() < heightc &&
1406            fontSize < maxFontSize
1407        ) {
1408            fontSize++;
1409            $text.css('font-size', fontSize + 'px');
1410
1411            if ($text.outerWidth() > widthc || $text.outerHeight() > heightc) {
1412                fontSize--;
1413                $text.css('font-size', fontSize + 'px');
1414                break;
1415            }
1416        }
1417    },
1418
1419    getNumberCards: function (instance) {
1420        const mOptions = $eXeOrdena.options[instance];
1421        return mOptions.cardsGame[mOptions.active].cards.length;
1422    },
1423
1424    ajustarCanvas: function (instance) {
1425        const mOptions = $eXeRelaciona.options[instance],
1426            $contenedor = $(`#rlcContainerGame-${instance}`),
1427            $canvas = $(`#rlcCanvas-${instance}`);
1428
1429        const cssW = $contenedor.width();
1430        const cssH = $contenedor.height();
1431        const dpr = Math.max(window.devicePixelRatio || 1, 1);
1432        mOptions._dpr = dpr;
1433        mOptions._cssW = cssW;
1434        mOptions._cssH = cssH;
1435
1436        $canvas.attr('width', Math.round(cssW * dpr));
1437        $canvas.attr('height', Math.round(cssH * dpr));
1438        $canvas.css({ width: cssW + 'px', height: cssH + 'px' });
1439
1440        mOptions.canvas = $canvas[0];
1441
1442        if (mOptions.canvas) {
1443            mOptions.contexto = mOptions.canvas.getContext('2d');
1444            mOptions.contexto.setTransform(dpr, 0, 0, dpr, 0, 0);
1445            mOptions.canvasRect = mOptions.canvas.getBoundingClientRect();
1446            $eXeRelaciona.requestRedraw(instance);
1447        }
1448    },
1449
1450    redibujarLineas: function (instance, isMoving) {
1451        const mOptions = $eXeRelaciona.options[instance];
1452        if (!mOptions) return;
1453        mOptions._moving = !!isMoving;
1454        $eXeRelaciona.requestRedraw(instance);
1455    },
1456
1457    dibujaLineaCurva: function (contexto, x0, y0, x1, y1, color) {
1458        const p0 = { x: x0, y: y0 },
1459            p1 = { x: x1, y: y1 },
1460            dx = p1.x - p0.x,
1461            dy = p1.y - p0.y,
1462            desplazamiento = Math.min(100, Math.abs(dx) / 2),
1463            pc1 = { x: p0.x + desplazamiento, y: p0.y },
1464            pc2 = { x: p1.x - desplazamiento, y: p1.y },
1465            tangente = { x: 3 * (p1.x - pc2.x), y: 3 * (p1.y - pc2.y) },
1466            angulo = Math.atan2(tangente.y, tangente.x);
1467
1468        contexto.beginPath();
1469        contexto.strokeStyle = color;
1470        contexto.moveTo(p0.x, p0.y);
1471        contexto.bezierCurveTo(pc1.x, pc1.y, pc2.x, pc2.y, p1.x, p1.y);
1472        contexto.stroke();
1473        $eXeRelaciona.dibujaPuntaFlecha(contexto, p1, angulo, color);
1474    },
1475
1476    dibujaPuntaFlecha: function (contexto, punto, angulo, color) {
1477        const tamañoFlecha = $eXeRelaciona.isMobile() ? 10 : 12,
1478            anguloFlecha = Math.PI / 6;
1479
1480        contexto.fillStyle = color;
1481        contexto.beginPath();
1482        contexto.moveTo(punto.x, punto.y);
1483
1484        contexto.lineTo(
1485            punto.x - tamañoFlecha * Math.cos(angulo - anguloFlecha),
1486            punto.y - tamañoFlecha * Math.sin(angulo - anguloFlecha)
1487        );
1488
1489        contexto.lineTo(
1490            punto.x - tamañoFlecha * Math.cos(angulo + anguloFlecha),
1491            punto.y - tamañoFlecha * Math.sin(angulo + anguloFlecha)
1492        );
1493
1494        contexto.closePath();
1495        contexto.fill();
1496    },
1497
1498    removeConnection: function (element, instance) {
1499        const lineId = element.data('lineindex');
1500
1501        if (lineId !== undefined) {
1502            $eXeRelaciona.removeLine(lineId, instance);
1503            $(`#rlcContainerWords-${instance} .RLCP-Word`).each(function () {
1504                if ($(this).data('lineindex') === lineId) {
1505                    $(this).removeClass('RLCP-Connected');
1506                }
1507            });
1508
1509            $(`#rlcContainerDefinitions-${instance} .RLCP-Definition`).each(
1510                function () {
1511                    if ($(this).data('lineindex') === lineId) {
1512                        $(this)
1513                            .removeClass('RLCP-Connected')
1514                            .data('lineindex', 0);
1515                    }
1516                }
1517            );
1518        }
1519        $eXeRelaciona.redibujarLineas(instance, false);
1520    },
1521
1522    checkAllConnected: function (instance) {
1523        const mOptions = $eXeRelaciona.options[instance],
1524            $constainerGame = $(`#rlcGameContainer-${instance}`);
1525
1526        $(`#rlcCheckButton-${instance}`).hide();
1527        $(`#rlcResetButton-${instance}`).hide();
1528        $(`#rlcButtons-${instance}`).css('display', 'flex');
1529
1530        if (
1531            $constainerGame.find('.RLCP-Word.RLCP-Connected').length ===
1532            $constainerGame.find('.RLCP-Word').length
1533        ) {
1534            if (mOptions.permitirErrores) {
1535                $(`#rlcCheckButton-${instance}`).show();
1536            } else {
1537                $eXeRelaciona.checkState(instance);
1538            }
1539        }
1540    },
1541
1542    isMobile: function () {
1543        return /Android|BlackBerry|iPhone|iPad|iPod|Opera Mini|IEMobile/i.test(
1544            navigator.userAgent
1545        );
1546    },
1547
1548    enterCodeAccess: function (instance) {
1549        const mOptions = $eXeRelaciona.options[instance],
1550            codeInput = $(`#rlcCodeAccessE-${instance}`).val().toLowerCase(),
1551            codeAccess = mOptions.itinerary.codeAccess.toLowerCase();
1552
1553        if (codeAccess === codeInput) {
1554            $(`#rlcCodeAccessDiv-${instance}, #rlcCubierta-${instance}`).hide();
1555            $(`#rlcContainerGame-${instance}`).show();
1556            $eXeRelaciona.refreshGame(instance);
1557            $eXeRelaciona.startGame(instance);
1558        } else {
1559            $(`#rlcMesajeAccesCodeE-${instance}`)
1560                .fadeOut(300)
1561                .fadeIn(200)
1562                .fadeOut(300)
1563                .fadeIn(200);
1564            $(`#rlcCodeAccessE-${instance}`).val('');
1565        }
1566    },
1567
1568    updateScore: function (correctAnswer, instance) {
1569        const mOptions = $eXeRelaciona.options[instance];
1570        let obtainedPoints = 0;
1571
1572        if (correctAnswer) {
1573            mOptions.hits++;
1574            obtainedPoints = 10 / mOptions.realNumberCards;
1575        } else {
1576            mOptions.errors++;
1577        }
1578        mOptions.score = Math.max(mOptions.score + obtainedPoints, 0);
1579        const sscore = mOptions.score.toFixed(2);
1580
1581        $(`#rlcPScore-${instance}`).text(sscore);
1582        $(`#rlcPHits-${instance}`).text(mOptions.hits);
1583        $(`#rlcPErrors-${instance}`).text(mOptions.errors);
1584        $(`#rlcPNumber-${instance}`).text(
1585            mOptions.realNumberCards - mOptions.hits - mOptions.errors
1586        );
1587    },
1588
1589    showMessage: function (type, message, instance) {
1590        const colors = [
1591                '#555555',
1592                $eXeRelaciona.borderColors.red,
1593                $eXeRelaciona.borderColors.green,
1594                $eXeRelaciona.borderColors.blue,
1595                $eXeRelaciona.borderColors.yellow,
1596            ],
1597            color = colors[type],
1598            $rlcMessage = $(`#rlcMessage-${instance}`);
1599        $rlcMessage
1600            .html(message)
1601            .css({ color: color, 'font-style': 'bold' })
1602            .show();
1603    },
1604
1605    saveEvaluation: function (instance) {
1606        const mOptions = $eXeRelaciona.options[instance];
1607        ((mOptions.scorerp = (mOptions.hits * 10) / mOptions.realNumberCards),
1608            $exeDevices.iDevice.gamification.report.saveEvaluation(
1609                mOptions,
1610                $eXeRelaciona.isInExe
1611            ));
1612    },
1613
1614    sendScore: function (auto, instance) {
1615        const mOptions = $eXeRelaciona.options[instance];
1616
1617        ((mOptions.scorerp = score =
1618            (mOptions.hits * 10) / mOptions.realNumberCards),
1619            (mOptions.previousScore = $eXeRelaciona.previousScore));
1620        mOptions.userName = $eXeRelaciona.userName;
1621
1622        $exeDevices.iDevice.gamification.scorm.sendScoreNew(auto, mOptions);
1623
1624        $eXeRelaciona.previousScore = mOptions.previousScore;
1625    },
1626};
1627$(function () {
1628    $eXeRelaciona.init();
1629});

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.