PageSourceSearch

https://omomega.com/omdoc/

html omomega.com collected 2026-09-24 11:07:19 UTC 57,514 bytes, 1,448 lines download raw bytes

1
2
3<!DOCTYPE html>
4<html lang="en">
5<head>
6    <title>OmOmega | omDoc – Document center</title>
7    
8    <meta charset="utf-8">
9    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
10    <meta name="description" content="OmOmega - Trợ lý riêng của bạn">
11    <meta name="keywords" content="OmOmega, omomega, omChat, omchat, omBot, ombot, omDoc, omdoc, trí tuệ nhân tạo, ai, machine learning, deep learning">
12    <meta name="robots" content="index,follow">
13    <meta name="DC.title" content="OmOmega Trợ lý riêng của bạn">
14
15    <meta property="og:image" content="/static/img/logo/omomega-icon218.png">
16
17    <link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,300i" rel="stylesheet">
18
19    <link rel="stylesheet" type="text/css" href="/static/css/font-awesome.min.css"/>
20	<link rel="stylesheet" type="text/css" href="/static/css/themify-icons.css"/>
21
22    <!-- Styles -->
23    <link href="/static/css/core.min.css" rel="stylesheet">
24    <link href="/static/css/app.min.css" rel="stylesheet">
25    <link href="/static/css/style.min.css" rel="stylesheet">
26    <link href="/static/css/om-style.css" rel="stylesheet">
27
28    <!-- Favicons -->
29    <link rel="apple-touch-icon" href="/static/img/logo-icon-light.png">
30    <link rel="icon" href="/static/img/logo-icon-light.png">
31
32    <!-- Google AdSense-->
33    
33<script data-ad-client="ca-pub-4642668630048169" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
33
34    <link href="/static/vendor/toastr/css/toastr.min.css" rel="stylesheet"/>
35
36
37    
38    
39    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
40    <link href="/static/css/omdoc/omdoc_home.css" rel="stylesheet">
41
42</head>
43<body>
44    
45    
46    
47    <!-- Topbar -->
48    
49<!-- top bar -->
50<header class="topbar">
51    <div class="topbar-left">
52
53        <a class="topbar-btn d-none d-md-block" href="/" title="Home" style="margin-top: 0">
54            <i class="material-icons fa fa-home"></i>
55        </a>
56
57        <a class="topbar-btn d-none d-md-block" href="#" data-provide="fullscreen tooltip" title="Fullscreen"
58           onclick="on_click_btn_full_screen();">
59            <i class="material-icons fullscreen-default">fullscreen</i>
60            <i class="material-icons fullscreen-active">fullscreen_exit</i>
61        </a>
62
63        
63<script>
64            function on_click_btn_full_screen() {
65                if (document.fullscreenElement) {
66                    document.exitFullscreen();
67                } else {
68                    document.documentElement.requestFullscreen().catch(err => {
69                        console.error('Lỗi khi vào chế độ fullscreen:', err);
70                    });
71                }
72            }
73        </script>
73
74
75        <div class="dropdown d-none d-md-block">
76            <span class="topbar-btn" data-toggle="dropdown"><i class="ti-layout-grid3-alt"></i></span>
77            <div class="dropdown-menu dropdown-grid">
78                <a class="dropdown-item" href="/">
79                    <span data-i8-icon="home"></span>
80                    <span class="title">Trang chá»§</span>
81                </a>
82                <a class="dropdown-item" href="/omdoc/">
83                    <span data-i8-icon="reading_ebook"></span>
84                    <span class="title">omDoc</span>
85                </a>
86                <a class="dropdown-item" href="/ombot/">
87                    <span data-i8-icon="safe"></span>
88                    <span class="title">omBot</span>
89                </a>
90                <a class="dropdown-item" href="/omtask/">
91                    <span data-i8-icon="calendar"></span>
92                    <span class="title">Công việc</span>
93                </a>
94                <a class="dropdown-item" href="/omchat/">
95                    <span data-i8-icon="sms"></span>
96                    <span class="title">Chat</span>
97                </a>
98                
99                <a class="dropdown-item" href="/ommap/">
100                    <span data-i8-icon="globe"></span>
101                    <span class="title">Map</span>
102                </a>
103                <a class="dropdown-item" href="/about/contact/">
104                    <span data-i8-icon="contacts"></span>
105                    <span class="title">Liên hệ</span>
106                </a>
107                <a class="dropdown-item" href="/rules/">
108                    <span data-i8-icon="plus"></span>
109                    <span class="title">Thông tin</span>
110                </a>
111            </div>
112        </div>
113
114        <div class="topbar-divider d-none d-md-block"></div>
115
116        <div class="lookup d-none d-xl-block topbar-search" id="om-search">
117            <input class="form-control" id="om-search-input" type="text" autocomplete="off">
118            <div class="lookup-placeholder">
119                <i class="ti-search"></i>
120                <span data-provide="typing"
121                      data-type="entered&lt;strong&gt; Overview&lt;/strong&gt;|entered&lt;strong&gt; Notifications&lt;/strong&gt;|entered&lt;strong&gt; Statistics&lt;/strong&gt;|entered&lt;strong&gt; Data layer&lt;/strong&gt;|Search&lt;strong&gt; ...&lt;/strong&gt;"
122                      data-loop="false" data-type-speed="90" data-back-speed="50" data-show-cursor="false">Search<strong> ...</strong></span>
123            </div>
124        </div>
125    </div>
126
127    <div class="topbar-right">
128        <ul class="topbar-btns">
129            <li class="">
130                <!-- Language switcher -->
131                
132
133<!-- Language switcher -->
134<span class="topbar-btn om-lang-btn" data-toggle="dropdown" data-provide="tooltip"
135      data-tooltip-color="light" title="Language">
136    <i class="ti-world"></i>
137    <span class="om-lang-code d-none d-md-inline-block">EN</span>
138    <i class="ion-android-arrow-down"></i>
139</span>
140<div class="dropdown-menu dropdown-menu-right om-lang-menu">
141    <h6 class="dropdown-header">Language</h6>
142
143    <form action="/i18n/setlang/" method="post" class="om-lang-form">
144        <input type="hidden" name="csrfmiddlewaretoken" value="U7ZuojYWq3QyruS54JxcqKbYsAyBwxndLZlfG2t3jnDhl0nHtgSyGRkvQcX1jbw4">
145        <input type="hidden" name="next" value="/omdoc/">
146        <input type="hidden" name="language" value="vi">
147        <button type="submit" class="dropdown-item om-lang-item ">
148            <span class="om-lang-flag">🇻🇳</span>
149            <span class="om-lang-name">Tiếng Việt</span>
150            
151        </button>
152    </form>
153
154    <form action="/i18n/setlang/" method="post" class="om-lang-form">
155        <input type="hidden" name="csrfmiddlewaretoken" value="U7ZuojYWq3QyruS54JxcqKbYsAyBwxndLZlfG2t3jnDhl0nHtgSyGRkvQcX1jbw4">
156        <input type="hidden" name="next" value="/omdoc/">
157        <input type="hidden" name="language" value="en">
158        <button type="submit" class="dropdown-item om-lang-item active">
159            <span class="om-lang-flag">🇬🇧</span>
160            <span class="om-lang-name">English</span>
161            <i class="ti-check om-lang-check"></i>
162        </button>
163    </form>
164</div>
165
166<style>
167    /* Language switcher — match the topbar's clean, professional look */
168    .om-lang-btn { display: inline-flex; align-items: center; gap: 4px; }
169    .om-lang-code { font-size: 13px; font-weight: 600; letter-spacing: 0.5px; }
170    .om-lang-menu { min-width: 190px; padding: 6px 0; }
171    .om-lang-form { margin: 0; }
172    .om-lang-item {
173        display: flex; align-items: center; gap: 10px; width: 100%;
174        background: none; border: 0; text-align: left; cursor: pointer;
175        padding: 8px 16px; font-size: 14px; color: #333;
176    }
177    .om-lang-item:hover { background-color: #f5f7fa; }
178    .om-lang-item.active { color: #36753f; font-weight: 600; }
179    .om-lang-flag { font-size: 16px; line-height: 1; }
180    .om-lang-name { flex-grow: 1; }
181    .om-lang-check { color: #36753f; }
182</style>
183<!-- END Language switcher -->
184
185                <!-- END Language switcher -->
186            </li>
187            <li class="">
188                <!-- Global quickview -->
189                
190<!-- Global quickview -->
191<span class="topbar-btn" data-toggle="dropdown"><i class="ti-align-right"></i></span>
192<div class="dropdown-menu dropdown-menu-right">
193    <div class="media-list media-list-divided media-list-hover media-list-xs scrollable"
194         style="height: 482px">
195        <i class="nav-link">Contents</i>
196        <div class="quickview-body">
197            <div class="tab-content">
198                <div class="tab-pane fade active show">
199                    <div class="media-list media-list-xs media-list-hover media-list-divided">
200                        
201
202                        <a class="media media-vertical bb-0" href="/">
203                            <h6>Home</h6>
204                            <small>omomega.com</small>
205                        </a>
206
207                        <div class="divider text-uppercase fw-500 fs-10 ls-2 mb-0 mt-0">Products</div>
208
209                        <a class="media media-vertical bb-0" href="/omdoc/">
210                            <h6>omDoc</h6>
211                            <small>Knowledge sharing</small>
212                        </a>
213
214                        <a class="media media-vertical" href="/ombot/">
215                            <h6>omBot</h6>
216                            <small>AI tools</small>
217                        </a>
218
219                        <a class="media media-vertical" href="/omchat/">
220                            <h6>omChat</h6>
221                            <small>Internal messaging</small>
222                        </a>
223
224                        <a class="media media-vertical" href="/ommap/">
225                            <h6>omMap</h6>
226                            <small>Personal map</small>
227                        </a>
228
229                        <a class="media media-vertical" href="/omtask/">
230                            <h6>omTask</h6>
231                            <small>Task management</small>
232                        </a>
233
234                        
235
236                        <div class="divider text-uppercase fw-500 fs-10 ls-2 mb-0 mt-0">Statistics</div>
237
238                        <a class="media media-vertical" href="">
239                            <h6>Online</h6>
240                            <small>3</small>
241                        </a>
242
243                        <a class="media media-vertical" href="">
244                            <h6>Total visits</h6>
245                            <small>1260644</small>
246                        </a>
247
248                        <div class="divider text-uppercase fw-500 fs-10 ls-2 mb-0 mt-0">Terms</div>
249
250                        <a class="media media-vertical" href="/rules/">
251                            <h6>Terms</h6>
252                            <small>OmOmega's commitment</small>
253                        </a>
254
255                        <div class="divider text-uppercase fw-500 fs-10 ls-2 mb-0 mt-0">Contact</div>
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272                        <a class="media media-vertical" href="/about/contact/">
273                            <h6>Contact</h6>
274                            <small>Contact info, send feedback</small>
275                        </a>
276                    </div>
277                </div>
278            </div>
279        </div>
280    </div>
281</div>
282<!-- END Global quickview -->
283
284                <!-- END Global quickview -->
285            </li>
286            <li class="d-none d-xl-block">
287                <!-- Setting -->
288                
289<!-- Global quickview -->
290<span class="topbar-btn" data-toggle="dropdown"><i class="ti-settings"></i></span>
291<div class="dropdown-menu dropdown-menu-right">
292    <div class="media-list media-list-divided media-list-hover media-list-xs scrollable"
293         style="height: 482px">
294        <div class="media">
295            <div class="media-body">
296                <p><strong>Header bar color</strong></p>
297                <p>You can change the color of the header bar</p>
298
299                <div class="color-selector color-selector-sm mt-12">
300                    <label class="inverse">
301                        <input type="radio" value="default" name="global-topbar-color" checked>
302                        <span style="background-color:#ffffff"></span>
303                    </label>
304
305                    <label>
306                        <input type="radio" value="33cabb" name="global-topbar-color">
307                        <span style="background-color:#33cabb"></span>
308                    </label>
309
310                    <label>
311                        <input type="radio" value="48b0f7" name="global-topbar-color">
312                        <span style="background-color:#48b0f7"></span>
313                    </label>
314
315                    <label>
316                        <input type="radio" value="faa64b" name="global-topbar-color">
317                        <span style="background-color:#faa64b"></span>
318                    </label>
319
320                    <label>
321                        <input type="radio" value="f96868" name="global-topbar-color">
322                        <span style="background-color:#f96868"></span>
323                    </label>
324
325                    <label>
326                        <input type="radio" value="926dde" name="global-topbar-color">
327                        <span style="background-color:#926dde"></span>
328                    </label>
329
330                    <label>
331                        <input type="radio" value="57c7d4" name="global-topbar-color">
332                        <span style="background-color:#57c7d4"></span>
333                    </label>
334
335                    <label>
336                        <input type="radio" value="3f4a59" name="global-topbar-color">
337                        <span style="background-color:#3f4a59"></span>
338                    </label>
339                </div>
340            </div>
341        </div>
342    </div>
343</div>
344<!-- END Global quickview -->
345                <!-- End Setting -->
346            </li>
347        </ul>
348
349        <div class="topbar-divider"></div>
350        <div>
351            
352                <ul class="topbar-btns">
353                    <li>
354                    <span class="topbar-btn">
355                        <a href="#" onclick="om_login();"><i class="ti-user"></i></a></span>
356                    </li>
357                </ul>
358            
359        </div>
360        <div class="d-none d-xl-block">
361            <a href="#" target="_blank" data-toggle="modal" data-target="#modal_download_app">
362                <img src="/static/img/logo/download_appstore.jpg" width="80" alt="OmOmega">
363            </a>
364        </div>
365
366        <div class="topbar-divider d-none d-md-block"></div>
367        <!-- Copy current time button -->
368        <span id="btn-copy-time" class="d-none d-md-block" onclick="copyCurrentTime(event)"
369              title="Copy time"
370              style="cursor:pointer; display:inline-flex !important; align-items:center; margin-right:4px; transition: opacity 0.2s;">
371            <i class="ti-files" style="color:#8aba6e; font-size:14px;"></i>
372        </span>
373        <div onclick="openOmCalendarModal()" class="d-none d-md-block">
374            <span><b id="date-clock" class="text-success fs-22 fw-500"></b>, </span>
375            <span>(Lunar:<b id="lunar-date-clock" class="text-info fs-18 fw-500"></b>
376                
377                )
378            </span>
379            
379<script src="/static/js/utils/lunar.js"></script>
379
380            
380<script>
381                document.addEventListener("DOMContentLoaded", () => {
382                    setInterval(function () {
383                        updateTime();
384                    }, 1000);
385                });
386
387                function addZero(i) {
388                    if (i < 10) {
389                        i = "0" + i;
390                    }
391                    return i;
392                }
393
394                function updateTime() {
395                    const d = new Date();
396                    const day = d.getDay();
397                    const date = d.getDate();
398                    const month = d.getMonth() + 1;
399                    const year = d.getFullYear();
400                    const h = d.getHours();
401                    const m = d.getMinutes();
402                    const s = d.getSeconds();
403
404                    let time_str = `${addZero(h)}:${addZero(m)}:${addZero(s)}`;
405                    $("#time-clock").text(time_str);
406                    let date_str = `${date}/${month}/${year}`;
407                    $("#date-clock").text(date_str);
408
409                    const days_of_week = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
410                    $("#day-clock").text(days_of_week[day]);
411
412                    const solar = Solar.fromYmd(year, month, date);
413                    const lunar = solar.getLunar();
414                    const lunar_day = lunar.getDay();
415                    var lunar_month = lunar.getMonth();
416                    const lunar_str = `${lunar_day}/${lunar_month}/${lunar.getYear()}`;
417                    $("#lunar-date-clock").text(lunar_str);
418
419                    
420                }
421
422                // Copy the current time display text to clipboard
423                function copyCurrentTime(event) {
424                    event.stopPropagation();
425                    const day = $("#day-clock").text();
426                    const time = $("#time-clock").text();
427                    const date = $("#date-clock").text();
428                    const lunar = $("#lunar-date-clock").text();
429                    let copyText = `${day}, ${time} ${date}, (Lunar: ${lunar}`;
430                    
431                    copyText += ')';
432                    navigator.clipboard.writeText(copyText).then(function() {
433                        // Visual feedback: briefly change icon to checkmark
434                        const $btn = $('#btn-copy-time');
435                        const $icon = $btn.find('i');
436                        $icon.removeClass('ti-files').addClass('ti-check');
437                        $icon.css('color', '#4CAF50');
438                        setTimeout(function() {
439                            $icon.removeClass('ti-check').addClass('ti-files');
440                            $icon.css('color', '#8aba6e');
441                        }, 1200);
442                        if (typeof toastr !== 'undefined') {
443                            toastr.success(copyText, 'Copied!');
444                        }
445                    });
446                }
447            </script>
447
448        </div>
449        <div>
450            <span><b id="day-clock" class="text-primary fs-18 fw-500"></b>, </span>
451            <span><b id="time-clock" class="fs-26 fw-500" style="color:#819824;"></b> &nbsp;</span>
452        </div>
453    </div>
454</header>
455
456<!-- OmOmega Search Popup -->
457<div class="om-search-overlay" id="omSearchOverlay"></div>
458<div class="om-search-popup" id="omSearchPopup">
459    <div class="om-search-popup-header">
460        <h6>Kết quả cho <strong id="omSearchQuery"></strong></h6>
461        <button class="om-search-popup-close" id="omSearchClose">&times;</button>
462    </div>
463    <div class="om-search-results" id="omSearchResults"></div>
464    <div class="om-search-footer">
465        <span><kbd>↑</kbd> <kbd>↓</kbd> Chọn &nbsp; <kbd>Enter</kbd> Mở &nbsp; <kbd>Esc</kbd> Đóng</span>
466        <span>OmOmega Search</span>
467    </div>
468</div>
469
470<div class="modal modal-center fade" id="modal_download_app" tabindex="-1">
471    <div class="modal-dialog">
472        <div class="modal-content">
473            <div class="modal-header">
474                <h4 class="modal-title" id="myModalLabel">Tải ứng dụng <b
475                        style="color: darkgreen; font-weight: bold;" href="#">OmOmega</b></h4>
476                <button type="button" class="close" data-dismiss="modal">
477                    <span aria-hidden="true">&times;</span>
478                </button>
479            </div>
480            <div class="modal-body">
481                <div class="card">
482                    <h5>Quét mã QR để tải ứng dụng (sử dụng camera của điện thoại)</h5>
483                    <!-- Nav tabs -->
484                    <ul class="nav nav-tabs nav-tabs-danger nav-justified" role="tablist">
485                        <li class="nav-item">
486                            <a class="nav-link active" data-toggle="tab" href="#ios_store_link" role="tab">iOS</a>
487                        </li>
488                        <li class="nav-item">
489                            <a class="nav-link" data-toggle="tab" href="#android_store_link" role="tab">Android</a>
490                        </li>
491                    </ul>
492
493                    <!-- Tab panes -->
494                    <div class="card-body tab-content">
495                        <div class="tab-pane fade active show" id="ios_store_link">
496                            <h6>Quét mã QR hoặc truy cập <a style="color: darkgreen; font-weight: bold;" href="https://apps.apple.com/vn/app/omomega/id6612094647" target="_blank">App Store</a></h6>
497                            <div class="card-body carousel-item active">
498                              <img src="/static/img/qr/mobile_app/omomega_ios_app_store.jpg" alt="OmOmega" width="289">
499                            </div>
500                        </div>
501                        <div class="tab-pane fade" id="android_store_link">
502                            <h6>Quét mã QR hoặc truy cập <a style="color: darkgreen; font-weight: bold;" href="https://play.google.com/store/apps/details?id=com.omomega.OmOmegaApp" target="_blank">Google Play</a></h6>
503                            <div class="card-body carousel-item active">
504                              <img src="/static/img/qr/mobile_app/omomega_android_google_play.png" alt="OmOmega" width="289">
505                            </div>
506                        </div>
507                    </div>
508                </div>
509            </div>
510            <div class="modal-footer">
511                <button type="button" class="btn btn-bold btn-pure btn-secondary" data-dismiss="modal">Đóng</button>
512            </div>
513        </div>
514    </div>
515</div>
516
517
518    
518<script type="text/javascript">
519        function om_login() {
520            url_path = window.location.pathname;
521            window.location.href = '/user/login?next=' + url_path
522        }
523    </script>
523
524
525<!-- OmOmega Search Script -->
526<script>
527(function() {
528    var omSearchData = [];
529    var omActiveIdx = -1;
530    var omPopupOpen = false;
531
532    // Load search data once
533    fetch('/static/data/json/om-search-data.json')
534        .then(function(r) { return r.json(); })
535        .then(function(d) { omSearchData = d; })
536        .catch(function(e) { console.warn('OmSearch: data load failed', e); });
537
538    // Helper: normalize Vietnamese text for matching
539    function omNorm(s) {
540        return (s || '').toLowerCase()
541            .normalize('NFD').replace(/[\u0300-\u036f]/g, '')
542            .replace(/đ/g, 'd').replace(/Đ/g, 'D');
543    }
544
545    // Helper: highlight matching text in a string
546    function omHighlight(text, queryWords) {
547        if (!queryWords.length) return text;
548        var escaped = queryWords.map(function(w) {
549            return w.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
550        });
551        var re = new RegExp('(' + escaped.join('|') + ')', 'gi');
552        return text.replace(re, '<mark>$1</mark>');
553    }
554
555    // Search and filter data
556    function omDoSearch(query) {
557        var normQ = omNorm(query);
558        var words = normQ.split(/\s+/).filter(function(w) { return w.length > 0; });
559        if (!words.length) return [];
560
561        var scored = [];
562        omSearchData.forEach(function(item) {
563            var normTitle = omNorm(item.title);
564            var normTokens = omNorm(item.tokens);
565            var normDesc = omNorm(item.description);
566            var allText = normTitle + ' ' + normTokens + ' ' + normDesc;
567
568            // All query words must match somewhere
569            var allMatch = words.every(function(w) { return allText.indexOf(w) >= 0; });
570            if (!allMatch) return;
571
572            // Scoring: title match > token match > desc match
573            var score = 0;
574            words.forEach(function(w) {
575                if (normTitle.indexOf(w) >= 0) score += 10;
576                if (normTokens.indexOf(w) >= 0) score += 5;
577                if (normDesc.indexOf(w) >= 0) score += 2;
578            });
579            // Exact title start bonus
580            if (normTitle.indexOf(normQ) === 0) score += 20;
581
582            scored.push({ item: item, score: score });
583        });
584
585        scored.sort(function(a, b) { return b.score - a.score; });
586        return scored.map(function(s) { return s.item; });
587    }
588
589    // Render results into popup
590    function omRenderResults(results, query) {
591        var container = document.getElementById('omSearchResults');
592        var queryWords = query.split(/\s+/).filter(function(w) { return w.length > 0; });
593        container.innerHTML = '';
594
595        if (!results.length) {
596            container.innerHTML = '<div class="om-search-empty">' +
597                '<i class="ti-search"></i>' +
598                '<p>Không tìm thấy kết quả cho "' + query + '"</p></div>';
599            return;
600        }
601
602        // Group by category
603        var groups = {};
604        var catOrder = [];
605        results.forEach(function(item) {
606            var cat = item.category || 'Khác';
607            if (!groups[cat]) { groups[cat] = []; catOrder.push(cat); }
608            groups[cat].push(item);
609        });
610
611        var globalIdx = 0;
612        catOrder.forEach(function(cat) {
613            var label = document.createElement('div');
614            label.className = 'om-search-category-label';
615            label.textContent = cat;
616            container.appendChild(label);
617
618            groups[cat].forEach(function(item) {
619                var el = document.createElement('a');
620                el.className = 'om-search-item';
621                el.href = item.url;
622                el.setAttribute('data-idx', globalIdx);
623                el.innerHTML =
624                    '<div class="om-search-item-icon"><i class="' + item.icon + '"></i></div>' +
625                    '<div class="om-search-item-info">' +
626                        '<div class="om-search-item-title">' + omHighlight(item.title, queryWords) + '</div>' +
627                        '<div class="om-search-item-desc">' + omHighlight(item.description, queryWords) + '</div>' +
628                    '</div>' +
629                    '<span class="om-search-item-arrow"><i class="ti-angle-right"></i></span>';
630                el.addEventListener('click', function(e) {
631                    // Navigate on click (default <a> behavior)
632                    omClosePopup();
633                });
634                container.appendChild(el);
635                globalIdx++;
636            });
637        });
638    }
639
640    // Open popup
641    function omOpenPopup(query) {
642        var results = omDoSearch(query);
643        document.getElementById('omSearchQuery').textContent = query;
644        omRenderResults(results, query);
645
646        document.getElementById('omSearchOverlay').classList.add('show');
647        document.getElementById('omSearchPopup').classList.add('show');
648        omActiveIdx = -1;
649        omPopupOpen = true;
650    }
651
652    // Close popup
653    function omClosePopup() {
654        document.getElementById('omSearchOverlay').classList.remove('show');
655        document.getElementById('omSearchPopup').classList.remove('show');
656        omActiveIdx = -1;
657        omPopupOpen = false;
658    }
659
660    // Keyboard navigation inside popup
661    function omNavigateResults(dir) {
662        var items = document.querySelectorAll('#omSearchResults .om-search-item');
663        if (!items.length) return;
664        items.forEach(function(el) { el.classList.remove('active'); });
665        omActiveIdx += dir;
666        if (omActiveIdx < 0) omActiveIdx = items.length - 1;
667        if (omActiveIdx >= items.length) omActiveIdx = 0;
668        items[omActiveIdx].classList.add('active');
669        items[omActiveIdx].scrollIntoView({ block: 'nearest' });
670    }
671
672    function omSelectActive() {
673        var items = document.querySelectorAll('#omSearchResults .om-search-item');
674        if (omActiveIdx >= 0 && omActiveIdx < items.length) {
675            window.location.href = items[omActiveIdx].getAttribute('href');
676            omClosePopup();
677        }
678    }
679
680    // Bind events after DOM ready
681    document.addEventListener('DOMContentLoaded', function() {
682        var input = document.getElementById('om-search-input');
683        if (!input) return;
684
685        // Handle placeholder visibility
686        var placeholder = input.parentElement.querySelector('.lookup-placeholder');
687
688        input.addEventListener('focus', function() {
689            if (placeholder) placeholder.querySelector('span').style.opacity = '0';
690        });
691        input.addEventListener('blur', function() {
692            if (!this.value && placeholder) placeholder.querySelector('span').style.opacity = '1';
693        });
694        input.addEventListener('input', function() {
695            if (placeholder) {
696                placeholder.querySelector('span').style.opacity = this.value ? '0' : '1';
697            }
698        });
699
700        // Enter to search
701        input.addEventListener('keydown', function(e) {
702            if (e.key === 'Enter') {
703                e.preventDefault();
704                var q = this.value.trim();
705                if (q.length > 0) {
706                    omOpenPopup(q);
707                    this.blur();
708                }
709            }
710        });
711
712        // Global keyboard handler
713        document.addEventListener('keydown', function(e) {
714            if (!omPopupOpen) return;
715            if (e.key === 'Escape') {
716                e.preventDefault();
717                omClosePopup();
718                input.focus();
719            } else if (e.key === 'ArrowDown') {
720                e.preventDefault();
721                omNavigateResults(1);
722            } else if (e.key === 'ArrowUp') {
723                e.preventDefault();
724                omNavigateResults(-1);
725            } else if (e.key === 'Enter' && omActiveIdx >= 0) {
726                e.preventDefault();
727                omSelectActive();
728            }
729        });
730
731        // Close on overlay click
732        document.getElementById('omSearchOverlay').addEventListener('click', omClosePopup);
733        document.getElementById('omSearchClose').addEventListener('click', omClosePopup);
734    });
735})();
736</script>
736
737<!-- END Topbar -->
738
739
740<style>
741    .om-cal-wrapper {
742        padding: 10px;
743        background-color: #fcfcfc;
744    }
745
746    .om-cal-grid {
747        display: grid;
748        grid-template-columns: repeat(7, 1fr);
749        gap: 6px;
750    }
751
752    .om-cal-header-day {
753        text-align: center;
754        font-weight: 600;
755        color: #555;
756        padding-bottom: 8px;
757        font-size: 14px;
758    }
759
760    .om-cal-day {
761        border: 1px solid transparent;
762        border-radius: 8px;
763        padding: 6px;
764        text-align: center;
765        cursor: pointer;
766        min-height: 70px;
767        display: flex;
768        flex-direction: column;
769        align-items: center;
770        justify-content: center;
771        background-color: #ffffff;
772        box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
773        transition: all 0.2s ease-in-out;
774    }
775
776    .om-cal-day:hover {
777        background-color: #f8f9fa;
778        transform: translateY(-2px);
779        box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
780    }
781
782    .om-cal-day.is-today {
783        border: 3px solid #0ea89e;
784        box-shadow: 0 0 0 2px rgba(14, 168, 158, 0.25), 0 2px 6px rgba(14, 168, 158, 0.2);
785    }
786
787    /* OmOmega App Primary Style */
788    .om-cal-day.is-weekend {
789        background-color: #FBCCCC;
790    }
791
792    .om-cal-day.is-holiday {
793        background-color: #FFA5A5;
794    }
795
796    .om-cal-gregorian {
797        font-size: 16px;
798        font-weight: bold;
799        color: #333;
800        line-height: 1.2;
801    }
802
803    .om-cal-lunar {
804        font-size: 11px;
805        color: #555;
806        margin-top: 2px;
807    }
808
809    .om-cal-bm {
810        font-size: 11px;
811        color: #007bff;
812        font-weight: bold;
813        text-transform: capitalize;
814    }
815
816    .om-cal-empty {
817        background: transparent;
818        box-shadow: none;
819        cursor: default;
820    }
821
822    .om-cal-empty:hover {
823        transform: none;
824        background: transparent;
825        box-shadow: none;
826    }
827
828    /* Bát Môn Wheel Styles */
829    .om-bm-wheel {
830        max-height: 180px;
831        overflow-y: auto;
832        border: 1px solid #eee;
833        border-radius: 8px;
834        padding: 4px 0;
835        background: #fafafa;
836        scroll-behavior: smooth;
837    }
838
839    .om-bm-wheel::-webkit-scrollbar {
840        width: 6px;
841    }
842
843    .om-bm-wheel::-webkit-scrollbar-thumb {
844        background: #ccc;
845        border-radius: 4px;
846    }
847
848    .om-bm-wheel-item {
849        padding: 8px 12px;
850        border-bottom: 1px solid #f1f1f1;
851        text-align: center;
852        font-size: 14px;
853        color: #333;
854    }
855
856    .om-bm-wheel-item:last-child {
857        border-bottom: none;
858    }
859
860    .om-bm-wheel-item.active {
861        background-color: #e6f7ff;
862        font-weight: bold;
863        color: #007bff;
864        border-radius: 4px;
865        margin: 0 4px;
866    }
867
868    /* BM Meaning Card Styles */
869    .om-bm-meaning-card {
870        width: 100%;
871        padding: 10px 12px;
872        margin-bottom: 10px;
873        background: #ffffff;
874        border: 1px solid #e8e8e8;
875        border-radius: 10px;
876        box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
877    }
878
879    .om-bm-meaning-label {
880        font-size: 11px;
881        font-weight: 600;
882        color: #888;
883        text-transform: uppercase;
884        letter-spacing: 0.5px;
885        margin-bottom: 4px;
886    }
887
888    .om-bm-meaning-header {
889        display: flex;
890        align-items: center;
891        justify-content: space-between;
892        margin-bottom: 4px;
893    }
894
895    .om-bm-meaning-title {
896        font-size: 15px;
897        font-weight: 700;
898        color: #007bff;
899        text-transform: capitalize;
900    }
901
902    .om-bm-meaning-window {
903        font-size: 12px;
904        color: #888;
905        white-space: nowrap;
906    }
907
908    .om-bm-meaning-text {
909        font-size: 13px;
910        color: #333;
911        line-height: 1.4;
912        margin: 0;
913    }
914
915    .om-bm-meaning-text.is-empty {
916        color: #aaa;
917        font-style: italic;
918    }
919</style>
920
921<div class="modal fade" id="modal_monthly_calendar" tabindex="-1" role="dialog" aria-hidden="true">
922    <div class="modal-dialog modal-dialog-centered" style="max-width: 480px;" role="document">
923        <div class="modal-content om-cal-wrapper">
924            <div class="modal-header d-flex align-items-center justify-content-between pb-3 border-bottom-0">
925                <button class="btn btn-sm btn-light" onclick="omCalPrevMonth()"><i class="fa fa-chevron-left"></i>
926                </button>
927                <div class="d-flex align-items-center">
928                    <select id="om_cal_month" class="form-control form-control-sm mx-1"
929                            style="width: auto; font-weight: bold; cursor: pointer;"
930                            onchange="omCalChangeSelect()"></select>
931                    <select id="om_cal_year" class="form-control form-control-sm mx-1"
932                            style="width: auto; font-weight: bold; cursor: pointer;"
933                            onchange="omCalChangeSelect()"></select>
934                </div>
935                <div>
936                    <button class="btn btn-sm btn-info" onclick="omCalGoToday()" title="Hôm nay"><i
937                            class="fa fa-crosshairs"></i></button>
938                    <button class="btn btn-sm btn-light ml-1" onclick="omCalNextMonth()"><i
939                            class="fa fa-chevron-right"></i></button>
940                </div>
941            </div>
942
943            <div class="modal-body pt-0">
944                <div class="om-cal-grid" style="padding-bottom: 0;">
945                    <div class="om-cal-header-day">Mo</div>
946                    <div class="om-cal-header-day">Tu</div>
947                    <div class="om-cal-header-day">We</div>
948                    <div class="om-cal-header-day">Th</div>
949                    <div class="om-cal-header-day">Fr</div>
950                    <div class="om-cal-header-day">Sa</div>
951                    <div class="om-cal-header-day">Su</div>
952                </div>
953                <div class="om-cal-grid" id="om_cal_days_container" style="padding-top: 0;"></div>
954                
955            </div>
956        </div>
957    </div>
958</div>
959
960<div class="modal fade" id="modal_om_cal_day_details" tabindex="-1" role="dialog" aria-hidden="true">
961    <div class="modal-dialog modal-dialog-centered" style="max-width: 360px;" role="document">
962        <div class="modal-content">
963            <div class="modal-header pb-2 border-bottom-0">
964                <h5 class="modal-title w-100 text-center font-weight-bold">Chi tiết ngày</h5>
965            </div>
966            <div class="modal-body pt-1">
967                <p class="mb-1"><strong>Thứ:</strong> <span id="om_cal_detail_weekday"></span></p>
968                <p class="mb-1"><strong>Ngày dương lịch:</strong> <span id="om_cal_detail_gregorian"></span></p>
969                <p class="mb-1"><strong>Ngày âm lịch:</strong> <span id="om_cal_detail_lunar"></span></p>
970
971                
972            </div>
973            <div class="modal-footer justify-content-center border-top-0 pt-0">
974                <button type="button" class="btn btn-primary" id="btn_om_cal_add_task" style="display: none;">Đầu việc</button>
975                <button type="button" class="btn btn-secondary" data-dismiss="modal">Đóng</button>
976            </div>
977        </div>
978    </div>
979</div>
980
981<script>
982    const omCalBmNames = ['hưu', 'sanh', 'thương', 'đổ', 'kiển', 'tử', 'kinh', 'khai'];
983    const omCalBmOffsets = [-1, 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 0, 0];
984    const omCalGregHolidays = ["1/1", "30/4", "1/5", "1/9", "2/9"];
985    const omCalLunarHolidays = ["1/1", "2/1", "3/1", "10/3"];
986    const omCalWeekdays = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
987    const omCalBmTimeByMonths = [[-1, -1], [0, 30], [0, 40], [0, 50], [1, 0], [1, 10], [1, 0], [0, 50], [0, 40], [0, 30], [0, 20], [0, 10], [0, 20]];
988
989    // BM Meaning store — fetched once from /omapi/bm-meanings/ and cached
990    let omCalBmMeanings = {};
991    let omCalBmMeaningsLoaded = false;
992    let omCalBmMeaningTimer = null;
993
994    function omCalFetchBmMeanings() {
995        fetch('/omapi/bm-meanings/')
996            .then(r => r.json())
997            .then(data => {
998                if (data.status === 1 && data.items) {
999                    omCalBmMeanings = data.items;
1000                    omCalBmMeaningsLoaded = true;
1001                    omCalUpdateBmMeaningCard();
1002                }
1003            })
1004            .catch(() => {});
1005    }
1006
1007    /**
1008     * Compute the current BM hour segment for right now:
1009     * returns {dayGate, hourGate, startStr, endStr}
1010     */
1011    function omCalGetCurrentBmSegment() {
1012        let now = new Date();
1013        let solarD = Solar.fromYmd(now.getFullYear(), now.getMonth() + 1, now.getDate());
1014        let lunarD = solarD.getLunar();
1015        let lDay = lunarD.getDay();
1016        let absLMonth = Math.abs(lunarD.getMonth());
1017
1018        // Day gate index
1019        let bmDayIndex = (omCalBmOffsets[absLMonth] + lDay - 1) % 8;
1020        let dayGate = omCalBmNames[bmDayIndex];
1021
1022        // Hour segment
1023        let startMins = omCalBmTimeByMonths[absLMonth][0] * 60 + omCalBmTimeByMonths[absLMonth][1];
1024        let currentMins = now.getHours() * 60 + now.getMinutes();
1025        let dist = (currentMins - startMins + 1440) % 1440;
1026        let hourIndex = Math.floor(dist / 120) % 12;
1027
1028        let hourGate = omCalBmNames[(bmDayIndex + hourIndex) % 8];
1029
1030        let segStart = (startMins + hourIndex * 120 + 1440) % 1440;
1031        let segEnd = (segStart + 120) % 1440;
1032        let h1 = Math.floor(segStart / 60).toString().padStart(2, '0');
1033        let m1 = (segStart % 60).toString().padStart(2, '0');
1034        let h2 = Math.floor(segEnd / 60).toString().padStart(2, '0');
1035        let m2 = (segEnd % 60).toString().padStart(2, '0');
1036
1037        return {
1038            dayGate: dayGate,
1039            hourGate: hourGate,
1040            startStr: `${h1}:${m1}`,
1041            endStr: `${h2}:${m2}`
1042        };
1043    }
1044
1045    function omCalUpdateBmMeaningCard() {
1046        let titleEl = document.getElementById('om_cal_bm_meaning_title');
1047        if (!titleEl) return; // BM not enabled for this user
1048
1049        let seg = omCalGetCurrentBmSegment();
1050
1051        // Title: capitalize first letter of dayGate + space + hourGate
1052        let title = seg.dayGate.charAt(0).toUpperCase() + seg.dayGate.slice(1) + ' ' + seg.hourGate;
1053        titleEl.innerText = title;
1054
1055        document.getElementById('om_cal_bm_meaning_window').innerText = `${seg.startStr} – ${seg.endStr}`;
1056
1057        let textEl = document.getElementById('om_cal_bm_meaning_text');
1058        let key = `${seg.dayGate}|${seg.hourGate}`;
1059        let meaning = omCalBmMeanings[key];
1060
1061        if (meaning && meaning.trim()) {
1062            textEl.innerText = meaning;
1063            textEl.classList.remove('is-empty');
1064        } else if (omCalBmMeaningsLoaded) {
1065            textEl.innerText = 'Chưa có nội dung';
1066            textEl.classList.add('is-empty');
1067        } else {
1068            textEl.innerText = 'Đang tải...';
1069            textEl.classList.add('is-empty');
1070        }
1071    }
1072
1073    // Start BM Meaning auto-update timer and fetch on modal show
1074    document.addEventListener('DOMContentLoaded', () => {
1075        let bmModal = document.getElementById('modal_monthly_calendar');
1076        if (bmModal) {
1077            $(bmModal).on('shown.bs.modal', () => {
1078                // Fetch meanings if not loaded yet
1079                if (!omCalBmMeaningsLoaded) omCalFetchBmMeanings();
1080                // Immediately update the card
1081                omCalUpdateBmMeaningCard();
1082                // Start 1-minute ticker
1083                if (omCalBmMeaningTimer) clearInterval(omCalBmMeaningTimer);
1084                omCalBmMeaningTimer = setInterval(omCalUpdateBmMeaningCard, 60000);
1085            });
1086            $(bmModal).on('hidden.bs.modal', () => {
1087                // Stop ticker when modal is closed
1088                if (omCalBmMeaningTimer) {
1089                    clearInterval(omCalBmMeaningTimer);
1090                    omCalBmMeaningTimer = null;
1091                }
1092            });
1093        }
1094    });
1095
1096    let omCalCurYear = new Date().getFullYear();
1097    let omCalCurMonth = new Date().getMonth() + 1;
1098
1099    // Initialize Dropdowns
1100    document.addEventListener("DOMContentLoaded", () => {
1101        let mSelect = document.getElementById("om_cal_month");
1102        for (let i = 1; i <= 12; i++) {
1103            mSelect.add(new Option("Month " + i, i));
1104        }
1105        let ySelect = document.getElementById("om_cal_year");
1106        for (let i = 1900; i <= 2100; i++) {
1107            ySelect.add(new Option(i, i));
1108        }
1109    });
1110
1111    function omCalCheckLunarHoliday(ld, lm, y, m, d) {
1112        if (omCalLunarHolidays.includes(`${ld}/${Math.abs(lm)}
1112`)) return true;
1113        if (Math.abs(lm) === 12) {
1114            let nextD = new Date(y, m - 1, d + 1);
1115            let nextSolar = Solar.fromYmd(nextD.getFullYear(), nextD.getMonth() + 1, nextD.getDate());
1116            if (Math.abs(nextSolar.getLunar().getMonth()) !== 12) return true;
1117        }
1118        return false;
1119    }
1120
1121    function renderOmCalendar(year, month) {
1122        omCalCurYear = year;
1123        omCalCurMonth = month;
1124        document.getElementById("om_cal_month").value = month;
1125        document.getElementById("om_cal_year").value = year;
1126
1127        let container = document.getElementById("om_cal_days_container");
1128        container.innerHTML = "";
1129
1130        let firstDay = new Date(year, month - 1, 1);
1131        let daysInMonth = new Date(year, month, 0).getDate();
1132
1133        let offset = firstDay.getDay() - 1;
1134        if (offset < 0) offset = 6;
1135
1136        let today = new Date();
1137
1138        for (let i = 0; i < offset; i++) {
1139            container.innerHTML += `<div class="om-cal-day om-cal-empty"></div>`;
1140        }
1141
1142        for (let i = 1; i <= daysInMonth; i++) {
1143            let curD = new Date(year, month - 1, i);
1144            let solarD = Solar.fromYmd(year, month, i);
1145            let lunarD = solarD.getLunar();
1146            let lDay = lunarD.getDay();
1147            let lMonth = lunarD.getMonth();
1148            let absLMonth = Math.abs(lMonth);
1149
1150            let isToday = (year === today.getFullYear() && month === today.getMonth() + 1 && i === today.getDate());
1151            let isWeekend = (curD.getDay() === 0 || curD.getDay() === 6);
1152            let isHol = omCalGregHolidays.includes(`${i}/${month}`) || omCalCheckLunarHoliday(lDay, absLMonth, year, month, i);
1153
1154            let bmIndex = (omCalBmOffsets[absLMonth] + lDay - 1) % 8;
1155            let bmName = omCalBmNames[bmIndex];
1156
1157            let classes = "om-cal-day";
1158            if (isToday) classes += " is-today";
1159            if (isHol) classes += " is-holiday";
1160            else if (isWeekend) classes += " is-weekend";
1161
1162            let lStr = lMonth < 0 ? `${lDay}/${absLMonth}*` : `${lDay}/${absLMonth}`;
1163
1164            container.innerHTML += `
1165                <div class="${classes}" onclick="omCalShowDetails(${year}, ${month}, ${i}, ${lDay}, ${absLMonth}, ${curD.getDay()}, '${bmName}', ${bmIndex}, '${lStr}')">
1166                    <span class="om-cal-gregorian">${i}</span>
1167                    <span class="om-cal-lunar">${lStr}</span>
1168                    
1169                </div>
1170            `;
1171        }
1172    }
1173
1174    function omCalPrevMonth() {
1175        omCalCurMonth--;
1176        if (omCalCurMonth < 1) {
1177            omCalCurMonth = 12;
1178            omCalCurYear--;
1179        }
1180        renderOmCalendar(omCalCurYear, omCalCurMonth);
1181    }
1182
1183    function omCalNextMonth() {
1184        omCalCurMonth++;
1185        if (omCalCurMonth > 12) {
1186            omCalCurMonth = 1;
1187            omCalCurYear++;
1188        }
1189        renderOmCalendar(omCalCurYear, omCalCurMonth);
1190    }
1191
1192    function omCalGoToday() {
1193        let t = new Date();
1194        renderOmCalendar(t.getFullYear(), t.getMonth() + 1);
1195    }
1196
1197    function omCalChangeSelect() {
1198        renderOmCalendar(parseInt(document.getElementById("om_cal_year").value), parseInt(document.getElementById("om_cal_month").value));
1199    }
1200
1201    function omCalShowDetails(y, m, d, lDay, absLMonth, wDay, bmName, bmDayIndex, lStr) {
1202        // Set basic info
1203        document.getElementById('om_cal_detail_weekday').innerText = omCalWeekdays[wDay];
1204        document.getElementById('om_cal_detail_gregorian').innerText = `${d}/${m}/${y}`;
1205
1206        let lunarEl = document.getElementById('om_cal_detail_lunar');
1207        let wheelEl = document.getElementById('om_cal_detail_bm_wheel');
1208
1209        if (wheelEl) {
1210            // User has Bát Môn enabled
1211            lunarEl.innerText = `${lStr}, ${bmName}`;
1212
1213            // Generate the hour wheel
1214            const startMins = omCalBmTimeByMonths[absLMonth][0] * 60 + omCalBmTimeByMonths[absLMonth][1];
1215
1216            let wheelHtml = '';
1217            let now = new Date();
1218            let isExactlyToday = (y === now.getFullYear() && m === (now.getMonth() + 1) && d === now.getDate());
1219            let currentMins = now.getHours() * 60 + now.getMinutes();
1220
1221            for (let i = 0; i < 12; i++) {
1222                let curStart = (startMins + i * 120 + 1440) % 1440;
1223                let curEnd = (curStart + 120) % 1440;
1224                let h1 = Math.floor(curStart / 60).toString().padStart(2, '0');
1225                let m1 = (curStart % 60).toString().padStart(2, '0');
1226                let h2 = Math.floor(curEnd / 60).toString().padStart(2, '0');
1227                let m2 = (curEnd % 60).toString().padStart(2, '0');
1228                let bmStr = omCalBmNames[(bmDayIndex + i) % 8];
1229
1230                // Highlight active time segment if the selected date is today
1231                let isActive = false;
1232                if (isExactlyToday) {
1233                    let dist = (currentMins - startMins + 1440) % 1440;
1234                    let currentHourIndex = Math.floor(dist / 120) % 12;
1235                    if (currentHourIndex === i) {
1236                        isActive = true;
1237                    }
1238                }
1239
1240                let activeClass = isActive ? 'active' : '';
1241                wheelHtml += `<div class="om-bm-wheel-item ${activeClass}">${h1}:${m1} - ${h2}:${m2} : <b class="text-uppercase">${bmStr}</b></div>`;
1242            }
1243            wheelEl.innerHTML = wheelHtml;
1244
1245            // Auto-scroll the wheel to the active item
1246            setTimeout(() => {
1247                let activeItem = wheelEl.querySelector('.active');
1248                if (activeItem) {
1249                    activeItem.scrollIntoView({behavior: 'smooth', block: 'center'});
1250                }
1251            }, 200);
1252
1253        } else {
1254            // User does not have Bát Môn enabled
1255            lunarEl.innerText = `${lStr}`;
1256        }
1257
1258        // Handle "Thêm đầu việc" button visibility
1259        let btnAdd = document.getElementById('btn_om_cal_add_task');
1260        let selectedDate = new Date(y, m - 1, d);
1261        let todayDate = new Date();
1262        todayDate.setHours(0, 0, 0, 0);
1263
1264        if (selectedDate >= todayDate) {
1265            btnAdd.style.display = 'inline-block';
1266            btnAdd.onclick = function () {
1267                // Hide both modals and redirect (or trigger your custom web task creation modal here)
1268                $('#modal_om_cal_day_details').modal('hide');
1269                $('#modal_monthly_calendar').modal('hide');
1270
1271                // Currently redirects to /omtask/ passing the date. Adjust this to your add-task function if needed.
1272                window.location.href = `/omtask/daily/?date=${d.toString().padStart(2, '0')}/${m.toString().padStart(2, '0')}/${y}`;
1273            };
1274        } else {
1275            btnAdd.style.display = 'none';
1276        }
1277
1278        $('#modal_om_cal_day_details').modal('show');
1279    }
1280</script>
vendor: 1 bytes, line 1280
1280
1281<script>
1282    function openOmCalendarModal() {
1283        let t = new Date();
1284        renderOmCalendar(t.getFullYear(), t.getMonth() + 1);
1285        $('#modal_monthly_calendar').modal('show');
1286    }
1287</script>
1287
1288    <!-- END Topbar -->
1289
1290    <!-- Main container -->
1291    <main class="main-container">
1292        <div class="main-content">
1293            <div class="omdoc-home" id="omdoc-home">
1294
1295                <!-- Hero Section -->
1296                <div class="omdoc-home__hero">
1297                    <div class="omdoc-home__icon">
1298                        <i class="ti-book"></i>
1299                    </div>
1300                    <h1 class="omdoc-home__title"><span>omDoc</span></h1>
1301                    <p class="omdoc-home__subtitle">
1302                        OmOmega's document & knowledge center — explore system guides and level up your programming skills
1303                    </p>
1304                </div>
1305
1306                <!-- Category Cards -->
1307                <div class="omdoc-home__grid">
1308
1309                    <!-- Card 1: Tài liệu OmRadio (IoT/Device-oriented) -->
1310                    <a href="/omdoc/omradio/" class="omdoc-home__card omdoc-home__card--iot" id="omdoc-card-omradio">
1311                        <div class="omdoc-home__card-icon">
1312                            <i class="ti-plug"></i>
1313                        </div>
1314                        <h2 class="omdoc-home__card-title">OmRadio documentation</h2>
1315                        <p class="omdoc-home__card-desc">
1316                            Usage guides, technical notes and updates for the OmIoT, OmHub devices and the OmRadio hardware ecosystem.
1317                        </p>
1318                        <span class="omdoc-home__card-btn">
1319                            Explore <i class="ti-arrow-right"></i>
1320                        </span>
1321                    </a>
1322
1323                    <!-- Card 2: Kiến thức Python mở rộng -->
1324                    <a href="/omdoc/python/" class="omdoc-home__card omdoc-home__card--python" id="omdoc-card-python">
1325                        <div class="omdoc-home__card-icon">
1326                            <i class="ti-desktop"></i>
1327                        </div>
1328                        <h2 class="omdoc-home__card-title">Extended Python knowledge</h2>
1329                        <p class="omdoc-home__card-desc">
1330                            Improve your Python programming skills; explore libraries, frameworks and advanced techniques for software developers.
1331                        </p>
1332                        <span class="omdoc-home__card-btn">
1333                            Learn more <i class="ti-arrow-right"></i>
1334                        </span>
1335                    </a>
1336
1337                    <!-- Card 3: omDoc English Study -->
1338                    <a href="/omdoc/ielts-study/" class="omdoc-home__card omdoc-home__card--ielts" id="omdoc-card-ielts">
1339                        <div class="omdoc-home__card-icon">
1340                            <i class="ti-pencil-alt"></i>
1341                        </div>
1342                        <h2 class="omdoc-home__card-title">omDoc English Study</h2>
1343                        <p class="omdoc-home__card-desc">
1344                            Practice and improve your English — Vocabulary, Reading, Listening, Writing, Speaking. An 18-unit program from 4.0 to 6.5.
1345                        </p>
1346                        <span class="omdoc-home__card-btn">
1347                            Get started <i class="ti-arrow-right"></i>
1348                        </span>
1349                    </a>
1350
1351                    <!-- Card 4: Health Exercise -->
1352                    <a href="/omdoc/health-exercise/" class="omdoc-home__card omdoc-home__card--health" id="omdoc-card-health">
1353                        <div class="omdoc-home__health-visual" aria-hidden="true">
1354                            <img src="/static/media/health_images/tiger.png" alt="">
1355                            <span class="omdoc-home__health-orbit"></span>
1356                        </div>
1357                        <span class="omdoc-home__card-kicker">Today's Practice</span>
1358                        <h2 class="omdoc-home__card-title">Health Practice</h2>
1359                        <p class="omdoc-home__card-desc">
1360                            Breath-coordinated exercises with the Five Animal Frolics — Tiger, Deer, Bear, Monkey, Crane. Sessions include guided breathing and progress tracking.
1361                        </p>
1362                        <span class="omdoc-home__card-btn">
1363                            Get started <i class="ti-arrow-right"></i>
1364                        </span>
1365                    </a>
1366
1367                    <!-- Card 5: Agriculture Research -->
1368                    <a href="/omdoc/agriculture/" class="omdoc-home__card omdoc-home__card--agriculture" id="omdoc-card-agriculture">
1369                        <div class="omdoc-home__card-icon">
1370                            <i class="ti-leaf"></i>
1371                        </div>
1372                        <h2 class="omdoc-home__card-title">Agriculture AI/IoT Research</h2>
1373                        <p class="omdoc-home__card-desc">
1374                            Nghiên cứu ứng dụng trí tuệ nhân tạo và IoT trong nông nghiệp, các kỹ thuật tối ưu hóa cây trồng và công nghệ thông minh.
1375                        </p>
1376                        <span class="omdoc-home__card-btn">
1377                            Explore <i class="ti-arrow-right"></i>
1378                        </span>
1379                    </a>
1380
1381                </div>
1382
1383                <!-- Stats Row -->
1384                <div class="omdoc-home__stats">
1385                    <div class="omdoc-home__stat">
1386                        <div class="omdoc-home__stat-number">53</div>
1387                        <div class="omdoc-home__stat-label">Articles</div>
1388                    </div>
1389                    <div class="omdoc-home__stat">
1390                        <div class="omdoc-home__stat-number">11</div>
1391                        <div class="omdoc-home__stat-label">Topics</div>
1392                    </div>
1393                    <div class="omdoc-home__stat">
1394                        <div class="omdoc-home__stat-number">7</div>
1395                        <div class="omdoc-home__stat-label">Documents</div>
1396                    </div>
1397                </div>
1398
1399            </div>
1400        </div>
1401
1402        <!-- Footer -->
1403        
1404
1405
1406<!-- Footer -->
1407<footer class="site-footer">
1408    <div class="row">
1409        <div class="col-6">
1410            <p class="text-center text-md-left"><b>© 2022 </b><a href="/about/contact/"><b style="color: #5b880c;">OmOmega</b></a> |
1411                Online: <b>3</b> | Visits: <b>1,260,644</b></p>
1412        </div>
1413
1414        <div class="col-6">
1415            <ul class="nav nav-primary nav-dotted nav-dot-separated justify-content-center justify-content-md-end">
1416                <li class="nav-item">
1417                    <a class="nav-link" href="#" target="_blank" data-toggle="modal" data-target="#modal_download_app">OmOmega Mobile</a>
1418                </li>
1419                <li class="nav-item">
1420                    <a class="nav-link" href="/rules/">Terms</a>
1421                </li>
1422                <li class="nav-item">
1423                    <a class="nav-link" href="/about/contact/">Contact</a>
1424                </li>
1425            </ul>
1426        </div>
1427    </div>
1428</footer>
1429<!-- END Footer -->
1430        <!-- END Footer -->
1431    </main>
1432    <!-- END Main container -->
1433
1434    
1435    
1436    <!-- Scripts -->
1437    
1437<script src="/static/js/core.min.js" ></script>
1437
1438    
1438<script src="/static/js/app.min.js"></script>
1438
1439    
1439<script src="/static/js/script.min.js"></script>
1439
1440    
1440<script src="/static/vendor/toastr/js/toastr.min.js"></script>
1440
1441
1442    
1443
1444    
1445    
1446</body>
1447</html>
1448

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.