1 line
515 KiB
JavaScript
1 line
515 KiB
JavaScript
!function(e){var t={};function a(s){if(t[s])return t[s].exports;var i=t[s]={i:s,l:!1,exports:{}};return e[s].call(i.exports,i,i.exports,a),i.l=!0,i.exports}a.m=e,a.c=t,a.d=function(e,t,s){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:s})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(e,t){if(1&t&&(e=a(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var s=Object.create(null);if(a.r(s),Object.defineProperty(s,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)a.d(s,i,function(t){return e[t]}.bind(null,i));return s},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=0)}([function(e,t,a){"use strict";a.r(t),a.d(t,"navigationHelper",(function(){return _}));class s{static get availableLanguages(){return[{id:"zh",text:"汉语",dict:i,moment:"zh-cn"}]}constructor(e){this.selectedLanguage=s.availableLanguages.find(t=>t.id==e),this.selectedLanguage&&(n.setSetting("language",e),moment.locale(this.selectedLanguage.moment))}static createInstance(e){void 0!==this.instance&&(null==this.instance.selectedLanguage?"en"===e:this.instance.selectedLanguage.id===e)||(this.instance=new s(e))}static getInstance(){return this.instance}T(e,t,a=[]){let s="";return s=null==this.selectedLanguage?t:this.selectedLanguage.dict[e]||t,a.reduce((e,t)=>e.replace(/%s/,t),s)}}const i={credits:"学分",prerequisites:"先修课程","tier-navigation":"类别","navigation-home":"主页","navigation-majors":"专业","navigation-major":"专业","navigation-classes":"课程","navigation-class":"课程","navigation-clubs":"社团","navigation-club":"社团","navigation-partners":"基/姬友","navigation-partner":"基/姬友","navigation-punishments":"处分","navigation-punishment":"处分","navigation-schedule":"日程","navigation-progress":"进度","navigation-roulette":"轮盘","navigation-help":"帮助","navigation-settings":"设置","navigation-mapping":"地图","navigation-graduated":"已毕业","navigation-disclaimer":"免责条款","scheduled-toast-error-header":"第三方cookies被禁止","scheduled-toast-error-content":"请切换至完整的地图工具网页,或在浏览器设置里允许它。","user-confirmation-yes":"确定","user-confirmation-cancel":"取消","user-confirmation-close":"关闭","update-notification":"后台版本已更新,点击刷新。","time-day":"天","time-days":"天","time-day-binding-no-minutes":" ","time-day-binding":" ","time-hour":"小时","time-hours":"小时","time-hours-binding":" ","time-minute":"分钟","time-minutes":"分钟","time-minutes-binding":" ","time-second":"秒","time-seconds":"秒","time-less-then-a-minute":"不足 1 分钟。","home-feedback-title":"用户反馈","home-feedback-description":'只需要轻轻点击一下,发送一个"心跳信号"给作者,Ta就能知道一位陌生的好朋友还默默支持、陪伴着Ta。',"home-feedback-discord":"如果你想加入PU的Discord讨论组 - 请点击这里。","home-welcome-header":"Project University欢迎你的加入","home-welcome-info-1":"在这里,你可以创建属于自己的大学或者加入别人已经建好的大学。由于本网站不会提供关于玩法的任何内容,因此你需要自己导入一张PU-map地图文件。该文件由玩家自行创建,用于提供具体的玩法。本网站完全运行在玩家自己的设备上,网站的后台服务器不会储存任何私人内容,因此请确保存档进度的安全,请确保导入内容的安全性,请确保人身安全和法律安全。","home-welcome-info-2":"在这里,你的大学生活将大致包括如下内容:选择专业、选择课程和进行日常任务。如果你没有达到任务的要求,你将受到处分。你可以通过完成课程考试的方式来获得相应的学分和解锁更难的课程。学完所有的专业必修课并获得足够的学分之后,你就可以尝试毕业,参加最后的专业结业考试。课程之余,你还可以参加社团或者勾搭基/姬友来改变特定课程的难度或获得福利。","home-welcome-info-3":"赶紧导入一张PU-map地图,并选择一个专业吧!你的选课学习之旅就此开启!","home-load-map-button":"导入地图","home-create-map-button":"创建地图","home-changelog-header":"更新日志","home-show-changelogs-button":"以前的更新日志","home-external-sources-header":"外部资源支持","home-disclaimer-link":"如果您想再次确认“免责条款和数据保护”的内容, 请单击此处。","home-special-thanks-header":"特别鸣谢","tour-navigation-close":"关闭","tour-navigation-next":"下一条","tour-majors-0-title":"菜单","tour-majors-0-text":"这里是主菜单,可以在不同的选项卡之间切换。","tour-majors-1-title":"折叠菜单","tour-majors-1-text":"在移动设备上主菜单会折叠,点击这里折叠或展开主菜单。","tour-majors-2-title":"专业","tour-majors-2-text":"每张专业卡片对应一个可选专业。","tour-majors-3-title":"详情","tour-majors-3-text":"点击图片查看该专业的具体内容。","tour-majors-4-title":"选项","tour-majors-4-text":"这里会出现选专业、退选专业的选项。","tour-majors-5-title":"内容一览","tour-majors-5-text":"按下这个信息按钮会为你展示该专业的具体内容。","tour-majors-6-title":"卡片信息","tour-majors-6-text":"卡片底端将展示该专业的简要描述。","tour-classes-0-title":"课程难度","tour-classes-0-text":"点击查看不同难度的课程。","tour-classes-1-title":"课程难度","tour-classes-1-text":"点击此处或左右滑动来查看不同难度的课程,从左至右难度依次增加。","tour-classes-2-title":"课程","tour-classes-2-text":"每张卡片对应一个可选课程。","tour-classes-3-title":"图片","tour-classes-3-text":"点击图片查看该课程的具体内容。","tour-classes-4-title":"选项","tour-classes-4-text":"这里会出现选课、退课和重修的选项。","tour-classes-5-title":"内容一览","tour-classes-5-text":"按下这个信息按钮会为你展示该课程的具体内容。","tour-classes-6-title":"卡片信息","tour-classes-6-text":"卡片底端将展示该课程的简要描述。","map-submap-failed-header":"设置子地图失败","map-submap-failed-content":"PU无法储存子地图。这可能由于你的浏览器开启了匿名模式访问。","map-save-failed-header":"进度保存失败","map-save-failed-content":"PU无法保存你的进度。这可能由于你的浏览器开启了匿名模式访问。","map-save-incompatible":"存档与当前主地图不兼容。","map-save-empty":"存档为空。","map-import-confirmation-header":"导入地图","map-import-confirmation-conent":"确定要导入这张地图吗?请确保地图来源的可靠性,否则后果自负。","map-import-script-header":"导入中止","map-import-script-content":"该地图文件内包含不安全的信息。出于安全性考虑,我们中止了导入。","map-import-submap-popup":"导入子地图","map-import-map-popup":"地图导入成功","task-time-without-mod-info":"原时长 %s。","task-punishment-without-mod-info":"原数量 %s。","task-random-punishment":"随机","task-param-without-mod-info":"原数量 %s。","task-perk-activated-header":"奖励处分已获得","task-perk-activated-content":"你随机得到了 1 项处分。","task-roulette-reset-header":"轮盘重置奖励","task-roulette-reset-content":"轮盘已经重置了,再去碰碰运气吧。","task-skip-perk-header":"请假条奖励","task-skip-perk-single-content":"请假条已获得。","task-skip-perk-multi-content":"%s 张请假条已获得","task-punishment-drawn-header":"任务处分已获得","task-punishment-drawn-content":"根据任务的要求,你随机得到了 1 项 %s处分。","task-punishment-header":"受到处分","task-punishment-custom-task":"自定义任务失败!","task-punishment-task":"日常任务失败!","task-punishment-exam":"课程考试失败!","task-punishment-thesis":"毕业考核失败!","task-punishment-drawn":"你获得了 1 项处分!","task-punishments-drawn":"你获得了 %s 项处分!","task-failed-header":"任务失败","task-failed-content":"你的 %s 任务完成失败!","task-custom-task-finished-header":"自定义任务完成","task-custom-task-finished-content":"你完成了 1 项自定义任务!","task-class-finished-header":"课程通过","task-class-finished-content":"恭喜你通过了 %s 课程!","task-finished-header":"任务完成","task-finished-content":"你完成了 %s 的课程任务!","task-major-finished-header":"专业毕业","task-major-finished-content":"你从 %s 专业中毕业了!","task-punishment-finished-header":"处分结束","task-punishment-finished-content":"对你的处分内容 %s 已经结束!","task-manual-counter-button":"剩余 %s 次","task-manual-finish-button":"完成","task-finish-by-text":"本任务需在%s完成。","task-fail-button":"失败","task-time-left-info":"剩余时长","task-resume-button":"继续","task-pause-button":"暂停","task-punish-button":"惩罚","task-status-paused":"任务已暂停","task-status-active":"任务进行中","task-active-task":"进行中的任务","task-class-missed-header":"翘课","task-class-missed-content":"你因为翘掉一节课而受到 1 项处分!","task-fail-header":"任务失败","task-fail-content":"确定要放弃该任务或任务失败了吗?","graduated-certificate-header":"毕业证书:%s","graduated-congratulations-header":"恭喜你从 %s 专业中毕业。","graduated-congratulations-1":"经过不懈努力之后,恭喜你离自己梦中的样子更近了一步。","graduated-congratulations-2":"为了你的梦想,你依然还有很多能去做的。再选择一个专业或者换一张地图继续努力吧~","graduated-congratulations-3":"作为已经毕业的学长/学姐,你也可以为学弟学妹们准备一些属于你的原创内容,让ta们的校园生活更加丰富多彩。","graduated-table-class":"课程","graduated-table-knowledge-points":"出勤点","graduated-table-grade":"绩点","progress-active-major":"你的当前专业","progress-completed-classes":"课程完成情况","progress-active-classes":"正在学习","progress-active-classes-no":"没有正在学习中的课程。","progress-mandatory-classes":"必修课程","progress-mandatory-classes-no":"没有必修课程。","progress-finished-majors":"已毕业专业","progress-finished-majors-no":"还没有从任何专业中毕业。","progress-finished-classes":"已通过课程","progress-finished-classes-no":"还没有通过任何课程。","progress-credits-info":"只有通过了课程考试,你才能获得学分。","progress-punishments-endured":"尚未结束的处分","progress-thesis-button":"选择结业考核内容","schedule-active-tasks-header":"进行中的任务","schedule-active-punishments-header":"执行中的处分","schedule-active-punishments-info":"在处分执行完毕前,禁止参加课程考试和毕业考核。","schedule-todays-classes-header":"今日课程","schedule-clubs-partner-header":"社团 & 基/姬友","schedule-weekly-header":"本周课表","schedule-easy-mode-info":'当前是"简单模式",你可以随意补习各门课程,翘掉补习不会受处分。',"schedule-weekend-info":"你在周末可以随意补习各门课程,翘掉补习不会受处分。","schedule-no-class-left":"今天的课已经上完了,明天再来吧。","schedule-no-class-left-add":"今天的课已经上完了,你想多选一门课吗?","schedule-no-classes":"无课程","schedule-feedback-header":"用户反馈请求","schedule-feedback-1":'同学你好~近一个月原作者收到的"心跳信号"急剧减少,这让Ta十分扫兴。如果一直这样下去,Ta可能会放弃这个项目的维护。先谢过你一直以来对本项目的支持,并由衷地希望你能够享受你的大学生活。由于原作者十分注重各位同学的隐私保护,所以Ta不会从本网站上获取用户的任何信息,但Ta又想知道自己的大学里还有多少学生,所以需要各位提供主动的信息反馈。只需要简单地点击这条%sbitly链接%s,就可以让Ta知道你还在这里上课。',"schedule-feedback-2":"此外,Ta还为这个项目开设了一个discord讨论组(英文),用于反馈用户体验和分享用户自己制作的地图,欢迎你的%s加入%s","schedule-feedback-3":'这条"心跳信号"请求每个月会显示一次。',"schedule-import-task-error-header":"任务导入失败","schedule-import-task-error-content":"未能成功导入他人分享的任务。","schedule-script-header":"导入中止","schedule-script-content":"该自定义任务内包含不安全的信息。出于安全性考虑,我们中止了导入。","schedule-custom-task-default-name":"自定义任务","schedule-import-custom-task-confirmation-header":"导入自定义任务","schedule-import-custom-task-confirmation-content":"确定要导入这条自定义任务吗?任务将在导入后自动开始。请确保地图来源的可靠性,否则后果自负。","roulette-header":"高潮轮盘","roulette-info-1":"小骚货,又想要了吗?","roulette-info-2":"来求我试试看~","roulette-info-3":"什么?你胆敢擅自高潮?","roulette-info-4":"滚过来受罚!","roulette-punishment-button":"我不小心弄出来了","roulette-spin-button":"求求您给我一次","punishment-tier-text":"程度","punishment-tier-light":"轻","punishment-tier-hard":"重","punishment-tier-hardcore":"硬核","punishment-light-header":"轻处分","punishment-hard-header":"重处分","punishment-hard-requirement":"你需要至少 %s 学分才能解锁重处分。","punishment-hardcore-header":"硬核处分","punishment-hardcore-requirement":"你需要至少 %s 学分并在设置中开启了“硬核处分”才能解锁硬核处分。","punishment-status-active":"执行中","punishment-status-locked":"未解锁","punishment-roll-button":"随机得到 %s处分","punishment-detail-start-button":"开始执行处分","punishment-detail-reroll-button":"随机更换处分","punishment-rerolled-knowledge-point-header":"处分已更换","punishment-rerolled-knowledge-point-content":"你因更换处分而被扣除了 %s 课程的 1 个出勤点。","punishment-rerolled-exam-header":"处分已更换","punishment-rerolled-exam-content":"你因更换处分而被取消了 %s 的课程考试成绩。你需要重新参加考试了。","punishment-rerolled-unknown-header":"处分已更换","punishment-rerolled-unknown-content":"你将为更换处分的行为付出代价。","punishment-unavailable-header":"不可获得的处分程度","punishment-unavailable-content":"你当前还无法获得此种程度的处分。","punishment-max-header":"达到处分数量上限","punishment-max-content":"你最多能够积累 %s 个执行中的处分。","punishment-null-header":"无其他可获取的处分","punishment-null-content":"你当前没有可以被给予的处分了。","punishment-single-text":"处分","punishment-multi-text":"处分","punishment-card-unblock-button":"Unblock","punishment-card-block-button":"Block","punishment-detail-unblock-button":"Unblock punishment","punishment-detail-block-button":"Block punishment","club-tier-normal":"普通","club-tier-elite":"精英","club-normal-header":"社团","club-elite-header":"精英社团","club-elite-requirement":"你至少需要 %s 学分才能参加精英社团。","club-status-active":"已加入","club-status-locked":"未解锁","club-card-join-button":"报名","club-card-drop-button":"退出","club-detail-modifiers":"可选奖励","club-detail-join-button":"参加本社团","club-detail-drop-button":"退出本社团","club-modifier-activity":"活动","club-modifier-perk":"奖励","club-perk-activate-button":"领取奖励","club-perk-active-button":"已领取","club-max-clubs-header":"达到社团数上限","club-max-clubs-content":"你最多只能同时参加 %s 个社团。","club-max-perks-header":"达到社团活动数上限","club-max-perks-content":"你最多只能同时参加 %s 个社团活动。","club-perk-unavailable-header":"社团奖励无法领取","club-perk-unavailable-content":"当前无法参加该社团活动领取奖励。","club-perk-unavailable-tag-header":"社团奖励无法领取","club-perk-unavailable-tag-content":"当前无相关课程(标签)与该社团活动相关。","partner-status-active":"已结交","partner-card-join-button":"勾搭","partner-card-drop-button":"绝交","partner-modifier-activity":"活动","partner-modifier-perk":"福利","partner-detail-modifiers":"可选福利","partner-detail-add-button":"勾搭Ta","partner-detail-leave-button":"和Ta绝交","partner-perk-activate-button":"领取福利","partner-perk-active-button":"已领取","partner-perk-not-active-header":"基/姬友福利无法领取","partner-perk-not-active-content":"你还没有勾搭上Ta。","partner-perk-tags-header":"基/姬友福利无法领取","partner-perk-tags-content":"当前无相关课程(标签)与该基/姬友活动相关。","partner-perk-max-perks-header":"基/姬友福利达到上限","partner-perk-max-perks-content":"你最多只能同时参加 %s 个基/姬友活动。","major-status-unavailable":"不可选","major-status-active":"当前专业","major-status-grade":"绩点","major-card-join-button":"选修","major-card-drop-button":"退选","major-detail-prerequisites":"必修课程","major-detail-thesis":"可选毕业考核内容","major-detail-thesis-button":"开始毕业考核","major-detail-join-button":"选修本专业","major-detail-drop-button":"退选本专业","major-detail-thesis-requirement-header":"参加毕业考核条件","major-detail-thesis-requirement-join":"你必须先选修本专业,才能参加毕业考核。","major-detail-thesis-requirement-credits":"你必须修满 %s 个学分。","major-detail-thesis-requirement-punishments":"你必须先执行完所有处分。","major-detail-thesis-requirement-prerequisites":"你必须通过所有必修课程。","major-detail-thesis-requirement-thesis":"你只能选择一条毕业考核内容。","major-drop-confirm-header":"专业退选","major-drop-confirm-content":"确定要退选该专业?","major-dropped-header":"退选成功","major-dropped-content":"你因为退选专业而受到处分!","class-tier-beginner":"入门","class-tier-intermediate":"普通","class-tier-advanced":"高级","class-tier-master":"大师","class-beginner-header":"入门课程","class-intermediate-header":"普通课程","class-intermediate-requirement":"你至少需要获得 %s 学分,并完成所有的先修课程才能解锁普通课程。","class-advanced-header":"高级课程","class-advanced-requirement":"你至少需要获得 %s 学分,并完成所有的先修课程才能解锁高级课程。","class-master-header":"大师课程","class-master-requirement":"你至少需要获得 %s 学分,并完成所有的先修课程才能解锁大师课程。","class-card-tasks":"任务","class-card-exams":"考试","class-card-exams-locked":"当前未解锁","class-card-join-button":"选课","class-card-drop-button":"退课","class-card-retake-button":"重修","class-status-locked":"未解锁","class-status-grade":"绩点","class-status-active":"正在学习","class-detail-days":"每周开课","class-detail-tasks":"可选任务选项","class-detail-exams":"考试选项","class-detail-expected-grade":"如果现在参加考试,本课程绩点为 %s。","class-detail-mandatory":"本课程为当前专业的必修课程 %s 。","class-detail-join-button":"选修本课程","class-detail-drop-button":"退选本课程","class-detail-retake-button":"重修本课程","class-detail-retake-warning":"本课程最终绩点为 %s,你还有进步的空间。若想提高绩点,你可以重修本课程,继续获得更多出勤点。当然,你最后需要重新参加课程考试。","class-detail-start-task-button":"开始学习","class-detail-start-exam-button":"开始考试","class-detail-exam-requirement-header":"考试条件","class-detail-exam-requirement-join":"你必须先选课,而后才能完成学习任务或参加考试。","class-detail-exam-requirement-attended":"你今日已经学过本课程了,劳逸结合才能进步更快。","class-detail-exam-requirement-attendance":"所需出勤点为 %s 个,当前已获得 %s 个。","class-detail-exam-requirement-attendance-tasks":"你还有尚未进行过的可选任务。","class-knowledge-points-info":"%s knowledge points collected.","class-detail-exam-requirement-punishment":"你还有未结束的处分。","class-detail-exam-requirement-days":"本课程今日不开课。","class-max-classes-header":"达到课程数上限","class-max-classes-content":"你最多只能同时选修 %s 门课。","class-retake-confirmation-header":"重修课程","class-retake-confirmation-content":"确定想要重修本课程吗?你已有的出勤点会保留,但你的课程考试将作废。","class-drop-confirmation-header":"退选课程","class-drop-confirmation-content":"确定想要退选本课程?","disclaimer-header":"免责条款和数据保护","disclaimer-block-1":"可以公开访问的网站需要满足一些法律要求 ,因此我们写下了这段简短的陈述来帮助您了解这个网站如何运作。 进入首页后,您可以在页面底部找到本页面的链接。 如果您同意以下内容,您可以点击页面底部的“我已理解并同意”, 并继续访问本网站。如果您不同意,我们将清理所有存储的数据,并自动退出。","disclaimer-overview-header":"概述","disclaimer-overview-1":"下文中的“用户”代指您,而“我们”代指网页提供者。","disclaimer-overview-2":"只要我们有意开放,本网站对用户免费,但我们有权随时关闭甚至清空本网站。","disclaimer-overview-3":"本网站除用于提供网站内容的简单网页服务外,不使用任何后端。","disclaimer-overview-4":"本网站完全基于您的设备运行,如果发生文件损坏或丢失,所有之前的信息均无法找回。","disclaimer-overview-5":"本网站只是一个框架,因此需要用户生成内容,这些内容是由用户手动创建和提供的,我们并不知道其他人如何使用这项服务。 您可以将其比作一个本地文本编辑器——您可以创建或打开文本文件,而一切都留在您的设备上。您亦可以使用这个工具来访问其他用户创建的文件。","disclaimer-overview-6":"本网站运行所需的源代码完全由本域名提供。对于某些离开了本页面的链接,我们对其行为不负任何责任。","disclaimer-overview-7":"用户生成的内容可能是恶意的,我们试图保护您免受明显的攻击,但最终您要对您加载的外部内容负责。","disclaimer-inner-header":"免责条款","disclaimer-inner":"使用本网站是免费的,没有广告或跟踪服务。任何人不得使用本框架获利。 由于本网站使用用户编辑生成的内容,但并不存储任何关于用户的信息,所以网站提供者无法审核将要加载的内容。 因此,您将对您添加的其他用户生成的内容承担全部责任。 提供者对链接页面的内容不负责任, 如果有法律要求从框架中删除任何引用,它将被遵守。","disclaimer-data-protection-header":"数据保护声明","disclaimer-data-protection":'没有数据被收集和存储在用户设备之外。进度、设置、内容和反馈标记都存储在本地设备上。 用户生成的内容会留在用户的设备上。如果您决定使用反馈选项提供 "心跳信号",您将离开本网站,之后便使用Bitly隐私条款。 反馈是可选的,Bitly-Link将被突出显示。',"disclaimer-bitly-link":"点击这里查看Bitly.com上的相关隐私条款。","disclaimer-stored-data-header":"数据存储","disclaimer-stored-data-description":"多种技术手段用于存储您的进度、设置和添加的用户生成内容。","disclaimer-stored-data-local":"本地数据存储: 设置, 推迟的通知, 最后一次反馈信息请求, 接受的免责条款, 已经显示的教程","disclaimer-stored-data-indexeddb":"IndexedDB存储: 用户生成内容, 当前进度","disclaimer-stored-data-cache":"临时文件存储: 本网站框架数据支持离线模式","disclaimer-accept-button":"我不同意","disclaimer-reject-button":"了解并同意","settings-header":"设置","settings-easy-mode-checkbox":"简单模式","settings-easy-mode-description":'在简单模式下,你可以在任何一天上课,取消了翘课的处分,并且可以任意随机更换处分而不被罚。该模式可以作为游戏的"暂停"。',"settings-hardcore-punishments-checkbox":"硬核处分","settings-hardcore-punishments-description":"硬核处分往往相当困难,禁用它们通常不会有任何影响。默认禁用。","settings-auto-hide-checkbox":"网页自动隐藏","settings-auto-hide-description":"确保你的小秘密不会被旁人看到,本网页会在 %s 后自动隐藏。","settings-back-top-checkbox":"回到顶端按钮","settings-back-top-description":'在手机上不容易在页面上切换,你可以开启 "回到顶端按钮" 来方便你迅速回到页面顶端。',"settings-hide-active-classes-checkbox":"在课程页面不显示正在学习中的课程","settings-hide-active-classes-description":"正在学习的课程会在%s日程%s中显示,勾选本项可以让你的%s课程%s列表更清爽。","settings-hide-finished-classes-checkbox":"在课程页面不显示已经通过的课程","settings-finished-active-classes-description":"已经通过的课程会在%s进度%s中显示,勾选本项可以让你的%s课程%s列表更清爽。","settings-skip-tier-checkbox":"自动切换至更高级别页","settings-skip-tier-description":"当你划至页面最下面时,将自动切换到下一层次页(课程难度、精英社团等)","settings-show-confirmations-checkbox":"当做出重大更改时弹出确认框","settings-show-confirmations-description":"防止误触和猫。","settings-theme-description":"选择网页配色风格。","settings-language-description":"选择想要使用的语言。[Select your prefered language.]","settings-save-button":"保存更改","settings-reset-button":"重置设定","settings-maps-header":"地图管理","settings-map-tool-button":"地图工具","settings-map-tool-button-description":"打开地图工具来创建自定义内容。","settings-map-import-button":"导入地图","settings-map-import-button-description":"导入一张地图或更新一张已经存在的地图。","settings-map-selection-header":"选择主地图","settings-map-selection-info-1":"切换至另一张地图(不同地图的进度是独立保存的)。","settings-map-selection-info-2":"如果你想要导出所有地图的进度,你需要对每一张主地图单独一一保存(子地图进度会保存在当前主地图进度中)。","settings-switch-map-button":"切换至选择的地图","settings-delete-map-button":"删除选择的地图","settings-import-save-button":"导入存档","settings-export-save-button":"导出存档","settings-share-save-button":"导出存档但不生产存档文件","settings-submap-selection-header":"选择子地图","settings-submap-selection-description":'请勾选所有你想要作为子地图的选项。你需要先导入子地图(点击 "导入地图"),然后你才能在选项列表中找到它。如果你找不到导入的子地图,可能它被默认用作了主地图,这时你需要先更换一张主地图,然后才能在列表中看到你导入的子地图。',"settings-submap-selection-button":"加载勾选的子地图","settings-global-actions-header":"全局操作","settings-reset-tutorial-button":"重置教程","settings-reset-tutorial-description":"这将让新手教学框重新显示。","settings-reset-game-button":"重置游戏","settings-reset-game-description":"这将完全重置整个游戏,包括地图、进度甚至是用户设置。","settings-delete-map-confirmation-header":"删除地图","settings-delete-map-confirmation-content":"确定要删除地图吗?你将会失去所有的存档。","settings-reset-game-confirmation-header":"重置游戏","settings-reset-game-confirmation-content":"确定要删除所有的地图和存档吗?你将丢失地图和当前进度。","settings-script-header":"导入中止","settings-script-content":"该存档文件内包含不安全的信息。出于安全性考虑,我们中止了导入。","settings-save-import-failed-header":"存档导入失败","settings-save-import-failed-content":"导入存档文件时出错,请检查后台记录以获取更多信息。","settings-save-import-confirmation-header":"导入存档","settings-save-import-confirmation-content":"确定要导入存档并覆盖你的现有进度吗?请确保存档来源的可靠性,否则后果自负。","settings-save-export-failed-header":"存档导出失败","settings-save-export-failed-content":"无法分享你的存档。","settings-import-share-save-failed-header":"存档导入失败","settings-import-share-save-failed-content":"无法导入分享的存档文件。","settings-import-share-save-confirmation-header":"导入存档","settings-import-share-save-confirmation-content":"确定要导入存档并覆盖你的现有进度吗?请确保存档来源的可靠性,否则后果自负。","settings-import-share-map-failed-header":"地图导入失败","settings-import-share-map-failed-content":"无法导入分享的地图。","settings-import-share-map-confirmation-header":"导入地图","settings-import-share-map-confirmation-content":"确定要导入这张地图吗?请确保地图来源的可靠性,否则后果自负。","settings-save-selection-header":"保存游戏进度","settings-save-sync-description":"You can now sync your saves over multiple devices or retain them without having to trouble yourself with the files. Just join our %sDiscord%s and use the channel %spu-saves%s to %s/register%s.","settings-save-sync-button":"Sync this device now","settings-reset-save-sync-button":"Remove save sync for this device","settings-import-save-sync-header":"Save-Sync setup finished","settings-import-save-sync-content":"Your saves will now be synced, if the required maps are available.","settings-import-save-sync-failed-header":"Save-Sync setup failed","settings-import-save-sync-failed-content":"The provided key was not accepted by the backend. You can try to create a new session or try again later.","settings-download-save-sync-failed-header":"Save-Sync download failed","settings-download-save-sync-failed-content":"The download of the latest SaveGame failed. You may need to create a new session.","settings-check-save-sync-failed-header":"Save-Sync check failed","settings-check-save-sync-failed-content":"Checking the status of the latest SaveGame failed. You may need to create a new session.","settings-upload-save-sync-failed-header":"Save-Sync upload failed","settings-upload-save-sync-failed-content":"Uploading the latest SaveGame failed. You may need to create a new session.","settings-time-offset-selection":"自定义一个时间偏移量,让夜晚变成你的清晨。当前游戏时间: ","settings-time-offset-hour":"时","settings-compress-exports-checkbox":"压缩导出","settings-compress-exports-description":"稍微压缩文件大小,能够更方便地分享它们。%s提示:%s在大多数苹果设备上会很有用!%s","settings-image-exports-checkbox":"图像导出","settings-image-exports-description":"分享一张图片而不是一段随机文本吧。必须压缩!%s警告:%s这是一个实验性功能,可能无法读取过大的文件!大部分即时通讯软件会删除附加的数据使其失效!%s","settings-complete-exports-checkbox":"完全导出","settings-complete-exports-description":"不必再逐个导入地图及存档,所有内容都将导出为单一文件。必须压缩!%s提示:%s这会让存档变得非常巨大!%s","settings-detailed-table-view-checkbox":"使用详细表格视图。","settings-detailed-table-view-description":"将*地图工具*的风格在原版“卡片”视图(模拟最终地图效果)和新增的“表格”视图之间切换。","settings-import-save-clipboard-button":"从剪贴板导入存档","settings-export-save-clipboard-button":"复制存档到剪贴板","settings-join-discord-button":"加入Discord","settings-join-discord-description":"欢迎加入PU的官方Discord。(仅限英语)","settings-unsaved-changes-warning-checkbox":"在地图工具中显示尚未保存警告","settings-unsaved-changes-warning-description":"虽然地图工具已支持自动保存,但你也可以开启这个警告来体型自己是否还有尚未导出的编辑。","settings-filters-header":"过滤器","settings-three-state-filters-checkbox":"三态过滤器","settings-three-state-filters-description":"默认过滤器只检查是否包含特定的标签,勾选本项可以让它也能够排除特定的标签。","settings-hide-active-classes-checkbox":"在课程页面中不显示正在学习的课程","settings-hide-finished-classes-checkbox":"在课程页面中不显示已通过的课程","settings-hide-locked-classes-checkbox":"在课程页面中不显示被锁定的课程","settings-hide-locked-clubs-checkbox":"在社团页面中不显示被锁定的社团","settings-hide-locked-punishments-checkbox":"在处分页面中不显示被锁定的处分","settings-hide-blocked-punishments-checkbox":"在处分页面中不显示被禁用的处分","help-table-grade":"绩点","help-table-threshold":"比例","help-table-min-knowledge":"最少出勤点数","help-table-min-knowledge-intermediate":"最少出勤点数(普通课程)","help-table-min-knowledge-advanced":"最少出勤点数(高级课程)","help-table-min-knowledge-master":"最少出勤点数(大师课程)","help-overview-header":"入学流程","help-overview-1":"选择一个%s专业%s","help-overview-2":"选择%s专业必修课程%s %s必修课程会带有星标%s 和一些%s附加课程%s","help-overview-3":"参加%s社团%s或者勾搭%s基/姬友%s可以让你的学习过程更加丰富多彩","help-overview-4":"上课并通过课程考试修够 %s 个学分","help-overview-5":"翘课会让你受%s处分%s","help-overview-6":"毕业","help-notice-header":"校园安全教育","help-notice-content":'人身安全必须始终放在首位。当你在课程期间感到不适,必须立刻结束当前课程,切忌拿自己的健康当儿戏!在进行任务途中如果感到不适,应立即停下来检查,避免发生意外。必要的时候请务必及时就医。此外,请珍惜自己的生命。一生中没有坎儿是过不去的,如果真的有事情想不开,去找人<a href="https://suicidepreventionlifeline.org/">帮帮自己</a>。',"help-progress-header":"课程进度说明","help-progress-content":"所有的进度数据都保存在你自己的浏览器缓存里面,网上不会留存任何备份。浏览器在隐私模式下,通常不会储存数据,所以建议你使用普通模式打开本网站。如果你坚持使用隐私模式,提醒你注意保存好你的相关数据文件,通常包括地图文件(.pu)和存档(.puSave)。","help-majors-header":"专业","help-majors-content":"选择一个%s专业%s便意味着你将在该领域深造。每个专业都有自己的必修课程(毕业先修课),并且每个课程都可能有它自己的先修课要求。选完专业之后,你可以到%s进度%s页面去查看你的必修课程。你需要获得 %s 个学分才能尝试毕业。如果你中途决定换专业,你可以去专业详情界面退选,但你会因此受到 %s 项随机的处分。","help-classes-header":"课程与课表","help-classes-content":"请注意每门课程在每周的开课时间。在课程开课当天,你需要完成课程任务或参加考试才能得到该课程的 1 个出勤点。如果你在开课当天都没有来上课,你将会自动受到 1 项处分。想了解所有的课程要求,可以去课程详情页面查看。你在通过一门课程后,可以立马再选一门,但你最多不能同时上超过 %s 门课。最好不要同时学习太多课程,否则你可能会跟不上课程进度。所有的课程都在周末开设了补习班,翘掉补习课不会被处分(但是任务失败仍会被处分)。","help-punishments-header":"处分","help-punishments-1":"翘课(转点仍未开始课程任务)一节将被给予 1 项随机的处分。","help-punishments-2":'任务失败时,你必须诚实地按下"失败",并接受处分。',"help-punishments-3":"暂停一个任务超过 %s 将被给予处分。当一个任务具有多个步骤时,你将获得额外的暂停时间,你需要在额外的暂停时间里完成其他任务。","help-punishments-4":"处分可以在任何时候完成。","help-punishments-5":"你最多可以积攒 10 个处分。","help-punishments-6":"只要处分没有全部完成,你就不能参加考试和毕业考核。因此最好不要积攒太多的处分。","help-punishments-7":"任务失败被给予的处分数:日常课程任务失败(%s 项),课程考试失败(%s 项),毕业考核失败(%s 项)。","help-clubs-header":"社团","help-clubs-1":"参加社团会给你不同的奖励,例如获得请假条或降低课程任务难度。","help-clubs-2":"社团是对课程内容的补充,你可以自由选择社团。","help-clubs-3":"奖励领取之后就不能退还了,所带来的影响将会持续一整天直到转点。","help-clubs-4":"一些社团活动和课程内容重复了,这时你只需要完成课程任务即可获得社团奖励。","help-clubs-5":"在领取奖励之前,先看看自己当日需要参加的课程内容。你一定不想自己因为社团任务被迫戴一整天口塞吧。","help-clubs-6":"你最多同时参加 %s 个社团,同时获取 %s 个社团福利。","help-clubs-7":"在学期的最后阶段,你会解锁精英社团。精英社团会给你更丰厚的奖励,与此同时也会给你更难的挑战。","help-clubs-8":"注意,你最多参加 %s 个精英社团。","help-clubs-9":"社团奖励领取之后,当日不可以取消!","help-clubs-10":"所有领取过的奖励会在次日刷新。","help-clubs-11":"注意,你最多只能从社团福利中获得 50% 的难度减免。即如果你在普通社团里得到50%的难度减免,同时你在精英社团里得到25%的减免,那么你最终只会得到50%的总减免,而非75%。","help-partners-header":"基/姬友","help-partners-content":"P基/姬友和社团很像,会给你带来相应的福利,例如给你额外的出勤点数。但不同之处在于,基/姬友会让你的课程难度增加。你最多可以同时拥有 %s 个基/姬友。Ta们带来的影响会持续一整天,并在次日重置。","help-tasks-header":"任务与计时器","help-tasks-content":'游戏中的专业和班级都有任务系统。在你的课程表页面上,你可以选择你的当日课程要做的任务。每个任务可以有多个子任务 一旦你开始一个任务,你会看到新的任务显示出来,在这里你可以启动它的计时器,或者按 "完成",如果任务不依赖于时间。定时器可以被暂停/恢复,并且它们可以在不同的日子里延续。如果任务从前一天开始但持续到第二天,你不会被处分。如果你的任务完成失败了,你必须按下 "失败 "键来接受处分。所有课程的计时器也可以在该课程的详情页面上看到。',"help-grades-header":"绩点","help-grades-content":"每当你参加并完成课堂任务时,你将获得 1 个出勤点,如果你有修改器(来自基/姬友或社团),你可以获得额外的出勤点。根据这些出勤点,你将在参加考试后得到一个分数。这取决于你决定你想在考试中表现得多好。你获得的成绩将用于计算你的专业成绩。如果你已经完成了一门课,想提高自己的成绩,你可以随时重修。如果你重修一门课,你需要重考,并取消该课的相关学分,但你将保留已经获得的出勤点。你每更换一次处分,你就将会失去你当前正在上的一门随机课程的出勤点。<br> 想要得到不同等级的绩点,你至少需要获得如下的出勤点数:","help-credits-header":"学分","help-credits-content":"毕业需要至少 %s 学分。一种参考的选课方法是 4 个入门课程,3 个普通课程,2 个专家课程和 2 个大师课程。你只有选修了课程之后才会解锁考试内容。完成考试后,你就会得到相应的学分。","help-graduating-header":"毕业","help-graduating-content":'你需要先完成所有的专业必修课,并得到 %s 学分,你才可以尝试毕业。某些专业毕业考核甚至可能会持续一个月。如果你在毕业考核的时候失败了,你需要按下 "失败" 按钮来受到 %s 个处分。某些专业毕业考核也会有额外的 "惩罚" 按钮,用来延长任务时长。当你从某个专业毕业,该专业就会被标记为 "完成",你上过的课程都会被重置。你可以继续选修另一门专业,为你的 "职业" 道路打好基础。',"help-roulette-header":"高潮轮盘系统","help-roulette-content":"在校期间除课程要求之外,你能否高潮取决于%s高潮轮盘%s的结果。你每天仅有一次宝贵的机会去碰碰运气。"};class n{static getLanguage(){return(navigator.languages&&navigator.languages.length?navigator.languages[0]:navigator.userLanguage||navigator.language||navigator.browserLanguage||"en").toLowerCase().substr(0,2)}static getTodaysDay(){return n.getNow().day()}static getNow(){return moment().add(n.getSetting("timeOffsetSelection")||0,"hours")}static getNowUnix(){return n.getNow().unix()}static getMoment(e){return moment(e)}static hasMatchingDate(e){if(n.getSetting("enableEasyMode"))return!0;const t=n.getTodaysDay();return 0===t||6===t||e.filter(e=>e===t).length>0}static daysToString(e){return e.map(e=>n.weekdays[e]).join(", ")}static get weekdays(){return moment.weekdays()}static getAcceptedDisclaimer(){return localStorage.getItem("acceptedDisclaimer")||!1}static setAcceptedDisclaimer(){localStorage.setItem("acceptedDisclaimer",!0)}static getlastRequestedFeedback(){return localStorage.getItem("lastRequestedFeedback")||void 0}static setlastRequestedFeedback(e){localStorage.setItem("lastRequestedFeedback",e)}static getTutorial(){try{return localStorage.getItem("tutorial")||""}catch(e){return"mapping;"}}static setTutorial(e){localStorage.setItem("tutorial",e)}static getLastPage(){return localStorage.getItem("lastPage")}static setLastPage(e){localStorage.setItem("lastPage",e)}static getSettings(){try{return JSON.parse(localStorage.getItem("settings"))||n.getDefaultSettings()}catch(e){return{...n.getDefaultSettings(),noSettings:!0}}}static setSettings(e){e.noSettings||localStorage.setItem("settings",JSON.stringify(e))}static getDefaultSettings(){return{enableHardcorePunishments:!1,enableEasyMode:!1,enableAutoHide:!0,enableBackToTop:!0,hideActiveClasses:!1,hideFinishedClasses:!0,autoExtendCarousel:!0,showConfirmations:!0,useTableView:!0,style:"darkly",useImageHoster:!1,sessionToken:void 0,timeOffsetSelection:0,useImageExport:!1,useBlobExport:!0,useDynamicExport:!1,useBase64Export:!1,compressExports:!0,completeExports:!1}}static getSetting(e){let t=n.getSettings()[e];return null!=t?t:n.getDefaultSettings()[e]}static setSetting(e,t){let a=n.getSettings();a[e]=t,n.setSettings(a)}static getScheduledToasts(){try{return JSON.parse(localStorage.getItem("scheduledToasts"))||[]}catch(e){const t=s.getInstance();return n.showToast(t.T("scheduled-toast-error-header","Third-party cookies are disabled"),t.T("scheduled-toast-error-content","Please switch to the full Mapping Tool or enable them in the browser settings."),"warning"),[]}}static setScheduledToasts(e){try{localStorage.setItem("scheduledToasts",JSON.stringify(e))}catch(e){return}}static scheduleToast(e,t,a){void 0===a&&(a="info");let s=n.getScheduledToasts();s.push({title:e,message:t,style:a}),n.setScheduledToasts(s)}static showScheduledToast(){let e=n.getScheduledToasts();for(let t of e)n.showToast(t.title,t.message,t.style);n.setScheduledToasts([])}static showToast(e,t,a){void 0===a&&(a="info");const i=s.getInstance();$("#toastContainer").append($(`<div role="alert" aria-live="assertive" aria-atomic="true" class="toast" data-autohide="true" data-delay="10000" style="width: 300px">\n <div class="toast-header">\n <strong class="mr-auto text-${a}">${e}</strong>\n <button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="${i.T("user-confirmation-close","Close")}">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="toast-body">\n ${t}\n </div>\n </div>`).toast("show").on("hidden.bs.toast",(function(e){$(e.target).remove()})))}static getBackdropContainer(e,t){return`<div class="img-backdrop-container-corner ${t?`bg-${t} img-backdrop-container-dropshadow`:""}">\n ${e}\n </div>`}static getBackdropProgressContainer(e,t){let a="success";return t<10&&(a="warning"),`\n <div class="img-backdrop-progress-container-corner ${a?`bg-${a} img-backdrop-container-dropshadow`:""}">\n <div class="progress justify-content-end">\n <div class="progress-bar bg-${a}" role="progressbar" style="width: ${t}%"></div>\n <div class="custom-progress-text position-absolute w-100">${e}</div>\n </div>\n </div>\n `}static getButtonsContainer(e){return(e=e.filter(e=>e&&e.length>0)).length<=0?"":`<div class="img-button-container-left-corner">\n ${e.join('<div class="img-button-seperator"></div>')}\n </div>`}static toggleCard(e){$("#"+e).toggleClass("active")}static getRandomString(e){return e||(e=10),[...Array(e)].map(e=>(~~(36*Math.random())).toString(36)).join("")}static scrollBackToTop(){$("html, body").animate({scrollTop:0},800)}static isToday(e){return moment.unix(e).isSame(n.getNow(),"day")}static requestUserConfirmation(e,t,a){if(!n.getSetting("showConfirmations"))return void a();const i=s.getInstance();let o="requestUserConfirmationModal-"+n.getRandomString(),r=`<div class="modal" id="${o}">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">${e}</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="${i.T("user-confirmation-close","Close")}">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <p>${t}</p>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-primary" data-dismiss="modal" ${a?`onClick="${wi.register(()=>{a()})}"`:""}>${i.T("user-confirmation-yes","Yes")}</button>\n <button type="button" class="btn btn-secondary" data-dismiss="modal">${i.T("user-confirmation-cancel","Cancel")}</button>\n </div>\n </div>\n </div>\n </div>`;$("#confirmationContainer").append(r),$("#"+o).modal({keyboard:!1}).modal("show").on("hidden.bs.modal",(function(e){$("#"+o).modal("dispose").remove()}))}static hasScript(e){const t=Object.getOwnPropertyNames(document).concat(Object.getOwnPropertyNames(Object.getPrototypeOf(Object.getPrototypeOf(document)))).filter((function(e){return!e.indexOf("on")&&(null==document[e]||"function"==typeof document[e])})).join("|"),a=new RegExp(`\\s(?:${t})=`,"i");return/<script(?: |>)/.test(e)||a.test(e)}static optimizeImage(e,t){const a=o.imageOptimization,s=new Image;s.src=e,s.onload=()=>{const e=document.createElement("canvas"),i=e.getContext("2d");let n=0,o=0;s.height>s.width&&s.height>a.height?(n=a.height,o=s.width*(a.height/s.height)):s.width>s.height&&s.width>a.width?(o=a.width,n=s.height*(a.width/s.width)):(o=s.width,n=s.height),e.width=o,e.height=n,i.drawImage(s,0,0,o,n),i.canvas.toBlob((function(e){const s=i.canvas.toDataURL(a.filemime,a.quality);t&&t(s,e)}),a.filemime,a.quality)}}static getGrade(e,t){if(!Number.isInteger(e)||!Number.isInteger(t))return"F";const a=e/t;for(const e of Object.entries(o.grades))if(a>=e[1])return e[0];return"F"}static getCurrentUrl(){return window.location.origin.endsWith("hwcdn.net")?"https://projectuniversity.net/":`${window.location.origin}${window.location.pathname}`}static extendDistPath(){return window.IS_RAW?"dist/":""}static dataURLtoFile(e,t){for(var a=e.split(","),s=a[0].match(/:(.*?);/)[1],i=atob(a[a.length-1]),n=i.length,o=new Uint8Array(n);n--;)o[n]=i.charCodeAt(n);return new File([o],t,{type:s})}static async encryptExportImage(e,t){const a=n.stringToArraybuffer(n.compressJSObjectToString(e));return{imageData:await n.appendDataToImage(t,a),ImageType:t.type}}static decryptExportImage(e){const t=n.readDataFromImage(e);if(t instanceof Uint8Array&&t.length>0){return n.decompressJsonFromString(n.arraybufferToString(t))}}static async loadImageFile(e){try{const t=await fetch(e);if(!t.ok)throw new Error("HTTP error! status: "+t.status);const a=await t.blob();return new File([a],"save.jpg",{type:"image/jpeg"})}catch(e){throw console.error("Error loading the image file:",e),e}}static async appendDataToImage(e,t){const a=await e.arrayBuffer(),s=new Uint8Array(a);let i=s.length;for(let e=s.length-2;e>=0;e--)if(255===s[e]&&217===s[e+1]){i=e+2;break}const n=new Uint8Array(i+t.length);return n.set(s.subarray(0,i)),n.set(t,i),n}static readDataFromImage(e){const t=new Uint8Array(e);let a=t.length;for(let e=t.length-2;e>=0;e--)if(255===t[e]&&217===t[e+1]){a=e+2;break}return t.subarray(a)}static deepClone(e){return JSON.parse(JSON.stringify(e))}static canCompress(){return"undefined"!=typeof z&&z.hasOwnProperty("inflate")}static shouldCompress(){return n.canCompress()&&n.getSetting("compressExports")}static shouldExportComplete(){return n.canCompress()&&n.getSetting("completeExports")}static isCompleteSavegame(e){return!(!e.hasOwnProperty("primaryMapData")||!e.hasOwnProperty("subMapData"))}static compressJSObjectToString(e){return btoa(n.arraybufferToString(z.deflate(n.stringToArraybuffer(encodeURIComponent(JSON.stringify(e)))).buffer))}static decompressJsonFromString(e){return decodeURIComponent(n.arraybufferToString(z.inflate(n.stringToArraybuffer(atob(e)))))}static arraybufferToString(e){const t=new Uint8Array(e);let a="",s=0;for(;s<t.length;)a+=String.fromCharCode(t[s++]);return a}static stringToArraybuffer(e){const t=new Uint8Array(e.length);for(let a=0;a<e.length;++a)t[a]=e.charCodeAt(a);return t}static async getServiceWorkerStatus(){if(!1 in window.navigator||!window.navigator.serviceWorker)return o.serviceWorkerStatus.inactive;const e=await window.navigator.serviceWorker.getRegistration();return e?e.active?o.serviceWorkerStatus.active:e.installing?o.serviceWorkerStatus.installing:e.waiting?o.serviceWorkerStatus.waiting:o.serviceWorkerStatus.unknown:o.serviceWorkerStatus.unregistered}}class o{static get serviceWorkerStatus(){return{unregistered:{id:"unregistered",type:"danger",useable:!1},inactive:{id:"inactive",type:"danger",useable:!1},active:{id:"active",type:"success",useable:!0},waiting:{id:"waiting",type:"warning",useable:!1},installing:{id:"installing",type:"warning",useable:!1},unknown:{id:"unknown",type:"warning",useable:!1}}}static get saveGameService(){return"https://save.projectuniversity.net"}static get privateBinService(){return"https://privatebin.net/"}static get imageService(){return"https://imgbb.com/"}static get styles(){return{darkly:{name:"darkly",navbar:"dark",bg:"dark"},pink:{name:"pink",navbar:"dark",bg:"dark"},cosmo:{name:"cosmo",navbar:"dark",bg:"primary"},minty:{name:"minty",navbar:"dark",bg:"primary"}}}static get imageOptimization(){return{height:800,width:600,filemime:"image/jpeg",quality:.75}}static isImage(e){return"image/jpeg"===e}static get exportTextType(){return"text/plain;charset=utf-8"}static get exportCompressedType(){return"application/octet-stream"}static get exportImageType(){return"image/jpg"}static get exportOptions(){return{pu_complete:{linkFilenamePrefix:"pu_complete",exportFilenamePrefix:"pu_complete_",textExportExtension:"puC",imageExportExtension:"jpg",srcImage:"logo_complete"},pu_save:{linkFilenamePrefix:"pu_save",exportFilenamePrefix:"pu_save_",textExportExtension:"puSave",imageExportExtension:"jpg",srcImage:"logo_save"},pu_task:{linkFilenamePrefix:"pu_task",exportFilenamePrefix:"pu_task_",textExportExtension:"puTask",imageExportExtension:"jpg",srcImage:"logo_task"},pu_map:{linkFilenamePrefix:"pu_map",exportFilenamePrefix:"pu_map_",textExportExtension:"puMap",imageExportExtension:"jpg",srcImage:"logo_map"},pu_game_map:{linkFilenamePrefix:"pu_game_map",exportFilenamePrefix:"pu_gamemap_",textExportExtension:"pu",imageExportExtension:"jpg",srcImage:"logo_gamemap"}}}static get grades(){return{"A+":.96,A:.88,"A-":.8,"B+":.76,B:.68,"B-":.6,"C+":.52,C:.44,"C-":.4,"D+":.32,D:.24,"D-":.12,F:0}}static get maxAttendances(){return{beginner:25,intermediate:25,advanced:25,master:25}}static get expectedAttendance(){return{beginner:3,intermediate:3,advanced:3,master:3}}static get maxConcurrentClasses(){return 20}static get maxTaskPause(){return 240}static get maxConcurrentPunishments(){return 10}static get maxConcurrentClubs(){return 10}static get maxConcurrentEliteClubs(){return 3}static get maxConcurrentClubPerks(){return 15}static get maxConcurrentPartnerPerks(){return 10}static get maxConcurrentPartner(){return 8}static get failDailyPunishment(){return 1}static get failCustomTask(){return 1}static get failExamPunishment(){return 2}static get failThesisPunishment(){return 5}static get classBeginnerCredits(){return 10}static get classIntermediateCredits(){return 10}static get classAdvancedCredits(){return 20}static get classMasterCredits(){return 25}static get baseTaskMultiplier(){return 1}static get currentVersion(){return 1}static get classesExamCredits(){return{beginner:o.classBeginnerCredits,intermediate:o.classIntermediateCredits,advanced:o.classAdvancedCredits,master:o.classMasterCredits}}static get rickRate(){return 1e3}static get autoHideTimer(){return 1}static get majorDropPunishments(){return 10}static get defaultTaskListSize(){return 2}}class r{constructor(e,t){let a=indexedDB.open("PU",1);a.onupgradeneeded=function(e){this.db=e.target.result,this.db.onabort=function(e){console.error("db abort",e)},this.db.onclose=function(e){console.error("db close",e)},this.db.onerror=function(e){console.error("db error",e)},this.db.createObjectStore("maps",{keyPath:"mapData.mapId"}).createIndex("lastSaved","lastSaved",{unique:!1})}.bind(this),a.onsuccess=function(t){if(this.db=t.target.result,void 0!==e&&e(this.db),void 0!==this.successEvents)for(let e=this.successEvents.length-1;e>=0;e--)this.successEvents.pop(e)()}.bind(this),a.onerror=function(e){void 0!==t&&t(e.target.errorCode)}}static getInstance(e,t){return void 0===this.instance&&(this.instance=new r(void 0,t)),void 0!==e&&(void 0!==this.instance.db?e():this.instance.registerLoadFinished(e)),this.instance}registerLoadFinished(e){void 0===this.successEvents&&(this.successEvents=[]),this.successEvents.push(e)}drop(e){this.db.close(),indexedDB.deleteDatabase("PU").onsuccess=function(){this.instance=void 0,this.db=void 0,e()}}static showDefaultError(e){console.error("db error",e),n.showToast("Database error","While attempting to save your progress an database error occured. Progress might have been lost.","danger")}addMap(e,t,a){if(!e.mapId)return void a("This map is missing an ID!");e.moduleId&&(e.mapId=`${e.mapId}(${e.moduleId})`);let s={mapData:e,lastSaved:n.getNowUnix()-60,saveGame:void 0,subMaps:[]},i=function(i){void 0!==i&&(s.saveGame=i.saveGame,s.subMaps=i.subMaps);let n=this.db.transaction(["maps"],"readwrite");n.objectStore("maps").put(s);n.onabort=function(a){void 0!==t&&t(a,e)},n.oncomplete=function(a){void 0!==t&&t(a,e)},n.onerror=function(e){void 0!==a?a(e.target.errorCode):r.showDefaultError(e)},n.commit()}.bind(this);this.getMap(e.mapId,e=>{i(e)})}updateMap(e,t,a){if(!e.mapData.mapId)return void a("The map ID is missing!");let s=this.db.transaction(["maps"],"readwrite");s.objectStore("maps").put(e);s.onabort=function(e){void 0!==t&&t(e)},s.oncomplete=function(e){void 0!==t&&t(e)},s.onerror=function(e){void 0!==a?a(e.target.errorCode):r.showDefaultError(e)},s.commit()}getMap(e,t){let a=this.db.transaction(["maps"],"readonly").objectStore("maps").get(e);a.onsuccess=function(e){void 0!==t&&t(e.target.result)},a.onerror=function(e){void 0!==t?t(void 0):r.showDefaultError(e)}}switchMap(e,t){let a=this.db.transaction(["maps"],"readonly").objectStore("maps").get(e),s=this;a.onsuccess=function(e){let a=e.target.result;a.lastSaved=n.getNowUnix(),s.updateMap(a,t,t)},a.onerror=function(e){void 0!==cbFinished?cbFinished(void 0):r.showDefaultError(e)}}removeMap(e,t){let a=this.db.transaction(["maps"],"readwrite");a.objectStore("maps").delete(e),a.onabort=function(e){void 0!==t&&t(e)},a.oncomplete=function(e){void 0!==t&&t(e)},a.onerror=function(e){r.showDefaultError(e)},a.commit()}getMaps(e,t){let a=this.db.transaction(["maps"],"readonly").objectStore("maps").index("lastSaved").openCursor(null,"prev"),s=[];a.onsuccess=function(t){let a=t.target.result;null!=a?(s.push(a.value),a.continue()):void 0!==e&&e(s)},a.onerror=function(e){void 0!==t?t(e.target.errorCode):r.showDefaultError(e)}}setSubMaps(e,t,a,s){if(!Array.isArray(t))return void(void 0!==s&&s("Invalid SubMaps"));let i=this;this.getMap(e,e=>{e.subMaps=t,e.lastSaved=n.getNowUnix(),i.updateMap(e,a,s)})}setSavegame(e,t,a,s){let i=this;this.getMap(e,e=>{e.saveGame=t,e.lastSaved=n.getNowUnix(),i.updateMap(e,a,s)})}getSavegame(e,t){this.getMap(e,e=>{t(e.saveGame)})}}class d{static applyParametersToObject(e){if(void 0===e||void 0===e.tasks)return e;for(let t in e.tasks){let a=e.tasks[t],s=x.getMultiplier(a.tags);(void 0===s||s<=.1)&&(s=1),e.tasks[t]=d.applyParametersToTaskObject(a,s)}return e}static applyParametersToTaskObject(e,t){if(void 0===e||void 0===e.task||void 0===e.parameters)return e;if(null==e.task.match(/\$param\d+/))return e;const a=s.getInstance();for(let s in e.parameters){let i=e.parameters[s],n="",o=Math.round(i.value*t);if("minutes"===i.unit){let e=d.minutesToTime(o),s=d.minutesToTime(i.value);n=i.applyMultiplier&&1!==t?`${e} <a class="text-info" data-toggle="popover" data-content="${a.T("task-time-without-mod-info","The time without modifiers is %s.",[`<b>${s}</b>`])}"><i class="fas fa-question-circle"></i></a>`:""+s}else if("seconds"===i.unit){let e=a.T("time-seconds","seconds");if(1==i.value&&(e=a.T("time-second","second")),i.applyMultiplier&&1!==t){let t=a.T("time-seconds","seconds");1==o&&(t=a.T("time-second","second")),n=`${o} ${t} <a class="text-info" data-toggle="popover" data-content="${a.T("task-param-without-mod-info","The amount without modifiers is %s.",[`<b>${i.value}</b> ${e}`])}><i class="fas fa-question-circle"></i></a>`}else n=`${i.value} ${e}`}else if("punishments"===i.unit){let e=i.punTier?a.T("punishment-tier-"+i.punTier,i.punTier):a.T("task-random-punishment","random"),s=a.T("punishment-single-text","punishment");if(i.value>1&&(s=a.T("punishment-multi-text","punishments")),i.applyMultiplier&&1!==t){let t=a.T("punishment-single-text","punishment");o>1&&(t=a.T("punishment-multi-text","punishments")),n=`${o} ${e} ${t} <a class="text-info" data-toggle="popover" data-content="${a.T("task-punishment-without-mod-info","The amount without modifiers is %s.",[`<b>${i.value}</b> ${e} ${s}`])}"><i class="fas fa-question-circle"></i></a>`}else n=`${i.value} ${e} ${s}`}else n=i.applyMultiplier&&1!==t?`${o} ${i.unit} <a class="text-info" data-toggle="popover" data-content="${a.T("task-param-without-mod-info","The amount without modifiers is %s.",[`<b>${i.value}</b> ${i.unit}`])}><i class="fas fa-question-circle"></i></a>`:`${i.value} ${i.unit}`;e.task=e.task.replace("$param"+s,`<span class="text-info">${n}</span>`)}return e}static getPercentageFromText(e){let t=/([0-9]+)(?:%)/g.exec(e);return t?Number(t[1])/100:0}static getClassSkipsFromText(e){let t=/(?:Skip) ([0-9])/g.exec(e);return t?Number(t[1]):0}static dateObjectToHumanTime(e){const t=moment.duration(e,"seconds");return`${t.days()>0?t.humanize()+" ":""}${("0"+t.hours()).slice(-2)}:${("0"+t.minutes()).slice(-2)}:${("0"+t.seconds()).slice(-2)}`}static minutesToTime(e){const t=moment.duration(e,"minutes"),a=s.getInstance();let i=t.years(),n=t.months(),o=t.days(),r=t.hours(),l=t.minutes();const d=a.T("time-day-binding",";");var c=[];return i&&(1===i?c.push(`${i} ${a.T("time-year","year")}`):c.push(`${i} ${a.T("time-years","years")}`)),n&&(1===n?c.push(`${n} ${a.T("time-month","month")}`):c.push(`${n} ${a.T("time-months","months")}`)),o&&(1===o?c.push(`${o} ${a.T("time-day","day")}`):c.push(`${o} ${a.T("time-days","days")}`)),r&&((o>0||n>0||i>0)&&l<=0?c.push(a.T("time-day-binding-no-minutes","and")):(o>0||n>0||i>0)&&c.push(d),1===r?c.push(`${r} ${a.T("time-hour","hour")}`):c.push(`${r} ${a.T("time-hours","hours")}`)),(o>0||n>0||i>0||r>0)&&l>0&&c.push(a.T("time-hours-binding","and")),l&&(1===l?c.push(`${l} ${a.T("time-minute","minute")}`):c.push(`${l} ${a.T("time-minutes","minutes")}`)),c.length<=0&&c.push(a.T("time-less-then-a-minute","Less than a minute.")),c.join(" ").replace(` ${d} `,d+" ")}}class c{constructor(){this._publicImageHosterKey=void 0,this._sessionToken=void 0}static getInstance(){return void 0===this.instance&&(this.instance=new c),this.instance}get isPublicImageHosterKeySet(){return!!this._publicImageHosterKey}static getQRCanvas(e,t){return kjua({render:"image",ecLevel:"H",text:e,mPosX:50,mPosY:50,mSize:20,size:400,rounded:100,mode:"label",label:t,fontname:"Helvetica",fontcolor:"#ff0055"})}static insertQR(e,t,a){const s=`\n <div class="qr-container pt-3 mt-3 mb-3 bg-secondary">\n <div class="qr-info text-info p-2 text-center">\n This export is only valid for 24h. You can scan the QR code with your target device or use the link.\n </div>\n <div class="qr-code d-flex justify-content-center"> \n </div>\n <div class="qr-link form-group p-3">\n <input class="form-control" type="text" readonly value="${e}" onClick="this.select(); document.execCommand('copy');">\n <small class="text-muted">Click on the link above to automatically copy it into your clipboard.</small>\n </div>\n </div>\n `;$(a).html(s),$(".qr-container .qr-code",a).html(c.getQRCanvas(e,t))}static sendRequest(e,t,a){try{const i=new XMLHttpRequest;if(t){var s=i.open;i.open=function(){const e="quiet-dawn-24177.herokuapp.com",t="https://"+e+"/",a=[].slice,i=window.location.protocol+"//"+window.location.host;var n=a.call(arguments),o=/^https?:\/\/([^\/]+)/i.exec(n[1]);return o&&o[0].toLowerCase()!==i&&o[1]!==e&&(n[1]=t+n[1]),s.apply(this,n)}}let n=e.params;!n||"object"!=typeof n||n instanceof FormData||(n=e.hasOwnProperty("parseAsParameter")&&!0!==e.parseAsParameter?JSON.stringify(n):Object.keys(n).map((function(e){return`${encodeURIComponent(e)}=${encodeURIComponent(n[e])}`})).join("&")),"GET"===e.method&&(e.url=`${e.url}?${n}`,n=null),i.open(e.method,e.url),i.onload=function(){this.status>=200&&this.status<300?a(i.response):a(JSON.stringify({error:!0,status:this.status,statusText:i.statusText}))},i.onerror=function(){a(JSON.stringify({error:!0,status:this.status,statusText:i.statusText}))},e.headers&&Object.keys(e.headers).forEach((function(t){i.setRequestHeader(t,e.headers[t])})),i.send(n)}catch(e){a(JSON.stringify({error:!0,statusText:"internal error"}))}}getPublicImageHosterKey(e){if(this.isPublicImageHosterKeySet)return void e(this._publicImageHosterKey);const t=o.imageService;let a=function(t){this._publicImageHosterKey=t.match(/PF\.obj\.config\.auth_token=\"(.*?)\"/)[1],e(this._publicImageHosterKey)}.bind(this),s={parseAsParameter:!1,method:"GET",url:t,params:"",headers:{Accept:"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"}};c.sendRequest(s,!0,a)}static uploadToImageHoster(e,t){const a=o.imageService;let s=function(e){const a=JSON.parse(e);"200"==a.status_code?t({error:!1,data:a.image.url}):t({error:!0,statusText:"failed to upload image"})}.bind(this),i=function(t){var i=new FormData;i.append("source",e),i.append("type","file"),i.append("action","upload"),i.append("timestamp",(new Date).getTime()),i.append("auth_token",t);let n={parseAsParameter:!1,method:"POST",url:a+"/json",params:i,headers:{Accept:"application/json"}};c.sendRequest(n,!0,s)}.bind(this);c.getInstance().getPublicImageHosterKey(i)}static downloadFromPastebin(e,t){let a=/\?(.*?)#(.*?)(?:&|$)/.exec(e);if(!a||3!==a.length)return void t({error:!0,statusText:"invalid paste url"});let s=function(e){const a=JSON.parse(e);t({error:!1,data:a.paste})}.bind(this),i=function(e){const i=JSON.parse(e);if(i&&null!=i.status&&0==i.status)try{const e=c.getCryptTool(),t=[i.ct,i.adata],n=e.base58decode(a[2]).padStart(32,"\0"),o="";e.decipher(n,o,t).then(s)}catch(e){t({error:!0,statusText:"failed decrypt"})}else t({error:!0,statusText:"invalid response"})}.bind(this),n={parseAsParameter:!1,method:"GET",url:e,headers:{Accept:"application/json, text/javascript, */*; q=0.01","Content-Type":"application/x-www-form-urlencoded; charset=UTF-8","X-Requested-With":"JSONHttpRequest"}};c.sendRequest(n,!0,i)}exchangeSaveGameToken(e,t){const a=o.saveGameService;let s=function(e){const a=JSON.parse(e);if(a.error)return t({error:!0,statusText:"failed to check save game token"}),this._sessionToken=void 0,void n.setSetting("sessionToken",this._sessionToken);this._sessionToken=a.sessionToken,n.setSetting("sessionToken",this._sessionToken),t({error:!1,data:a})}.bind(this),i={exchangeToken:e},r={parseAsParameter:!1,method:"POST",url:a+"/token/activate",params:JSON.stringify(i),headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8"}};c.sendRequest(r,!1,s)}verifySaveGameToken(e,t){const a=o.saveGameService;let s=function(a){if(""!==a)return this._sessionToken=void 0,n.setSetting("sessionToken",this._sessionToken),void t(null);this._sessionToken=e,t(this._sessionToken)}.bind(this),i={sessionToken:e},r={parseAsParameter:!1,method:"POST",url:a+"/token/verify",params:JSON.stringify(i),headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8"}};c.sendRequest(r,!1,s)}getSessionToken(e){!this._sessionToken&&n.getSetting("sessionToken")?this.verifySaveGameToken(n.getSetting("sessionToken"),e):this._sessionToken?e(this._sessionToken):e(null)}static checkSaveGame(e,t,a){if(!e||!t)return void a({error:!0,statusText:"session token / mapId is not set"});const s=o.saveGameService;let i=function(e){const t=JSON.parse(e);t.error?a({error:!0,statusText:"failed to check save game status"}):a({error:!1,data:t})}.bind(this),n={mapId:t,sessionToken:e},r={parseAsParameter:!1,method:"POST",url:s+"/save/check",params:JSON.stringify(n),headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8"}};c.sendRequest(r,!1,i)}static uploadSaveGame(e,t,a,s){if(!e||!t)return void s({error:!0,statusText:"session token / mapId is not set"});const i=o.saveGameService;let r=function(e){const t=JSON.parse(e);t.error?s({error:!0,statusText:"failed to upload the save game"}):s({error:!1,data:t})}.bind(this),l={mapId:t,sessionToken:e,saveData:JSON.stringify(a),saveDate:n.getNow().format()},d={parseAsParameter:!1,method:"POST",url:i+"/save/upload",params:JSON.stringify(l),headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8"}};c.sendRequest(d,!1,r)}static downloadSaveGame(e,t,a){if(!e||!t)return void a({error:!0,statusText:"session token / mapId is not set"});const s=o.saveGameService;let i=function(e){const t=JSON.parse(e);t.error?a({error:!0,statusText:"failed to download the save game"}):a({error:!1,data:t})}.bind(this),n={mapId:t,sessionToken:e},r={parseAsParameter:!1,method:"POST",url:s+"/save/download",params:JSON.stringify(n),headers:{Accept:"application/json","Content-Type":"application/json; charset=UTF-8"}};c.sendRequest(r,!1,i)}static uploadToPastebin(e,t){const a=o.privateBinService;try{const s=c.getCryptTool(),i=s.getSymmetricKey(),n=function(e){if(0===(e=JSON.parse(e)).status&&!0!==e.error){const n=a.split("/"),o=s.base58encode(i);t({error:!1,url:`${n[0]}//${n[2]}${e.url}#${o}`})}else t({error:!0,message:e.message})}.bind(this),o=function(e){let t={parseAsParameter:!1,method:"POST",url:a,params:{v:2,adata:e[1],ct:e[0],meta:{expire:"1day"}},headers:{Accept:"application/json, text/javascript, */*; q=0.01","Content-Type":"application/x-www-form-urlencoded; charset=UTF-8","X-Requested-With":"JSONHttpRequest"}};c.sendRequest(t,!0,n)}.bind(this);let r=[null,"plaintext",0,0];s.cipher(i,"",JSON.stringify({paste:e}),r).then(o)}catch(e){t({error:!0,message:"A techical error occured while uploading."})}}static getCryptTool(){return function(){const e={};let t=new baseX("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz");function a(e){return decodeURIComponent(e.split("").map((function(e){return"%"+("00"+e.charCodeAt(0).toString(16)).slice(-2)})).join(""))}function s(e){return encodeURIComponent(e).replace(/%([0-9A-F]{2})/g,(function(e,t){return String.fromCharCode("0x"+t)}))}function i(e){const t=new Uint8Array(e);let a="",s=0;for(;s<t.length;)a+=String.fromCharCode(t[s++]);return a}function n(e){const t=new Uint8Array(e.length);for(let a=0;a<e.length;++a)t[a]=e.charCodeAt(a);return t}async function o(e,t,a){if(e=n(s(e)),"zlib"===t){if(void 0===a)throw"Error compressing paste, due to missing WebAssembly support.";return a.deflate(e).buffer}return e}function r(e){let t="";const a=new Uint8Array(e);window.crypto.getRandomValues(a);for(let s=0;s<e;++s)t+=String.fromCharCode(a[s]);return t}function l(e,t){console.error(e,t)}async function d(e,t,a){let i=n(e);if(t.length>0){if("rawdeflate"===a[7]){let e=await window.crypto.subtle.digest({name:"SHA-256"},n(s(t))).catch(l);t=Array.prototype.map.call(new Uint8Array(e),e=>("00"+e.toString(16)).slice(-2)).join("")}let e=n(t),o=new Uint8Array(i.length+e.length);o.set(i,0),o.set(e,i.length),i=o}const o=await window.crypto.subtle.importKey("raw",i,{name:"PBKDF2"},!1,["deriveKey"]).catch(l);return window.crypto.subtle.deriveKey({name:"PBKDF2",salt:n(a[1]),iterations:a[2],hash:{name:"SHA-256"}},o,{name:"AES-"+a[6].toUpperCase(),length:a[3]},!1,["encrypt","decrypt"]).catch(l)}function c(e,t){return{name:"AES-"+t[6].toUpperCase(),iv:n(t[0]),additionalData:n(e),tagLength:t[4]}}return e.cipher=async function(e,t,a,s){let n=await z;const u=void 0===n?"none":"zlib",p=[r(16),r(8),1e5,256,128,"aes","gcm",u],m=[];for(let e=0;e<p.length;++e)m[e]=e<2?btoa(p[e]):p[e];return 0===s.length?s=m:null===s[0]&&(s[0]=m),[btoa(i(await window.crypto.subtle.encrypt(c(JSON.stringify(s),p),await d(e,t,p),await o(a,u,n)).catch(l))),s]},e.decipher=async function(e,t,s){let o,r,u,p,m=await z;if(s instanceof Array)o=JSON.stringify(s[1]),r=(s[1][0]instanceof Array?s[1][0]:s[1]).slice(),u=s[0];else{if("string"!=typeof s)throw"unsupported message format";{let e=JSON.parse(s);o=atob(e.adata),r=[e.iv,e.salt,e.iter,e.ks,e.ts,e.cipher,e.mode,"rawdeflate"],u=e.ct}}if(r[0]=atob(r[0]),r[1]=atob(r[1]),"zlib"===r[7]&&void 0===m)throw"Error decompressing paste, due to missing WebAssembly support.";try{p=await window.crypto.subtle.decrypt(c(o,r),await d(e,t,r),n(atob(u)))}catch(e){return console.error(e),""}try{return await async function(e,t,s){if("zlib"===t||"none"===t){if("zlib"===t){if(void 0===s)throw"Error decompressing paste, due to missing WebAssembly support.";e=s.inflate(new Uint8Array(e)).buffer}return a(i(e))}return"undefined"==typeof Base64?a(RawDeflate.inflate(a(atob(i(e))))):Base64.btou(RawDeflate.inflate(Base64.fromBase64(i(e))))}(p,r[7],m)}catch(e){return l(e),e}},e.getSymmetricKey=function(){return r(32)},e.base58encode=function(e){return t.encode(n(e))},e.base58decode=function(e){return i(t.decode(e))},e}()}}class u{constructor(){this.isLoadedI=!1,this.mapI=void 0,this.mapsI=void 0,this.staticsI=void 0,this.saveBackupI=void 0,this.savegameChangedI=!1,this.multiplierChangedI=!1,this.multipliedObjectsCacheI=void 0,this.registeredTaskIntervalsI=[],this.exportStorage={},this.dbI=r.getInstance(()=>{this.dbI.getMaps(this.loadMapSuccess.bind(this),this.loadMapError.bind(this))})}static getInstance(e){return void 0===this.instance&&(this.instance=new u),void 0!==e&&this.instance.registerLoadFinished(e),this.instance}static getDBInstance(){return this.getInstance().dbI}registerLoadFinished(e){void 0===this.successEvents&&(this.successEvents=[]),this.successEvents.push(e)}loadMapSuccess(e){if(!Array.isArray(e)||e.length<=0?this.mapI=void 0:(this.mapsI=e,this.mapI=e[0],void 0===this.mapI.saveGame&&(this.mapI.saveGame={}),void 0===this.mapI.subMaps&&(this.mapI.subMaps=[]),this.mapI=this.importSubMapsContent(this.mapI,e),this.mapI.mapData=this.removeInvalidReferencesInMapData(this.mapI),this.staticsI=this.loadStatics(this.mapI),this.mapI.saveGame=this.removeInvalidReferencesFromSaveGame(this.mapI)),this.isLoadedI=void 0!==this.mapI,void 0!==this.successEvents)for(let e=this.successEvents.length-1;e>=0;e--)this.successEvents.pop(e)()}removeInvalidReferencesInMapData(e){const t=e.mapData,a=Object.keys(t.majors),s=Object.keys(t.classes);if(t.majors&&t.classes)for(let e=0;e<a.length;e++){const s=t.majors[a[e]];if(s.prerequisites&&s.prerequisites.length>0)for(let e=0;e<s.prerequisites.length;e++){const a=s.prerequisites[e];t.classes.hasOwnProperty(a)||(s.prerequisites.splice(e,1),e--)}}if(t.classes)for(let e=0;e<s.length;e++){const a=t.classes[s[e]];if(a.prerequisites&&a.prerequisites.length>0)for(let e=0;e<a.prerequisites.length;e++){const s=a.prerequisites[e];t.classes.hasOwnProperty(s)||(a.prerequisites.splice(e,1),e--)}}return t}removeInvalidReferencesFromSaveGame(e){const t=e.mapData;let a=e.saveGame,s={finishedPunishments:[],blockedPunishments:[],activePunishments:[],assignedClasses:[],assignedClubs:[],assignedPartners:[]};if(a.activeMajor&&t.majors&&!t.majors[a.activeMajor]&&(a.activeMajor=void 0),a.finishedPunishments&&a.finishedPunishments.length>0)for(let e=0;e<a.finishedPunishments.length;e++){const i=a.finishedPunishments[e];t.punishments&&t.punishments[i]||(s.finishedPunishments.push(i),a.finishedPunishments.splice(e,1),e--)}if(a.blockedPunishments&&a.blockedPunishments.length>0)for(let e=0;e<a.blockedPunishments.length;e++){const i=a.blockedPunishments[e];t.punishments&&t.punishments[i]||(s.blockedPunishments.push(i),a.blockedPunishments.splice(e,1),e--)}if(a.activePunishments&&a.activePunishments.length>0)for(let e=0;e<a.activePunishments.length;e++){const i=a.activePunishments[e];t.punishments&&t.punishments[i]||(s.activePunishments.push(i),a.activePunishments.splice(e,1),e--)}if(a.assignedClasses&&a.assignedClasses.length>0)for(let e=0;e<a.assignedClasses.length;e++){const i=a.assignedClasses[e];t.classes&&t.classes[i.id]||(s.assignedClasses.push(i),a.assignedClasses.splice(e,1),e--)}if(a.finishedClasses&&a.finishedClasses.length>0)for(let e=0;e<a.finishedClasses.length;e++){const i=a.finishedClasses[e];t.classes&&t.classes[i]||(s.finishedClasses.push(i),a.finishedClasses.splice(e,1),e--)}if(a.assignedClubs&&a.assignedClubs.length>0)for(let e=0;e<a.assignedClubs.length;e++){const i=a.assignedClubs[e];t.clubs&&t.clubs[i.id]||(s.assignedClubs.push(i),a.assignedClubs.splice(e,1),e--)}if(a.assignedPartners&&a.assignedPartners.length>0)for(let e=0;e<a.assignedPartners.length;e++){const i=a.assignedPartners[e];t.partners&&t.partners[i.id]||(s.assignedPartners.push(i),a.assignedPartners.splice(e,1),e--)}if(a.finishedMajors&&a.finishedMajors.length>0)for(let e=0;e<a.finishedMajors.length;e++){const i=a.finishedMajors[e];t.majors&&t.majors[i]||(s.finishedMajors.push(i),a.finishedMajors.splice(e,1),e--)}if(a.activeTasks&&a.activeTasks.length>0)for(let e=0;e<a.activeTasks.length;e++){const s=a.activeTasks[e];if(s.object){if("custom"==s.object.type)continue;if("class"==s.object.type){if(t.classes&&t.classes[s.object.id])continue}else if("major"==s.object.type){if(t.majors&&t.majors[s.object.id])continue}else if("punishment"==s.object.type&&t.punishments&&t.punishments[s.object.id])continue;a.activeTasks.splice(e,1),e--}}return this.saveBackupI=s,a}importSubMapsContent(e,t){if(void 0!==e&&e.hasOwnProperty("subMaps")&&e.subMaps.length>0){let a={majors:{},classes:{},clubs:{},partners:{},punishments:{}},s=t.filter(t=>e.mapData.mapId!==t.mapData.mapId&&e.subMaps.includes(t.mapData.mapId));for(const t of s){let s=t.mapData.mapId;const i=t.mapData.moduleId;if(void 0!==i&&i.length>1&&(s=/^(.*?)\(/gm.exec(t.mapData.mapId)[1]),e.mapData.mapId.startsWith(s)&&(s=""),void 0!==t.mapData.general.help&&t.mapData.general.help.length>0){Array.isArray(e.mapData.general.help)||(e.mapData.general.help=[]);for(const a of t.mapData.general.help)e.mapData.general.help.find(e=>""+e.id==`${s}${a.id}`)||e.mapData.general.help.push(a)}for(const e in t.mapData.majors)if(t.mapData.majors.hasOwnProperty(e)){const i=t.mapData.majors[e],n=`${s}${e}`;a.majors[n]=i,a.majors[n].id=n,a.majors[n].prerequisites=i.prerequisites.map(e=>`${s}${e}`)}for(const e in t.mapData.classes)if(t.mapData.classes.hasOwnProperty(e)){const i=t.mapData.classes[e],n=`${s}${e}`;a.classes[n]=i,a.classes[n].id=n,a.classes[n].prerequisites=i.prerequisites.map(e=>`${s}${e}`)}for(const e in t.mapData.clubs)if(t.mapData.clubs.hasOwnProperty(e)){const i=t.mapData.clubs[e],n=`${s}${e}`;a.clubs[n]=i,a.clubs[n].id=n}for(const e in t.mapData.partners)if(t.mapData.partners.hasOwnProperty(e)){const i=t.mapData.partners[e],n=`${s}${e}`;a.partners[n]=i,a.partners[n].id=n}for(const e in t.mapData.punishments)if(t.mapData.punishments.hasOwnProperty(e)){const i=t.mapData.punishments[e],n=`${s}${e}`;a.punishments[n]=i,a.punishments[n].id=n}}Object.assign(e.mapData.majors,a.majors),Object.assign(e.mapData.classes,a.classes),Object.assign(e.mapData.clubs,a.clubs),Object.assign(e.mapData.partners,a.partners),Object.assign(e.mapData.punishments,a.punishments)}return e}loadMapError(e){alert("failed to load maps from local storage "+e)}loadStatics(e){let t={requiredCreditsUnlockIntermediate:30,requiredCreditsUnlockAdvanced:60,requiredCreditsUnlockMaster:100,requiredCreditsForGraduation:160,requiredCreditsToUnlockEliteClubs:100,requiredCreditsToUnlockHardPunishments:100},a=0,s=0,i=0,n=0,r=0;for(const t in e.mapData.classes)if(e.mapData.classes.hasOwnProperty(t)){const a=e.mapData.classes[t];"beginner"==a.tier?s++:"intermediate"==a.tier?i++:"advanced"==a.tier?n++:"master"==a.tier&&r++}return s*=o.classBeginnerCredits,i*=o.classIntermediateCredits,n*=o.classAdvancedCredits,r*=o.classMasterCredits,a+=s,s<t.requiredCreditsUnlockIntermediate&&(t.requiredCreditsUnlockIntermediate=s),a+=i,a<t.requiredCreditsUnlockAdvanced&&(t.requiredCreditsUnlockAdvanced=a),a+=n,a<t.requiredCreditsUnlockMaster&&(t.requiredCreditsUnlockMaster=a),a<t.requiredCreditsToUnlockEliteClubs&&(t.requiredCreditsToUnlockEliteClubs=a),a<t.requiredCreditsToUnlockHardPunishments&&(t.requiredCreditsToUnlockHardPunishments=a),a+=r,a<t.requiredCreditsForGraduation&&(t.requiredCreditsForGraduation=a),t}get static(){return this.staticsI}get multiplierChanged(){return this.multiplierChangedI}multiplierChanged(){this.multiplierChangedI=!0}get multiplierCacheHandler(){return{get:function(e,t,a){return e[t]},set:function(e,t,a){return!1}}}get multipliedObjectsChache(){if(this.multiplierChangedI||null==this.multipliedObjectsCacheI){let e=$.extend(!0,{},this.mapI.mapData);this.multiplierChangedI=!1;for(const t in e.classes)e.classes[t]=d.applyParametersToObject(e.classes[t]);for(const t in e.majors)e.majors[t]=d.applyParametersToObject(e.majors[t]);for(const t in e.punishments)e.punishments[t]=d.applyParametersToObject(e.punishments[t]);this.multipliedObjectsCacheI=e}return new Proxy(this.multipliedObjectsCacheI,this.multiplierCacheHandler)}resetTaskIntervals(){for(let e of this.registeredTaskIntervalsI)clearInterval(e.ref);this.registeredTaskIntervalsI=[]}addTaskInterval(e,t){for(let t of this.registeredTaskIntervalsI)t.id==e&&clearInterval(t.ref);this.registeredTaskIntervalsI=this.registeredTaskIntervalsI.filter(t=>t.id!=e),this.registeredTaskIntervalsI.push({id:e,ref:t})}get isMapLoaded(){return this.isLoadedI}get mapId(){return this.mapI.mapData.mapId}get map(){return this.mapI.mapData}get maps(){return(this.mapsI||[]).map(e=>$.extend(!0,{},e))}get savegameHandler(){let e=this;return{get:function(e,t,a){return e[t]},set:function(t,a,s){return e.savegameChangedI=!0,t[a]=s,!0}}}get wasSavegameChanged(){return this.savegameChangedI}get subMaps(){return this.mapI.subMaps}set subMaps(e){if(Array.isArray(e))return this.mapI.subMaps=e}get anyMajorFinished(){return(this.mapI.saveGame.finishedMajors||[]).length>0}setSubMaps(e,t){const a=s.getInstance();this.dbI.setSubMaps(this.mapI.mapData.mapId,this.mapI.subMaps,t=>{t&&"abort"==t.type&&n.scheduleToast(a.T("map-submap-failed-header","Setting sub map aborted"),a.T("map-submap-failed-content","PU was unable to store the new sub map assignment. This can happen if you run the website in incognito mode."),"warning"),e(t)},t)}getSavegame(){return this.isLoadedI?new Proxy(this.mapI.saveGame,this.savegameHandler):{}}setSavegame(e,t,a){if(!this.savegameChangedI)return;let i=this;const o=s.getInstance(),r=this.mapI.mapData.mapId;let l=JSON.parse(JSON.stringify(this.mapI.saveGame));this.saveBackupI&&(l.finishedPunishments=this.saveBackupI.finishedPunishments.concat(l.finishedPunishments||[]),l.blockedPunishments=this.saveBackupI.blockedPunishments.concat(l.blockedPunishments||[]),l.activePunishments=this.saveBackupI.activePunishments.concat(l.activePunishments||[]),l.assignedClasses=this.saveBackupI.assignedClasses.concat(l.assignedClasses||[]),l.assignedClubs=this.saveBackupI.assignedClubs.concat(l.assignedClubs||[]),l.assignedPartners=this.saveBackupI.assignedPartners.concat(l.assignedPartners||[]));let d=function(t){!t||"success"!=t.type&&"complete"!=t.type?t&&"abort"==t.type&&n.scheduleToast(o.T("map-save-failed-header","Save aborted"),o.T("map-save-failed-content","PU was unable to store your progress. This can happen if you run the website in incognito mode."),"warning"):(i.savegameChangedI=!1,a||this.uploadOnlineSaveGame(r,l,()=>{e&&e(t)}))}.bind(this);this.dbI.setSavegame(r,l,d,t)}checkOnlineSaveGame(e){if(this.savegameChangedI)return;if(!this.mapI||!this.mapI.mapData||!this.mapI.mapData.mapId)return;const t=this.mapI.mapData.mapId,a=s.getInstance();let i=null,o=null,r=function(t){if(t.error||!t.data)return void n.showToast(a.T("settings-download-save-sync-failed-header","Save-Sync download failed"),a.T("settings-download-save-sync-failed-content","The download of the latest SaveGame failed. You may need to create a new session."));let s=JSON.parse(t.data.saveData);s&&(this.saveBackupI=void 0,this.mapI.saveGame=s,this.savegameChangedI=!0,this.mapI.lastSaved=o,this.setSavegame(e,void 0,!0))}.bind(this),l=function(e){!e.error&&e.data?null!=e.data.saveDate&&(o=n.getMoment(e.data.saveDate).unix(),(o>this.mapI.lastSaved||!(void 0!==this.mapI.saveGame&&Object.entries(this.mapI.saveGame).length>0))&&c.downloadSaveGame(i,t,r)):n.showToast(a.T("settings-check-save-sync-failed-header","Save-Sync check failed"),a.T("settings-check-save-sync-failed-content","Checking the status of the latest SaveGame failed. You may need to create a new session."))}.bind(this),d=function(e){i=e,null!==i&&c.checkSaveGame(i,t,l)}.bind(this);c.getInstance().getSessionToken(d)}uploadOnlineSaveGame(e,t,a){const i=s.getInstance();let o=function(e){!e.error&&e.data||n.showToast(i.T("settings-upload-save-sync-failed-header","Save-Sync upload failed"),i.T("settings-upload-save-sync-failed-content","Uploading the latest SaveGame failed. You may need to create a new session."))}.bind(this),r=function(s){null!==s&&c.uploadSaveGame(s,e,t,o),a()}.bind(this);c.getInstance().getSessionToken(r)}importSaveGame(e,t,a){const i=s.getInstance();return e.mapId!==this.mapI.mapData.mapId?i.T("map-save-incompatible","SaveGame not compatible with the current map."):e.saveGame?(e.saveGame=p.resetAttendanceDeadline(e.saveGame),this.saveBackupI=void 0,this.mapI.saveGame=e.saveGame,this.savegameChangedI=!0,void this.setSavegame(t,a)):i.T("map-save-empty","SaveGame empty.")}changeMap(e){this.setSavegame(),this.isLoadedI=!1,this.dbI.switchMap(e,this.loadMapSuccess.bind(this),this.loadMapError.bind(this))}deleteMap(e,t){this.setSavegame(),this.dbI.removeMap(e,t)}async importMapFile(e){const t=s.getInstance(),a={title:t.T("map-import-confirmation-header","Import a map"),text:t.T("map-import-confirmation-conent","Are you sure that you want to import this map? Make sure to only install maps from trusted sources. Proceed at your own risk.")},i={title:t.T("map-import-failed-header","Map import failed"),text:t.T("map-import-failed-content","During the import for the selected file an error occured. You can check the console for more information.")},o={title:t.T("map-file-import-compression-error-header","Compression unavailable"),text:t.T("map-file-import-compression-error-content","The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.")},r={title:t.T("map-file-import-aborted-header","Import aborted"),text:t.T("map-file-import-aborted-content","The file you tried to import included untrusted code. To protect you the import was aborted.")},l=await _.runImport(e,i,a,o,r);n.isCompleteSavegame(l)?await this.importCompleteSavegame(l):this.importMap(l)}async importCompleteSavegame(e){const t=e=>new Promise((t,a)=>{this.dbI.addMap(e,t,a)});n.setSettings(e.settings||n.getDefaultSettings()),await t(e.primaryMapData);for(const a of e.subMapData)await t(a);await((e,t)=>new Promise((a,s)=>{this.dbI.setSubMaps(e,t,a,s)}))(e.mapId,e.subMaps),await((e,t)=>new Promise((a,s)=>{this.dbI.setSavegame(e,t,a,s)}))(e.mapId,e.saveGame),this.addMapSuccess(void 0,e.primaryMapData)}importMap(e){s.getInstance();this.mapI?this.dbI.addMap(e,this.addMapAsSubMapSuccess.bind(this),this.addMapError.bind(this)):this.dbI.addMap(e,this.addMapSuccess.bind(this),this.addMapError.bind(this))}addMapAsSubMapSuccess(e,t){n.scheduleToast(`${s.getInstance().T("map-import-submap-popup","Submap imported")}: ${t.general.title}`,""+t.general.description),this.subMaps.includes(t.mapId)||this.mapId==t.mapId||this.subMaps.push(t.mapId),this.setSubMaps(()=>{_.navigate("?page=settings")})}addMapSuccess(e,t){s.getInstance();n.scheduleToast(`${s.getInstance().T("map-import-map-popup","Map imported")}: ${t.general.title}`,""+t.general.description),this.registerLoadFinished(()=>{_.navigate("?page=help")}),this.changeMap(t.mapId)}addMapError(e){alert("failed to add the map to local storage "+e)}setExportStorageItem(e,t,a){this.exportStorage[e]={data:t,type:a,filename:e}}getExportStorageItem(e){return this.exportStorage[e]}}class p{static resetAttendanceDeadline(e){if(e.assignedClasses&&e.assignedClasses.length>0){const t=n.getNow().add(-1,"day").endOf("day").unix();e.assignedClasses=e.assignedClasses.map(e=>("active"==e.status&&e.lastAttended&&e.lastAttended<t&&(e.lastAttended=t),e))}return e}static getActiveMajor(){return u.getInstance().getSavegame().activeMajor}static setActiveMajor(e){u.getInstance().getSavegame().activeMajor=e}static getAssignedClasses(){return u.getInstance().getSavegame().assignedClasses||[]}static setAssignedClasses(e){u.getInstance().getSavegame().assignedClasses=e}static getAssignedClubs(){return u.getInstance().getSavegame().assignedClubs||[]}static setAssignedClubs(e){u.getInstance().getSavegame().assignedClubs=e}static getActivePartners(){return u.getInstance().getSavegame().assignedPartners||[]}static setActivePartners(e){u.getInstance().getSavegame().assignedPartners=e}static getRoulette(){return u.getInstance().getSavegame().roulette||[]}static setRoulette(e){u.getInstance().getSavegame().roulette=e}static getActiveTasks(){return u.getInstance().getSavegame().activeTasks||[]}static setActiveTasks(e,t){u.getInstance().getSavegame().activeTasks=e}static getFinishedMajors(){return u.getInstance().getSavegame().finishedMajors||[]}static setFinishedMajors(e){u.getInstance().getSavegame().finishedMajors=e}static getFinishedPunishments(){return u.getInstance().getSavegame().finishedPunishments||[]}static setFinishedPunishments(e){u.getInstance().getSavegame().finishedPunishments=e}static getBlockedPunishments(){return u.getInstance().getSavegame().blockedPunishments||[]}static setBlockedPunishments(e){u.getInstance().getSavegame().blockedPunishments=e}static getActivePunishments(){return u.getInstance().getSavegame().activePunishments||[]}static setActivePunishments(e){u.getInstance().getSavegame().activePunishments=e}static getEnduredPunishments(){return u.getInstance().getSavegame().enduredPunishments||0}static setEnduredPunishments(e){u.getInstance().getSavegame().enduredPunishments=e}static getClassSkipTokens(){return u.getInstance().getSavegame().classSkipTokens||0}static setClassSkipTokens(e){u.getInstance().getSavegame().classSkipTokens=e}static get classesData(){return u.getInstance().multipliedObjectsChache.classes}static get majorsData(){return u.getInstance().multipliedObjectsChache.majors}static get partnersData(){return u.getInstance().map.partners}static get clubsData(){return u.getInstance().map.clubs}static get punishmentsData(){return u.getInstance().multipliedObjectsChache.punishments}static get rouletteData(){return u.getInstance().map.rouletteOptions}static get requiredCreditsToUnlockEliteClubs(){return u.getInstance().static.requiredCreditsToUnlockEliteClubs}static get requiredCreditsUnlockIntermediate(){return u.getInstance().static.requiredCreditsUnlockIntermediate}static get requiredCreditsUnlockAdvanced(){return u.getInstance().static.requiredCreditsUnlockAdvanced}static get requiredCreditsUnlockMaster(){return u.getInstance().static.requiredCreditsUnlockMaster}static get requiredCreditsForGraduation(){return u.getInstance().static.requiredCreditsForGraduation}static get requiredCreditsToUnlockHardPunishments(){return u.getInstance().static.requiredCreditsToUnlockHardPunishments}}class m{static get expectedParameters(){return{getView:[]}}static getView(){const e=p.clubsData,t=s.getInstance();let a="",i="";for(const t in e){let s=e[t];const n=m.getCardHtml(s);"normal"===s.tier?a+=n:"elite"===s.tier&&(i+=n)}return`<div class="container-fluid pt-3">\n <div class="row">\n <div id="action-navigation" class="d-none d-lg-block col-lg-2 col-md-4 col-sm-12">\n <nav class="nav flex-column">\n <ul class="list-group pt-3">\n <li class="list-group-item active" aria-expanded="true">${t.T("tier-navigation","Navigation")}</li>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="0"><i class="fas fa-chevron-circle-right"></i> ${t.T("club-tier-normal","Normal")}</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="1"><i class="fas fa-chevron-circle-right"></i> ${t.T("club-tier-elite","Elite")}</a>\n </ul>\n </nav>\n </div>\n <div class="col">\n <div id="customCarousel" class="carousel slide" data-interval="false" data-ride="carousel">\n <ol class="carousel-indicators top-carousel d-lg-none">\n <li data-target="#customCarousel" data-slide-to="0" class="active"></li>\n <li data-target="#customCarousel" data-slide-to="1"></li>\n </ol>\n <div class="carousel-inner">\n <div class="carousel-item active">\n <h3>${t.T("club-normal-header","Clubs")}</h3>\n <div class="row carouselCardPadding">\n ${a}\n </div>\n </div>\n <div class="carousel-item">\n <h3>${t.T("club-elite-header","Elite clubs")}</h3>\n <h6 class="text-muted">${t.T("club-elite-requirement","You need at least %s credits to unlock elite clubs.",[p.requiredCreditsToUnlockEliteClubs])}</h6>\n <div class="row carouselCardPadding">\n ${i}\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>`}static getCardHtml(e){const t=s.getInstance();let a=h.isClubLocked(e.id),i=h.isClubActive(e.id),o=n.getRandomString();return`<div class="min-card-container col-lg-2 col-md-3 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n\n <div class="img-container-overview no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=club&clubId="+e.id)})}">\n <img src="${e.image}" class="card-img-top ${a?" img-locked":""}${i?" img-active":""}">\n </a>\n ${i?n.getBackdropContainer(t.T("club-status-active","Active")+' <i class="fas fa-heart"></i>',"success"):""}\n ${a?n.getBackdropContainer(t.T("club-status-locked","Locked")+' <i class="fas fa-lock"></i>',"danger"):""} \n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("showActivities-"+o)})}" class="btn btn-dark text-white"><i class="fas fa-info-circle"></i></a>`,m.getJoinButton(e.id),m.getDropButton(e.id)])}\n <div id="showActivities-${o}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n <ul class="list-group list-group-flush ">\n ${m.getPerksHtml(e.id)}\n </ul>\n </div>\n </div>\n </div>\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">${e.name}</h5>\n <p class="card-text">${e.description}</p>\n </div>\n </div>\n </div>`}static getJoinButton(e){return h.isJoinButtonAvailable(e)?`<button onClick="${wi.register(()=>{h.joinClub(e)})}" type="button" class="btn btn-dark text-success"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("club-card-join-button","Join")}</button>`:""}static getDropButton(e){return h.isDropButtonAvailable(e)?`<button onClick="${wi.register(()=>{h.dropClub(e)})}" type="button" class="btn btn-dark text-danger"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("club-card-drop-button","Drop")}</button>`:""}static getPerksHtml(e){const t=s.getInstance();let a=h.getClubPerks(e),i="";for(let e in a)i+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-warning">${t.T("club-modifier-activity","Activity")}:</b>\n </div> \n <div class="col">${a[e].job}</div>\n </div>\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-info">${t.T("club-modifier-perk","Perk")}:</b>\n </div>\n <div class="col">${a[e].perk}</div>\n </div>\n </li>`;return i}}class g{static get expectedParameters(){return{getView:["clubId"]}}static getView(e){const t=e.clubId,a=h.isClubLocked(t),i=h.isClubActive(t),n=s.getInstance();let o=p.clubsData[t];return`<div class="container-fluid">\n <h2 class="text-center py-3">${o.name}</h2>\n <div class="container">\n <div class="row">\n <div class="col-lg-6 col-sm-6">\n <div class="img-container-overview">\n <a onClick="${wi.register(()=>{_.navigateBack()})}">\n <img src="${o.image}" class="img-fluid img-rounded img-thumbnail">\n </a>\n ${a?`<div class="alert-danger text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${n.T("club-status-locked","Locked")}</b></div>`:""}\n ${i?`<div class="alert-success text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${n.T("club-status-active","Active")}</b></div>`:""}\n </div>\n </div>\n <div class="col-lg-6 col-sm-6">\n <p>${o.description}${g.getCommentHtml(o.comment)}</p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item text-info"><b>${n.T("club-detail-modifiers","Available perks")}:</b></li>\n ${g.getPerksHtml(o.id)}\n </ul>\n <div class="card-body">\n ${g.getJoinButton(o.id)}\n ${g.getDropButton(o.id)}\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static getCommentHtml(e){return!e||null==e||e.length<=0?"":`<br><span class="text-muted">${e}</span>`}static getPerksHtml(e){const t=s.getInstance();let a=h.getClubPerks(e),i="";for(let s in a)i+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-warning">${t.T("club-modifier-activity","Activity")}:</b>\n\n </div> \n <div class="col">${a[s].job}</div>\n </div>\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-info">${t.T("club-modifier-perk","Perk")}:</b>\n </div>\n <div class="col">${a[s].perk}\n ${g.activatePerk(e,s)}\n </div>\n </div>\n </li>`;return i}static getJoinButton(e){return h.isJoinButtonAvailable(e)?`<br><button onClick="${wi.register(()=>{h.joinClub(e)})}" type="button" class="btn btn-outline-success mr-2"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("club-detail-join-button","Join club")}</button>`:""}static getDropButton(e){return h.isDropButtonAvailable(e)?`<br><button onClick="${wi.register(()=>{h.dropClub(e)})}" type="button" class="btn btn-outline-danger mr-2"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("club-detail-drop-button","Drop club")}</button>`:""}static activatePerk(e,t){const a=h.isClubActive(e),i=h.isPerkActive(e,t),o=h.canActivatePerk(e,t),r=h.isMaxPerksActive(),l=n.getSetting("enableEasyMode");return!r&&o&&a&&!i?`<button onClick="${wi.register(()=>{h.activatePerk(e,t)})}" type="button" class="btn btn-outline-info mt-1" style="width: 100%;"><i class="fas fa-play-circle"></i> ${s.getInstance().T("club-perk-activate-button","Activate perk")}</button>`:a&&i&&l?`<button onClick="${wi.register(()=>{h.deactivatePerk(e,t)})}" type="button" class="btn btn-outline-success mt-1" style="width: 100%;"><i class="fas fa-undo"></i> ${s.getInstance().T("club-perk-deactivate-button","Deactivate perk")}</button>`:a&&i?`<button type="button" class="btn btn-outline-success mt-1" style="width: 100%;"><i class="fas fa-check-circle"></i> ${s.getInstance().T("club-perk-active-button","Perk is active")}</button>`:""}}class h{static isJoinButtonAvailable(e){const t=h.getJoinedClub(e),a=h.isClubLocked(e),s=h.isMaxClubJoined();return!t&&!a&&!s}static isDropButtonAvailable(e){return h.getJoinedClub(e)}static getClubPerks(e){return p.clubsData[e].perks}static getJoinedClub(e){const t=p.getAssignedClubs().filter(t=>t.id==e);return 0!==t.length&&t[0]}static joinClub(e){if(h.getJoinedClub(e))return;const t=s.getInstance();if(h.isMaxClubJoined())return void n.showToast(t.T("club-max-clubs-header","Maximum clubs assigned"),t.T("club-max-clubs-content","You are only allowed to join %s clubs at a time.",[o.maxConcurrentClubs]));let a=p.getAssignedClubs(),i={id:e,activePerks:{}};a.push(i),p.setAssignedClubs(a),_.getInstance().inAppNavigation("?page=club&clubId="+e)}static dropClub(e){let t=p.getAssignedClubs();t=t.filter(t=>t.id!==e),p.setAssignedClubs(t),_.getInstance().inAppNavigation("?page=clubs")}static isClubActive(e){return!!h.getJoinedClub(e)}static isPerkActive(e,t){const a=h.getJoinedClub(e);return!!a&&(void 0!==a.activePerks[t]&&(!!a.activePerks[t].active&&!!n.isToday(a.activePerks[t].activatedOn)))}static canActivatePerk(e,t){return"8"!==e||h.getActiveClubPerks(e).length<=0}static isMaxPerksActive(){return h.getAllActivePerks()>=o.maxConcurrentClubPerks}static isMaxClubJoined(){return p.getAssignedClubs().length>=o.maxConcurrentClubs}static getActiveClubPerks(e){if(!h.isClubActive(e))return[];return h.getAllActivePerks().filter(t=>t.clubId===e)}static getAllActivePerks(){let e=p.getAssignedClubs(),t=[];for(let a in e){let s=e[a],i=s.id,n=s.activePerks;for(let e in n)h.isPerkActive(i,e)&&t.push({clubId:i,perkId:e})}return t}static activatePerk(e,t){const a=s.getInstance();if(h.isMaxPerksActive())return void n.showToast(a.T("club-max-perks-header","Maximum club perks active"),a.T("club-max-perks-content","You are only allowed to activate %s club perks at a time.",[o.maxConcurrentClubPerks]));if(!h.canActivatePerk(e,t))return void n.showToast(a.T("club-perk-unavailable-header","Perk can not be activated"),a.T("club-perk-unavailable-content","It's currently not possible to activate this perk."));let i=S.getActiveTags(),r=p.clubsData[e].perks[t],l=r.tags;if(i.length<=0||l.length>0&&!l.some(e=>i.includes(e)))return void n.showToast(a.T("club-perk-unavailable-tag-header","Perk can not be activated"),a.T("club-perk-unavailable-tag-content","You don't have any class with matching tags active."));const d=p.getAssignedClubs().map(a=>(a.id===e&&(a.activePerks[t]={active:!0,activatedOn:n.getNowUnix()}),a));p.setAssignedClubs(d),x.perkActivated(r),u.getInstance().multiplierChanged(),_.inAppReload()}static deactivatePerk(e,t){const a=s.getInstance();if(!h.isPerkActive(e,t))return void n.showToast(a.T("club-perk-not-active-header","Perk can not be deactivated"),a.T("club-perk-not-active-content","It's currently not possible to deactivate this perk."));const i=p.getAssignedClubs().map(a=>(a.id===e&&delete a.activePerks[t],a)),o=p.clubsData[e].perks[t];p.setAssignedClubs(i),x.perkDeactivated(o),u.getInstance().multiplierChanged(),_.inAppReload()}static isClubLocked(e){const t=p.clubsData[e];let a=0,s=S.collectedCredits;return"elite"===t.tier&&(a=p.requiredCreditsToUnlockEliteClubs),s<a}}class v{static get expectedParameters(){return{getView:[]}}static getView(){const e=p.partnersData;let t="";for(const a in e){let s=e[a];t+=v.getCardHtml(s)}return`<div class="container-fluid">\n <div class="row pt-3">${t}</div>\n </div>`}static getCardHtml(e){const t=f.isPartnerActive(e.id),a=n.getRandomString();return`<div class="min-card-container col-lg-3 col-md-4 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=partner&partnerId="+e.id)})}">\n <img src="${e.image}" class="card-img-top${t?" img-active":""}">\n </a>\n ${t?n.getBackdropContainer(s.getInstance().T("partner-status-active","Active")+' <i class="fas fa-heart"></i>',"success"):""}\n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("showActivities-"+a)})}" class="btn btn-dark text-white"><i class="fas fa-info-circle"></i></a>`,v.getJoinButton(e.id),v.getDropButton(e.id)])}\n <div id="showActivities-${a}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n <ul class="list-group list-group-flush">\n ${v.getPerksHtml(e.id)}\n </ul>\n </div>\n </div>\n </div>\n \n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">${e.name}</h5>\n <p class="card-text">${e.description}</p>\n </div>\n </div>\n </div>`}static getJoinButton(e){return f.isJoinButtonAvailable(e)?`<button onClick="${wi.register(()=>{f.addPartner(e)})}" type="button" class="btn btn-dark text-success"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("partner-card-join-button","Join")}</button>`:""}static getDropButton(e){return f.isDropButtonAvailable(e)?`<button onClick="${wi.register(()=>{f.leavePartner(e)})}" type="button" class="btn btn-dark text-danger"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("partner-card-drop-button","Drop")}</button>`:""}static getPerksHtml(e){const t=s.getInstance();let a=f.getPartnerPerks(e),i="";for(let e in a)i+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-warning">${t.T("partner-modifier-activity","Activity")}:</b>\n </div> \n <div class="col">${a[e].job}</div>\n </div>\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-info">${t.T("partner-modifier-perk","Perk")}:</b>\n </div>\n <div class="col">${a[e].perk}</div>\n </div>\n </li>`;return i}}class b{static get expectedParameters(){return{getView:["partnerId"]}}static getView(e){const t=e.partnerId,a=f.isPartnerActive(t),i=s.getInstance();let n=p.partnersData[t];return`<div class="container-fluid">\n <h2 class="text-center py-3">${n.name}</h2>\n <div class="container">\n <div class="row">\n <div class="col-lg-6 col-sm-6">\n <div class="img-container-overview">\n <a onClick="${wi.register(()=>{_.navigateBack()})}">\n <img src="${n.image}" class="img-fluid img-rounded img-thumbnail">\n </a>\n ${a?`<div class="alert-success text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${i.T("partner-status-active","Active")}</b></div>`:""}\n </div>\n </div>\n <div class="col-lg-6 col-sm-6">\n <p>${n.description}</p>\n <ul class="list-group list-group-flush container">\n <li class="list-group-item text-info"><b>${i.T("partner-detail-modifiers","Available perks")}:</b></li>\n ${b.getPerksHtml(n.id)}\n </ul>\n <div class="card-body">\n ${b.getJoinButton(n.id)}\n ${b.getDropButton(n.id)}\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static getPerksHtml(e){const t=s.getInstance();let a=f.getPartnerPerks(e),i="";for(let s in a)i+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-warning">${t.T("partner-modifier-activity","Activity")}:</b>\n </div> \n <div class="col">${a[s].job}</div>\n </div>\n <div class="row pl-2">\n <div class="col" style="max-width: 73px; padding-right: 0px; text-align:right;">\n <b class="text-info">${t.T("partner-modifier-perk","Perk")}:</b>\n </div>\n <div class="col">${a[s].perk}\n ${b.activatePerk(e,s)}\n </div>\n </div>\n </li>`;return i}static getJoinButton(e){return f.isJoinButtonAvailable(e)?`<br><button onClick="${wi.register(()=>{f.addPartner(e)})}" type="button" class="btn btn-outline-success mr-2"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("partner-detail-add-button","Add partner")}</button>`:""}static getDropButton(e){return f.isDropButtonAvailable(e)?`<br><button onClick="${wi.register(()=>{f.leavePartner(e)})}" type="button" class="btn btn-outline-danger mr-2"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("partner-detail-leave-button","Leave partner")}</button>`:""}static activatePerk(e,t){const a=f.isPartnerActive(e),i=f.isPerkActive(e,t),o=f.isMaxPerksActive(),r=n.getSetting("enableEasyMode"),l=s.getInstance();return o||!a||i?a&&i&&r?`<button onClick="${wi.register(()=>{f.deactivatePerk(e,t)})}" type="button" class="btn btn-outline-success mt-1" style="width: 100%;"><i class="fas fa-undo"></i> ${s.getInstance().T("partner-perk-deactivate-button","Deactivate perk")}</button>`:a&&i?`<button type="button" class="btn btn-outline-success mt-1" style="width: 100%;"><i class="fas fa-check-circle"></i> ${l.T("partner-perk-active-button","Perk is active")}</button>`:"":`<button onClick="${wi.register(()=>{f.activatePerk(e,t)})}" type="button" class="btn btn-outline-info mt-1" style="width: 100%;"><i class="fas fa-play-circle"></i> ${l.T("partner-perk-activate-button","Activate perk")}</button>`}}class f{static isJoinButtonAvailable(e){const t=f.getActivePartner(e),a=f.isMaxPartnerJoined();return!t&&!a}static isDropButtonAvailable(e){return f.getActivePartner(e)}static getPartnerPerks(e){return p.partnersData[e].perks}static isMaxPartnerJoined(){return p.getActivePartners().length>=o.maxConcurrentPartner}static isMaxPerksActive(){return f.getAllActivePerks()>=o.maxConcurrentPartnerPerks}static getAllActivePerks(){let e=p.getActivePartners(),t=[];for(let a in e){let s=e[a],i=s.id,n=s.activePerks;for(let e in n)f.isPerkActive(i,e)&&t.push({partnerId:i,perkId:e})}return t}static getActivePartner(e){const t=p.getActivePartners().filter(t=>t.id==e);return 0!==t.length&&t[0]}static addPartner(e){if(f.getActivePartner(e))return;if(f.isMaxPartnerJoined())return;let t=p.getActivePartners(),a={id:e,activePerks:{}};t.push(a),p.setActivePartners(t),_.getInstance().inAppNavigation("?page=partner&partnerId="+e)}static leavePartner(e){let t=p.getActivePartners();t=t.filter(t=>t.id!==e),p.setActivePartners(t),_.getInstance().inAppNavigation("?page=partners")}static isPartnerActive(e){return!!f.getActivePartner(e)}static isPerkActive(e,t){const a=f.getActivePartner(e);return!!a&&(void 0!==a.activePerks[t]&&(!!a.activePerks[t].active&&!!n.isToday(a.activePerks[t].activatedOn)))}static activatePerk(e,t){const a=s.getInstance();if(!f.isPartnerActive(e))return void n.showToast(a.T("partner-perk-not-active-header","Perk can not be activated"),a.T("partner-perk-not-active-content","This partner is not active."));if(f.isMaxPerksActive())return void n.showToast(a.T("partner-perk-max-perks-header","Maximum partner perks active"),a.T("partner-perk-max-perks-content","You are only allowed to activate %s partner perks at a time.",[o.maxConcurrentPartnerPerks]));let i=S.getActiveTags(),r=p.partnersData[e].perks[t],l=r.tags;if(i.length<=0||l.length>0&&!l.some(e=>i.includes(e)))return void n.showToast(a.T("partner-perk-tags-header","Perk can not be activated"),a.T("partner-perk-tags-content","You don't have any class with matching tags active."));const d=p.getActivePartners().map(a=>(a.id===e&&(a.activePerks[t]={active:!0,activatedOn:n.getNowUnix()}),a));p.setActivePartners(d),x.perkActivated(r),u.getInstance().multiplierChanged(),_.inAppReload()}static deactivatePerk(e,t){const a=s.getInstance();if(!f.isPerkActive(e,t))return void n.showToast(a.T("partner-perk-not-active-header","Perk can not be deactivated"),a.T("partner-perk-not-active-content","It's currently not possible to deactivate this perk."));const i=p.getActivePartners().map(a=>(a.id===e&&delete a.activePerks[t],a)),o=p.partnersData[e].perks[t];p.setActivePartners(i),x.perkDeactivated(o),u.getInstance().multiplierChanged(),_.inAppReload()}}class k{static get expectedParameters(){return{getView:[]}}static getView(){const e=p.getActiveMajor(),t=p.majorsData[e],a=I.mandatoryClasses(e),i=S.getActiveClasses(),r=S.getFinishedClasses(),l=p.getFinishedMajors(),d=s.getInstance();let c="",u="",m="",g="",h=l.length,v=0,b=0,f=0,y=0,w=0;for(const e of l){let t=p.majorsData[e];g+=C.getCardHtml(t)}for(let e of a){let t=S.getAssignedClass(e),a=p.classesData[e];c+=P.getCardHtml(a,t,!0,"col-lg-3 col-sm-3 pb-5")}for(let e of i){let t=p.classesData[e.id],s=a.some(t=>t==e.id);u+=P.getCardHtml(t,e,s,"col-lg-3 col-sm-3 pb-5")}for(let e of r){let t=p.classesData[e.id],s=a.some(t=>t==e.id);m+=P.getCardHtml(t,e,s,"col-lg-3 col-sm-3 pb-5")}for(let e of r){let t=p.classesData[e.id],s=a.some(t=>t==e.id);switch(t.tier){case"beginner":v+=1;break;case"intermediate":b+=1;break;case"advanced":f+=1;break;case"master":y+=1}s&&(w+=1)}return`<div class="container pb-3">\n <h4 class="py-3 text-success">${d.T("progress-active-major","Current major")}</h4>\n <div class="container">\n <div class="row">\n <div class="col-lg-3 col-sm-3 pb-5">\n <div class="img-container-overview">\n <img src="${t.image}" class="img-fluid img-rounded img-thumbnail">\n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=major&majorId="+e)})}" class="btn btn-dark text-white"><i class="fas fa-eye"></i></a>`])}\n </div>\n </div>\n <div class="col-lg-9 col-sm-9">\n <h4 class="text-warning">${t.name}</h4>\n <p>${t.description}${k.getCurrentCredits()}${k.getPunishmentsEndured()}</p>\n ${k.getGraduationButtonHtml()}\n <h5 class="text-warning">${d.T("progress-completed-classes","Class completion")}:</h5>\n <ul class="list-group list-group-flush">\n <li class="list-group-item"><b>${d.T("class-beginner-header","Beginner classes")}:</b> ${v}</li>\n <li class="list-group-item"><b>${d.T("class-intermediate-header","Intermediate classes")}:</b> ${b}</li>\n <li class="list-group-item"><b>${d.T("class-advanced-header","Advanced classes")}:</b> ${f}</li>\n <li class="list-group-item"><b>${d.T("class-master-header","Master classes")}:</b> ${y}</li>\n </ul>\n </div>\n </div>\n </div>\n <h4 class="py-3 text-success">${d.T("progress-active-classes","Active classes")} <span class="text-muted">[${i.length}/${o.maxConcurrentClasses}]</span></h4>\n <div class="container">\n <div class="row">\n ${u.length<=0?`<div>${d.T("progress-active-classes-no","No active classes available.")}</div>`:u}\n </div>\n </div>\n <h4 class="py-3 text-success">${d.T("progress-mandatory-classes","Mandatory classes")} <span class="text-muted">[${w}/${a.length}]</span></h4>\n <div class="container">\n <div class="row">\n ${c.length<=0?`<div>${d.T("progress-mandatory-classes-no","No mandatory classes available.")}</div>`:c}\n </div>\n </div>\n <h4 class="py-3 text-success">${d.T("progress-finished-majors","Finished majors")} <span class="text-muted">[${h}]</span></h4>\n <div class="container">\n <div class="row">\n ${g.length<=0?`<div>${d.T("progress-finished-majors-no","No majors finished yet.")}</div>`:g}\n </div>\n </div>\n <h4 class="py-3 text-success">${d.T("progress-finished-classes","Finished classes")} <span class="text-muted">[${r.length}]</span></h4>\n <div class="container">\n <div class="row">\n ${m.length<=0?`<div>${d.T("progress-finished-classes-no","No classes finished yet.")}</div>`:m}\n </div>\n </div>\n </div>\n `}static getGraduationButtonHtml(){const e=p.getActiveMajor();return I.isThesisAvailable(e)?`<button onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=major&majorId="+e)})}" type="button" class="btn btn-outline-warning mb-3" style="width: 100%;"><i class="fas fa-location-arrow"></i> ${s.getInstance().T("progress-thesis-button","Choose a thesis")}</button>`:""}static getPunishmentsEndured(){let e=p.getEnduredPunishments();return e>0?`<br><span class="text-muted"><b>${s.getInstance().T("progress-punishments-endured","Punishments endured")}:</b> ${e}</span>`:""}static getCurrentCredits(){const e=s.getInstance();return`<br><span class="text-muted"><b>${e.T("credits","Credits")}:</b> ${S.collectedCredits}/${p.requiredCreditsForGraduation}</span>${S.collectedCredits<=0?`<small class="pl-2 text-danger">${e.T("progress-credits-info","You only get credits for finishing a class by attending the exam.")}</small>`:""}`}}class y{static get expectedParameters(){return{getView:[]}}static getView(){const e=p.punishmentsData,t=s.getInstance();let a="",i="",n="";for(const t in e){let s=e[t];"light"===s.tier?a+=y.getCardHtml(s):"hard"===s.tier?i+=y.getCardHtml(s):"hardcore"===s.tier&&(n+=y.getCardHtml(s))}return`<div class="container-fluid pt-3">\n <div class="row">\n <div id="action-navigation" class="d-none d-lg-block col-lg-2 col-md-4 col-sm-12">\n <nav class="nav flex-column">\n <ul class="list-group pt-3">\n <li class="list-group-item active" aria-expanded="true">${t.T("tier-navigation","Navigation")}</li>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="0"><i class="fas fa-chevron-circle-right"></i> ${t.T("punishment-tier-light","Light")}</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="1"><i class="fas fa-chevron-circle-right"></i> ${t.T("punishment-tier-hard","Hard")}</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="2"><i class="fas fa-chevron-circle-right"></i> ${t.T("punishment-tier-hardcore","Hardcore")}</a>\n </ul>\n </nav>\n </div>\n <div class="col">\n <div id="customCarousel" class="carousel slide" data-interval="false" data-ride="carousel">\n <ol class="carousel-indicators top-carousel d-lg-none">\n <li data-target="#customCarousel" data-slide-to="0" class="active"></li>\n <li data-target="#customCarousel" data-slide-to="1"></li>\n <li data-target="#customCarousel" data-slide-to="2"></li>\n </ol>\n <div class="carousel-inner">\n <div class="carousel-item active">\n <h3>${t.T("punishment-light-header","Light punishments")}</h3>\n ${y.getRollPunishmentButton()}${y.getRollPunishmentButton("light")}<br>\n <div class="row carouselCardPadding">\n ${a}\n </div>\n </div>\n <div class="carousel-item">\n <h3>${t.T("punishment-hard-header","Hard punishments")}</h3>\n <h6 class="text-muted">${t.T("punishment-hard-requirement","You need at least %s credits to unlock hard punishments.",[p.requiredCreditsToUnlockHardPunishments])}</h6>\n ${y.getRollPunishmentButton("hard")}\n <div class="row carouselCardPadding">\n ${i}\n </div>\n </div>\n <div class="carousel-item">\n <h3>${t.T("punishment-hardcore-header","Hardcore punishments")}</h3>\n <h6 class="text-muted">${t.T("punishment-hardcore-requirement","You need at least %s credits and enable hardcore punishments in the settings to unlock them.",[p.requiredCreditsToUnlockHardPunishments])}</h6>\n ${y.getRollPunishmentButton("hardcore")}\n <div class="row carouselCardPadding">\n ${n}\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>`}static getCardHtml(e){const t=T.isPunishmentLocked(e.id),a=T.isPunishmentActive(e.id),i=void 0!==x.getActiveTaskForObject(e.id,"punishment"),o=T.wasFinishedBefore(e.id),r=T.isBlocked(e.id),l=s.getInstance();let d='<i class="fas fa-info-circle"></i>';return i&&(d='<i class="fas fa-play-circle"></i>'),`<div class="min-card-container col-lg-2 col-md-4 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <img src="${e.image}" class="card-img-top${t?" img-locked":""}${a?" img-active":""}">\n ${n.getButtonsContainer([a||o?`<a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=punishment&punishmentId="+e.id)})}" class="btn btn-dark text-white">${d}</a>`:"",y.getBlockButton(e)])}\n ${a?n.getBackdropContainer(l.T("punishment-status-active","Active")+' <i class="fas fa-heart"></i>',"success"):""}\n ${t?n.getBackdropContainer(l.T("punishment-status-locked","Locked")+' <i class="fas fa-lock"></i>',"danger"):""}\n ${r?n.getBackdropContainer(l.T("punishment-status-blocked","Blocked")+' <i class="fas fa-lock"></i>',"warning"):""}\n </div>\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">${e.name}</h5>\n <div class="card-text container-flex">${y.getPunishmentTasksHtml(e)}</div>\n </div>\n </div>\n </div>`}static getPunishmentTasksHtml(e){return""}static getRollPunishmentButton(e){if(T.isPunishmentTierAvailable(e)){const t=void 0===e?void 0:""+e,a=s.getInstance();return`<button onClick="${wi.register(()=>{T.getRandomPunishmentAction(t)})}" type="button" class="btn btn-outline-info mr-2 mb-2" style="margin-top: -8px;"><i class="fas fa-random"></i> ${a.T("punishment-roll-button","Roll %s punishment",[void 0===e?"":a.T("punishment-tier-"+e,e)])}</button>`}return""}static getBlockButton(e){if(!T.isPunishmentTierAvailable(e.tier))return"";if(!T.wasFinishedBefore(e.id)&&!T.isPunishmentActive(e.id)&&!n.getSetting("enableEasyMode"))return"";const t=T.isBlocked(e.id);return t&&!n.getSetting("enableEasyMode")?"":t?`<button onClick="${wi.register(()=>{T.unblockPunishment(e.id)})}" type="button" class="btn btn-dark text-success"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("punishment-card-unblock-button","Unblock")}</button>`:`<button onClick="${wi.register(()=>{T.blockPunishment(e.id,e.tier)})}" type="button" class="btn btn-dark text-warning"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("punishment-card-block-button","Block")}</button>`}}class w{static get expectedParameters(){return{getView:["punishmentId"]}}static getView(e){const t=e.punishmentId,a=T.isPunishmentLocked(t),i=T.isPunishmentActive(t),n=x.getActiveTaskForObject(t,"punishment"),o=s.getInstance();let r=p.punishmentsData[t],l=w.getPunishmentTier(r.tier,o.T("punishment-tier-"+r.tier,r.tier));return`<div class="container-fluid">\n <h2 class="text-center py-3">${r.name}</h2>\n <div class="container">\n <div class="row">\n <div class="col-lg-6 col-sm-6">\n <div class="img-container-overview">\n <a onClick="${wi.register(()=>{_.navigateBack()})}">\n <img src="${r.image}" class="img-fluid img-rounded img-thumbnail">\n </a>\n ${i?`<div class="alert-success text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${o.T("punishment-status-active","Active")}</b></div>`:""}\n ${a?`<div class="alert-danger text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${o.T("punishment-status-locked","Locked")}</b></div>`:""}\n </div>\n </div>\n <div class="col-lg-6 col-sm-6">\n <p><b>${o.T("punishment-tier-text","Tier")}:</b> ${l}</p>\n <div>\n ${void 0!==n?x.getTaskProgressHtml(n):w.getPunishmentTasksHtml(r)}\n </div>\n <div class="card-body">\n ${w.getBlockButton(r)}\n </div> \n </div>\n </div>\n </div>\n </div>\n `}static getBlockButton(e){if(!T.isPunishmentTierAvailable(e.tier))return"";if(!T.wasFinishedBefore(e.id)&&!T.isPunishmentActive(e.id)&&!n.getSetting("enableEasyMode"))return"";const t=T.isBlocked(e.id);return t&&!n.getSetting("enableEasyMode")?"":t?`<button onClick="${wi.register(()=>{T.unblockPunishment(e.id)})}" type="button" class="btn btn-outline-success mr-2"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("punishment-detail-unblock-button","Unblock punishment")}</button>`:`<button onClick="${wi.register(()=>{T.blockPunishment(e.id,e.tier)})}" type="button" class="btn btn-outline-warning mr-2"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("punishment-detail-block-button","Block punishment")}</button>`}static getPunishmentTasksHtml(e){let t="";for(let a in e.tasks)e.tasks[a].isExam||(t+=` <li class="list-group-item text-info">\n ${e.tasks[a].task}${w.getPunishmentButton(e.id,a)}${w.getRerollButton(e.id,e.tier)}\n </li>`);return`<ul class="list-group list-group-flush">${t}</ul>`}static getPunishmentTier(e,t){let a="";return"light"===e?a="text-success":"hard"===e?a="text-warning":"hardcore"===e&&(a="text-danger"),`<span class="${a}">${t}</span>`}static getPunishmentButton(e,t){return T.isPunishmentActive(e)?`<br><button onClick="${wi.register(()=>{T.startPunishment(e,t)})}" type="button" class="btn btn-outline-info mt-1" style="width: 100%;"><i class="fas fa-play-circle"></i> ${s.getInstance().T("punishment-detail-start-button","Start punishment")}</button>`:""}static getRerollButton(e,t){return T.isPunishmentActive(e)?`<br><button onClick="${wi.register(()=>{T.rerollPunishment(e,t)})}" type="button" class="btn btn-outline-warning mt-1" style="width: 100%;"><i class="fas fa-random"></i> ${s.getInstance().T("punishment-detail-reroll-button","Reroll punishment")}</button>`:""}}class T{static unblockPunishment(e){let t=p.getBlockedPunishments().filter(t=>t!==e);p.setBlockedPunishments(t),_.getInstance().inAppNavigation("?page=punishments")}static blockPunishment(e,t){let a=p.getBlockedPunishments();a.includes(e)||(a.push(e),p.setBlockedPunishments(a)),T.isPunishmentActive(e)?(T.removeActivePunishment(e),T.getRandomPunishmentAction(t)):_.getInstance().inAppNavigation("?page=punishments")}static isPunishmentActive(e){return 0!==p.getActivePunishments().filter(t=>t===e).length}static startPunishment(e,t){if(!T.isPunishmentActive(e))return!1;x.startTask(p.punishmentsData[e],"tasks",t)}static rerollPunishment(e,t){if(!T.isPunishmentActive(e))return!1;T.finishPunishment(e);const a=s.getInstance();let i=!1;if(!n.getSetting("enableEasyMode")){let e=p.getAssignedClasses().filter(e=>"active"==e.status&&e.attendances>0);if(e.length<=0&&(e=p.getAssignedClasses().filter(e=>"finished"==e.status&&e.attendances>0)),e.length>0){const t=e[Math.floor(Math.random()*e.length)],s=p.classesData[t.id];"active"==t.status?(i=!0,S.addClassAttendance(t.id,-1),n.scheduleToast(a.T("punishment-rerolled-knowledge-point-header","Punishment rerolled"),a.T("punishment-rerolled-knowledge-point-content","You rerolled your punishment, you lost a knowledge point for %s.",[s.name]),"warning")):"finished"==t.status&&(i=!0,S.updateAssignedClassStatus(t.id,"active"),n.scheduleToast(a.T("punishment-rerolled-exam-header","Punishment rerolled"),a.T("punishment-rerolled-exam-content","You rerolled your punishment, you lost your exam attendance for %s.",[s.name]),"warning"))}}i||n.scheduleToast(a.T("punishment-rerolled-unknown-header","Punishment rerolled"),a.T("punishment-rerolled-unknown-content","You rerolled your punishment, this will have consequences."),"warning"),T.getRandomPunishmentAction(t)}static wasFinishedBefore(e){return p.getFinishedPunishments().includes(e)}static isBlocked(e){return p.getBlockedPunishments().includes(e)}static finishPunishment(e){let t=p.getFinishedPunishments();t.includes(e)||(t.push(e),p.setFinishedPunishments(t)),T.removeActivePunishment(e)}static removeActivePunishment(e){let t=p.getActivePunishments().filter(t=>t!==e);p.setActivePunishments(t)}static isPunishmentLocked(e){const t=p.punishmentsData[e];return!T.isPunishmentTierAvailable(t.tier)}static isPunishmentTierAvailable(e){return!("hardcore"===e&&!n.getSetting("enableHardcorePunishments"))&&("hard"!==e&&"hardcore"!==e||!(S.collectedCredits<p.requiredCreditsToUnlockHardPunishments))}static getRandomPunishmentAction(e){let t=T.getRandomPunishment(e);t&&_.getInstance().inAppNavigation("?page=punishment&punishmentId="+t)}static getRandomPunishment(e){const t=s.getInstance();if(void 0!==e&&!T.isPunishmentTierAvailable(e))return void n.showToast(t.T("punishment-unavailable-header","Punishment tier not available"),t.T("punishment-unavailable-content","You are not allowed to use these punishments yet."));let a=p.getActivePunishments();if(a.length>=o.maxConcurrentPunishments)return void n.showToast(t.T("punishment-max-header","No additional punishment allowed"),t.T("punishment-max-content","You are allowed to have %s active punishments.",[o.maxConcurrentPunishments]));const i=p.getBlockedPunishments();let r=p.punishmentsData,l=[];for(const t in r){const a=r[t];T.isPunishmentActive(t)||(void 0!==e&&a.tier!==e||T.isPunishmentTierAvailable(a.tier)&&(i.includes(t)||l.push(t)))}if(l.length<=0)return void n.showToast(t.T("punishment-null-header","No punishments available"),t.T("punishment-null-content","Unable to determine any punishment that you can do."));const d=l[Math.floor(Math.random()*l.length)];return a.push(d),p.setActivePunishments(a),d}}class x{static getActiveModifiers(e){let t=o.baseTaskMultiplier,a=p.getAssignedClubs(),s=p.getActivePartners(),i=0,n=0,r=0,l=0,c=0;for(let t in a){let s=a[t],o=s.id,u=s.activePerks,m=p.clubsData[o].perks;for(let t in u){let a=m[t];h.isPerkActive(o,t)&&(void 0===e||a.tags.length>0&&!a.tags.some(t=>e.includes(t))||("difficulty"===a.modType?i+=Number.isNaN(Number.isInteger(a.modVal))?d.getPercentageFromText(a.job):a.modVal/100:"punishment"===a.modType&&(l+=Number.isNaN(Number.isInteger(a.modVal))?1:a.modVal),"difficulty"===a.perkType?i-=Number.isNaN(Number.isInteger(a.perkVal))?d.getPercentageFromText(a.perk):a.perkVal/-100:"skip"===a.perkType?n+=Number.isNaN(Number.isInteger(a.perkVal))?d.getClassSkipsFromText(a.perk):a.perkVal:"attendance"===a.perkType?r+=Number.isNaN(Number.isInteger(a.perkVal))?1:a.perkVal:"resetRoulette"===a.perkType&&(c+=Number.isNaN(Number.isInteger(a.perkVal))?1:a.perkVal)))}}for(let t in s){let a=s[t],o=a.id,u=a.activePerks,m=p.partnersData[o].perks;for(let t in u){let a=m[t];f.isPerkActive(o,t)&&(void 0===e||a.tags.length>0&&!a.tags.some(t=>e.includes(t))||("difficulty"===a.modType?i+=Number.isNaN(Number.isInteger(a.modVal))?d.getPercentageFromText(a.job):a.modVal/100:"punishment"===a.modType&&(l+=Number.isNaN(Number.isInteger(a.modVal))?1:a.modVal),"difficulty"===a.perkType?i-=Number.isNaN(Number.isInteger(a.perkVal))?d.getPercentageFromText(a.perk):a.perkVal/-100:"skip"===a.perkType?n+=Number.isNaN(Number.isInteger(a.perkVal))?d.getClassSkipsFromText(a.perk):a.perkVal:"attendance"===a.perkType?r+=Number.isNaN(Number.isInteger(a.perkVal))?1:a.perkVal:"resetRoulette"===a.perkType&&(c+=Number.isNaN(Number.isInteger(a.perkVal))?1:a.perkVal)))}}let u=t+i;return u<.5&&(u=.5),{multiplier:u,classSkips:n,bonusAttendance:r,punishments:l,resetRoulette:c}}static perkActivated(e){if(!e)return;const t=s.getInstance();if("punishment"==e.modType){let a=Number.parseInt(e.modVal);Number.isNaN(a)&&(a=1);for(let e=0;e<a;e++)T.getRandomPunishment(void 0),n.scheduleToast(t.T("task-perk-activated-header","Perk punishment drawn"),t.T("task-perk-activated-content","A punishment was automatically drawn."),"warning")}if("resetRoulette"==e.perkType&&(p.setRoulette({}),n.scheduleToast(t.T("task-roulette-reset-header","Roulette reset perk"),t.T("task-roulette-reset-content","The roulette was reset, you may now try again."),"info")),"skip"==e.perkType){let a=Number.isInteger(e.perkVal)?e.perkVal:1;a=Number.isNaN(a)?1:a,S.addClassSkipToken(a),n.scheduleToast(t.T("task-skip-perk-header","Class skip perk"),1==a?t.T("task-skip-perk-single-content","Class skip added."):t.T("task-skip-perk-multi-content","%s class skips added.",[a]),"info")}}static perkDeactivated(e){if(!e)return;const t=s.getInstance();if("resetRoulette"==e.perkType&&(p.setRoulette({id:void 0,rolledOn:n.getNowUnix()}),n.scheduleToast(t.T("task-roulette-undone-reset-header","Undone: Roulette reset perk"),t.T("task-roulette-undone-reset-content","The roulette reset was undone, you may now try tomorrow again."),"info")),"skip"==e.perkType){let a=Number.isInteger(e.perkVal)?e.perkVal:1;a=Number.isNaN(a)?1:a,a*=-1,S.addClassSkipToken(a),n.scheduleToast(t.T("task-skip-perk-removed-header","Class skip perk removed"),-1==a?t.T("task-skip-perk-removed-single-content","Class skip removed."):t.T("task-skip-perk-removed-multi-content","%s class skips removed.",[a]),"info")}}static getMultiplier(e){return x.getActiveModifiers(e).multiplier}static startTask(e,t,a){const i=s.getInstance();let o=$.extend(!0,{},e),r=o[t][a],l=r.task,d=o[t][a].tags,c=0,u=0,m=0,g=0;if(!l)return!1;let h=x.getActiveModifiers(d),v={id:n.getRandomString(),object:o,objectTaskListReference:t,objectTaskReference:a,multiplier:h.multiplier,bonusAttendance:h.bonusAttendance,classSkips:h.classSkips,punishments:h.punishments,resetRoulette:h.resetRoulette,isExam:r.isExam,pauseBonus:0,timers:[]};delete v.object.image,o&&o.disableMultiplier&&1==o.disableMultiplier&&(v.multiplier=1),o&&o.disableBonusAttendance&&1==o.disableBonusAttendance&&(v.bonusAttendance=0),o&&o.disableClassSkips&&1==o.disableClassSkips&&(v.classSkips=0),o&&o.disablePunishments&&1==o.disablePunishments&&(v.punishments=0),o&&o.disableResetRoulette&&1==o.disableResetRoulette&&(v.resetRoulette=0);let b=n.getNow().hour(23).minute(0).second(0),f=n.getNow();f>b&&(f=f.add(1,"day")),f.unix()<g&&(f=moment.unix(g));const k=e=>("minutes"==e.unit||"seconds"==e.unit)&&(void 0===e.spawnTimer||!1!==e.spawnTimer),y=e=>"punishments"==e.unit&&(null!=e.punTier&&!!Number.isInteger(e.value)),w=e=>void 0===e.provideCounter||!1!==e.provideCounter;for(let e of Object.keys(r.parameters)){let t=r.parameters[e];if(k(t)){let e=0;if("minutes"==t.unit?e=60*t.value:"seconds"==t.unit&&(e=t.value),t.applyMultiplier&&(e=Math.round(e*v.multiplier)),e>0){let a=n.getNowUnix(),s=a+e;m+=e,g<s&&(g=s);let i={id:n.getRandomString(),requiredTime:e,startAt:a,expectedFinishAt:s,timerInfo:t.timerInfo||"",pausedAt:void 0,totalPauseTime:0};t.hiddenTask&&(i.hiddenTask=!0),t.punishTime&&t.punishTimeMinutes&&Number.isInteger(t.punishTimeMinutes)&&(i.punishTime=!0,i.punishSeconds=60*Number.parseInt(t.punishTimeMinutes)),(t.hiddenTask||void 0!==t.startTimerAutomatically&&!0!==t.startTimerAutomatically)&&(i.pausedAt=a,v.pauseBonus+=c),c=e,u+=1,v.timers.push(i)}}else if(y(t)){""==t.punTier&&(t.punTier=void 0);for(let e=0;e<Number.parseInt(t.value);e++)T.getRandomPunishment(t.punTier),n.scheduleToast(i.T("task-punishment-drawn-header","Task punishment drawn"),i.T("task-punishment-drawn-content","As defined by this task a %s punishment was automatically drawn.",[t.punTier?t.punTier:i.T("task-random-punishment","random")]),"info")}else if(w(t)){let e=n.getNowUnix(),a={id:n.getRandomString(),requiredTime:0,startAt:e,expectedFinishAt:e,mustBeFinishedBy:f.endOf("day").unix(),pausedAt:void 0,totalPauseTime:0,timerInfo:t.timerInfo||"",counter:t.value,unit:t.unit};t.hiddenTask&&(a.hiddenTask=!0),t.applyMultiplier&&(a.counter=Math.round(a.counter*v.multiplier)),v.timers.push(a)}}if(u>1&&c>0&&(v.pauseBonus+=c),v.timers.length<=0){let e=n.getNowUnix(),t={id:n.getRandomString(),requiredTime:0,startAt:e,expectedFinishAt:e,mustBeFinishedBy:n.getNow().add(1,"day").startOf("day").unix(),pausedAt:void 0,totalPauseTime:0,timerInfo:""};v.timers.push(t)}let C=p.getActiveTasks();C.push(v),p.setActiveTasks(C),_.inAppReload()}static pauseTask(e,t){let a=p.getActiveTasks();a=a.map(a=>{if(a.id===e){let e=n.getNowUnix();for(let s in a.timers){let i=a.timers[s];i.id===t&&(i.expectedFinishAt<=e||void 0===i.mustBeFinishedBy&&(i.pausedAt=e))}}return a}),p.setActiveTasks(a),_.inAppReload()}static resumeTask(e,t){let a=p.getActiveTasks();a=a.map(a=>{if(a.id===e){let e=n.getNowUnix();for(let s in a.timers){let i=a.timers[s];if(i.id!==t)continue;if(void 0===i.pausedAt)continue;let n=i.pausedAt,o=i.startAt;i.totalPauseTime+=e-n;let r=n-o;i.startAt=e,i.requiredTime=i.requiredTime-r,i.expectedFinishAt=i.startAt+i.requiredTime,i.pausedAt=void 0}}return a}),p.setActiveTasks(a),_.inAppReload()}static finishManualTask(e){let t=p.getActiveTasks();t=t.map(t=>{if(t.id===e)for(let e in t.timers){let a=t.timers[e];void 0!==a.mustBeFinishedBy&&(a.mustBeFinishedBy=void 0)}return t}),p.setActiveTasks(t),x.checkForFinishedTasks(),_.inAppReload()}static punishTask(e){let t=p.getActiveTasks();t=t.map(t=>{if(t.id===e)for(let e in t.timers){let a=t.timers[e],s=n.getNowUnix();a.expectedFinishAt<=s||a.punishTime&&(a.expectedFinishAt=a.expectedFinishAt+a.punishSeconds)}return t}),p.setActiveTasks(t),_.inAppReload()}static failTaskAction(e){const t=s.getInstance();n.requestUserConfirmation(t.T("task-fail-header","Fail task"),t.T("task-fail-content","Are you sure that you want to fail this task?"),()=>{x.failTask(e),_.inAppReload()})}static failTask(e){let t=p.getActiveTasks(),a=t.find(t=>t.id===e);if(!a)return!1;if(a.failed)return!1;a.failed=!0;let s=n.getNowUnix();for(let e in a.timers){let t=a.timers[e];t.expectedFinishAt<=s||(t.expectedFinishAt=n.getNowUnix(),t.pausedAt=void 0,t.mustBeFinishedBy=void 0)}return p.setActiveTasks(t),a}static failTaskPunishment(e){const t=s.getInstance();let a=0;"custom"==e.object.type&&(a=o.failCustomTask,n.scheduleToast(t.T("task-punishment-header","Punishment"),`${t.T("task-punishment-custom-task","You failed a custom Task!")} ${o.failCustomTask>1?t.T("task-punishments-drawn","%s punishments were drawn!",[o.failCustomTask]):t.T("task-punishment-drawn","A punishment was drawn!")}`,"danger")),"class"==e.object.type&&("tasks"==e.objectTaskListReference&&(a=o.failDailyPunishment,n.scheduleToast(t.T("task-punishment-header","Punishment"),`${t.T("task-punishment-task","You failed a task!")} ${o.failDailyPunishment>1?t.T("task-punishments-drawn","%s punishments were drawn!",[o.failDailyPunishment]):t.T("task-punishment-drawn","A punishment was drawn!")}`,"danger")),e.isExam&&(a=o.failExamPunishment,n.scheduleToast(t.T("task-punishment-header","Punishment"),`${t.T("task-punishment-exam","You failed an exam!")} ${o.failExamPunishment>1?t.T("task-punishments-drawn","%s punishments were drawn!",[o.failExamPunishment]):t.T("task-punishment-drawn","A punishment was drawn!")}`,"danger"))),"major"==e.object.type&&e.isExam&&(a=o.failThesisPunishment,n.scheduleToast(t.T("task-punishment-header","Punishment"),`${t.T("task-punishment-thesis","You failed a thesis!")} ${o.failThesisPunishment>1?t.T("task-punishments-drawn","%s punishments were drawn!",[o.failThesisPunishment]):t.T("task-punishment-drawn","A punishment was drawn!")}`,"danger")),"punishment"==e.object.type&&T.rerollPunishment(e.object.id,e.object.tier);for(let e=0;e<a;e++)T.getRandomPunishment(void 0)}static isAnyTaskActive(){let e=p.getActiveTasks();return e&&e.length>0}static checkForFinishedTasks(){if(!x.isAnyTaskActive())return!1;const e=s.getInstance();let t=p.getActiveTasks(),a=t.filter(e=>{if(e.failed)return!1;let t=n.getNowUnix();for(let a in e.timers){let s=e.timers[a];if(!(s.counter&&s.counter<=0)&&!(s.expectedFinishAt>t)){if(void 0!==s.mustBeFinishedBy&&s.mustBeFinishedBy<=t)return!0;if(void 0!==s.pausedAt&&t-e.pauseBonus-s.pausedAt+s.totalPauseTime>60*o.maxTaskPause)return!0}}return!1});for(let s of a)"class"!=s.object.type&&"major"!=s.object.type&&"custom"!=s.object.type||(s=x.failTask(s.id),t=t.filter(e=>e.id!=s.id),t.push(s),n.scheduleToast(e.T("task-failed-header","Task failed"),e.T("task-failed-content","You failed the task %s!",[s.object.name]),"danger"));a.length>0&&p.setActiveTasks(t);let i=t.filter(e=>{if(e.failed)return!0;let t=n.getNowUnix();for(let a in e.timers){let s=e.timers[a];if(s.counter&&s.counter>0)return!1;if(s.expectedFinishAt>t)return!1;if(void 0!==s.mustBeFinishedBy)return!1;if(void 0!==s.pausedAt)return!1}return!0}),r=!1,l=!1;for(let a of i){if(t=t.filter(e=>!(e.id===a.id)),"custom"==a.object.type){if(r=!0,a.failed){x.failTaskPunishment(a);continue}n.scheduleToast(e.T("task-custom-task-finished-header","Custom task finished"),e.T("task-custom-task-finished-content","You finished a custom task!"),"success")}if("class"==a.object.type){let t=n.getNow().add(-1,"day").startOf("day").unix();for(let e in a.timers){let s=a.timers[e];s.startAt>t&&(t=s.startAt)}if(S.setLastAttended(a.object.id,t),r=!0,a.failed){x.failTaskPunishment(a);continue}S.addClassAttendance(a.object.id,1+a.bonusAttendance),a.isExam?(S.updateAssignedClassStatus(a.object.id,"finished"),n.scheduleToast(e.T("task-class-finished-header","Class finished"),e.T("task-class-finished-content","Congratulations you finished class %s!",[a.object.name]),"success")):n.scheduleToast(e.T("task-finished-header","Task finished"),e.T("task-finished-content","You finished a task for %s!",[a.object.name]),"success")}if("major"==a.object.type){if(a.failed){r=!0,x.failTaskPunishment(a);continue}r=!1,n.scheduleToast(e.T("task-major-finished-header","Major finished"),e.T("task-major-finished-content","You graduated %s!",[a.object.name]),"success"),l=a.object.id;let t=p.getFinishedMajors();t.push(l),p.setFinishedMajors(t),p.setActiveMajor()}if("punishment"==a.object.type){if(r=!0,a.failed){x.failTaskPunishment(a);continue}T.finishPunishment(a.object.id),n.scheduleToast(e.T("task-punishment-finished-header","Punishment finished"),e.T("task-punishment-finished-content","You finished the punishment %s!",[a.object.name]),"success")}}i.length>0&&p.setActiveTasks(t),l&&_.navigate("?page=graduated&majorId="+l),r&&_.inAppReload()}static getActiveTaskForObject(e,t){if(x.isAnyTaskActive())return p.getActiveTasks().find(a=>a.object.id==e&&a.object.type==t)}static isTaskActiveForObject(e,t){return void 0!==x.getActiveTaskForObject(e,t)}static manualTaskCountdown(e,t){let a=1;t.counter>20&&(a=10);let s=p.getActiveTasks();s=s.map(s=>{if(s.id===e)for(let e in s.timers){let i=s.timers[e];i.id===t.id&&(i.counter-=a,i.counter>0||(i.mustBeFinishedBy=void 0))}return s}),p.setActiveTasks(s),$("#manualTaskCounter-"+t.id).text(""+t.counter),t.counter<=0&&(x.checkForFinishedTasks(),_.inAppReload())}static getTaskProgressHtml(e){if(!e||void 0===e)return"";let t=e.object.tasks[e.objectTaskReference],a="",i=n.getNowUnix(),o=!1;const r=s.getInstance();for(let t in e.timers){let s=e.timers[t];if(!(s.expectedFinishAt<=i&&void 0===s.pausedAt&&void 0===s.mustBeFinishedBy)&&(!o||!s.hiddenTask))if(void 0!==s.mustBeFinishedBy){o=!0;let t="";t=s.counter>0?`<button onClick="${wi.register(()=>{x.manualTaskCountdown(e.id,s)})}" type="button" class="btn btn-secondary text-warning"><i class="fas fa-exclamation-circle"></i> ${r.T("task-manual-counter-button","Do %s times",[`<span id="manualTaskCounter-${s.id}">${s.counter}</span>`])}</button>`:`<button onClick="${wi.register(()=>{x.finishManualTask(e.id)})}" type="button" class="btn btn-secondary text-success"><i class="fas fa-check-circle"></i> ${r.T("task-manual-finish-button","Complete")}</button>`,a+=` <p class="card-text">${s.timerInfo.length>0?`<b>${s.timerInfo}:</b> `:""}${r.T("task-finish-by-text","This task must have been finished %s.",[n.getNow().to(moment.unix(s.mustBeFinishedBy))])}</p>\n <div class="btn-group w-100" role="group">\n ${t}\n <button onClick="${wi.register(()=>{x.failTaskAction(e.id)})}" type="button" class="btn btn-secondary text-danger"><i class="fas fa-exclamation-circle"></i> ${r.T("task-fail-button","Fail")}</button> \n </div>`}else{o=!0;let t=setInterval(()=>{x.updateProgressTask(s.id,s.startAt,s.expectedFinishAt,s.pausedAt,t,s.totalPauseTime,e.pauseBonus)},1e3);u.getInstance().addTaskInterval(s.id,t),a+=` <p class="card-text mb-1">${s.timerInfo.length>0?`<b>${s.timerInfo}:</b>`:`<b>${r.T("task-time-left-info","Time left")}:</b>`} <span id="taskTime-${s.id}">00:00:00</span></p>\n <div class="progress mb-3">\n <div id="taskProgress-${s.id}" class="progress-bar progress-bar bg-info" role="progressbar" style="width: 0%"></div>\n </div>\n <div class="btn-group w-100 pb-3" role="group">\n ${void 0!==s.pausedAt?`<button onClick="${wi.register(()=>{x.resumeTask(e.id,s.id)})}" type="button" class="btn btn-secondary text-success"><i class="fas fa-play-circle"></i> ${r.T("task-resume-button","Resume")}</button>`:`<button onClick="${wi.register(()=>{x.pauseTask(e.id,s.id)})}" type="button" class="btn btn-secondary text-warning"><i class="fas fa-pause-circle"></i> ${r.T("task-pause-button","Pause")}</button>`}\n\n ${s.punishTime?`<button onClick="${wi.register(()=>{x.punishTask(e.id)})}" type="button" class="btn btn-secondary text-danger">${r.T("task-punish-button","Punish")}</button>`:""}\n <button onClick="${wi.register(()=>{x.failTaskAction(e.id)})}" type="button" class="btn btn-secondary text-danger"><i class="fas fa-exclamation-circle"></i> ${r.T("task-fail-button","Fail")}</button> \n </div>`}}return`<div class="card border-info mb-3">\n <div class="card-header">\n ${void 0!==e.pausedAt?'<i class="fas fa-pause-circle"></i> '+r.T("task-status-paused","Task is paused"):'<i class="fas fa-play-circle"></i> '+r.T("task-status-active","Task in progress")}\n </div>\n <div class="card-body">\n <p class="card-text"><b>${r.T("task-active-task","Active task")}:</b> ${t.task}</p>\n ${a}\n </div>\n </div>`}static updateProgressTask(e,t,a,s,i,r,l){let c=$("#taskProgress-"+e),u=$("#taskTime-"+e);if(c.length<=0||u.length<=0)return clearInterval(i),!1;let p=n.getNowUnix(),m=!1,g=0;t=t,a=a,void 0!==s&&(m=!0,g=s-p-r+l+60*o.maxTaskPause,p=s);let h=Math.floor((p-t)/(a-t)*100);c.css("width",h+"%"),c.text(h+"%"),u.html(`${d.dateObjectToHumanTime(a-p)} ${m?'<i class="fas fa-pause-circle"></i> '+d.minutesToTime(Math.floor(g/60)):""}`),h>=100&&clearInterval(i),(h>=100||m&&g<=0)&&(x.checkForFinishedTasks(),_.inAppReload())}static checkForSkippedClasses(){if(n.getSetting("enableEasyMode"))return;const e=s.getInstance(),t=n.getNow().add(-1,"day").startOf("day").unix(),a=p.getAssignedClasses().filter(e=>"active"==e.status&&void 0!==e.lastAttended&&e.lastAttended<t);if(a.length<=0)return;let i=p.getActivePunishments().length,r=!1;for(let s of a){if(x.isTaskActiveForObject(s.id,"class"))continue;let a=s.lastAttended;const l=p.classesData[s.id];for(;a<t&&i<o.maxConcurrentPunishments;){a=moment.unix(a).add(1,"day").unix();const t=moment.unix(a).day();0!=t&&6!=t&&(l.days.includes(t)&&(r=!0,p.getClassSkipTokens()>0?p.setClassSkipTokens(p.getClassSkipTokens()-1):(i++,T.getRandomPunishment(),n.scheduleToast(e.T("task-class-missed-header","Class missed"),e.T("task-class-missed-content","You missed a class and a punishment was rolled."),"danger"))))}S.setLastAttended(s.id,t)}r&&_.reload()}}class C{static get expectedParameters(){return{getView:[]}}static getView(){const e=p.majorsData;let t="";for(const a in e){let s=e[a];t+=C.getCardHtml(s)}return`<div class="container-fluid">\n <div class="row pt-3">${t}</div>\n </div>`}static getCardHtml(e){const t=p.getActiveMajor()===e.id,a=I.isMajorFinished(e.id),i=a&&I.getGrade(e.id),o=I.isAnyMajorActive(),r=n.getRandomString(),l=x.getActiveTaskForObject(e.id,"major"),d=void 0!==l,c=s.getInstance();let u='<i class="fas fa-info-circle"></i>';d&&(u='<i class="fas fa-play-circle"></i>');let m="major";return a&&(m="graduated"),`<div class="min-card-container col-lg-3 col-md-6 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation(`?page=${m}&majorId=${e.id}`)})}">\n <img src="${e.image}" class="card-img-top${t?" img-active":""}${t||!o||a?"":" img-locked"}${a?" img-finished":""}">\n </a>\n ${t?n.getBackdropContainer(c.T("major-status-active","Active")+' <i class="fas fa-heart"></i>',"success"):""}\n ${a?n.getBackdropContainer(`${c.T("major-status-grade","Grade")} <b>${i}</b> <i class="fas fa-check-circle"></i>`,"info"):""} \n ${!o||t||a?"":n.getBackdropContainer(c.T("major-status-unavailable","Not available")+' <i class="fas fa-lock"></i>')}\n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("showExams-"+r)})}" class="btn btn-dark text-white">${u}</a>`,C.getJoinButton(e.id),C.getDropButton(e.id)])}\n <div id="showExams-${r}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n ${void 0!==l?x.getTaskProgressHtml(l):`\n <ul class="list-group list-group-flush">\n ${C.getThesisHtml(e)}\n </ul>`}\n </div>\n </div>\n </div>\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">${e.name} <small class="text-muted">${e.name2}</small></h5>\n <p class="card-text">${e.description}</p>\n </div>\n </div>\n </div>`}static getThesisHtml(e){let t="";for(let a in e.tasks)e.tasks[a].isExam&&(t+=`<li class="list-group-item text-muted">\n <b class="text-info"><i class="fas fa-chevron-right"></i></b> ${e.tasks[a].task}\n </li>`);return t}static getJoinButton(e){return I.isJoinButtonAvailable(e)?`<button onClick="${wi.register(()=>{I.joinMajor(e)})}" type="button" class="btn btn-dark text-success"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("major-card-join-button","Join")}</button>`:""}static getDropButton(e){return I.isDropButtonAvailable(e)?`<button onClick="${wi.register(()=>{I.dropMajor()})}" type="button" class="btn btn-dark text-danger"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("major-card-drop-button","Drop")}</button>`:""}}class M{static get expectedParameters(){return{getView:["majorId"]}}static getView(e){const t=e.majorId,a=I.isMajorActive(t),i=x.getActiveTaskForObject(t,"major"),n=s.getInstance();let o=p.majorsData[t];return`<div class="container-fluid">\n <h2 class="text-center py-3">${o.name}</h2>\n <div class="container">\n <div class="row">\n <div class="col-lg-6 col-sm-6">\n <div class="img-container-overview">\n <a onClick="${wi.register(()=>{_.navigateBack()})}">\n <img src="${o.image}" class="img-fluid img-rounded img-thumbnail">\n </a>\n ${a?`<div class="alert-success text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${n.T("major-status-active","Active")}</b></div>`:""}\n </div>\n </div>\n <div class="col-lg-6 col-sm-6">\n <p class="">\n <b>${n.T("major-detail-prerequisites","Prerequisites")}:</b> ${A.prerequisitesToString(o.prerequisites)}\n </p> \n <p>${o.description}</p>\n ${M.getThesisRequirements(o.id)}\n ${void 0!==i?x.getTaskProgressHtml(i):`\n <ul class="list-group list-group-flush">\n <li class="list-group-item text-danger"><b>${n.T("major-detail-thesis","Available thesis topics")}:</b></li>\n ${M.getThesisHtml(o)}\n </ul>`}\n <div class="card-body">\n ${M.getJoinButton(o.id)}\n ${M.getDropButton(o.id)}\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static getThesisHtml(e){let t="";for(let a in e.tasks)e.tasks[a].isExam&&(t+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${e.tasks[a].task}${M.getThesisButton(e.id,a)}</div>\n </div> \n </li>`);return t}static getThesisButton(e,t){return I.isThesisAvailable(e)?`<br><button onClick="${wi.register(()=>{I.startThesis(e,t)})}" type="button" class="btn btn-outline-danger mt-1" style="width: 100%;"><i class="fas fa-play-circle"></i> ${s.getInstance().T("major-detail-thesis-button","Start thesis")}</button>`:""}static getJoinButton(e){return I.isJoinButtonAvailable(e)?`<br><button onClick="${wi.register(()=>{I.joinMajor(e)})}" type="button" class="btn btn-outline-success mr-2"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("major-detail-join-button","Join major")}</button>`:""}static getDropButton(e){return I.isDropButtonAvailable(e)?`<br><button onClick="${wi.register(()=>{I.dropMajor()})}" type="button" class="btn btn-outline-danger mr-2"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("major-detail-drop-button","Drop major")}</button>`:""}static getThesisRequirements(e){const t=s.getInstance();let a=`<li class="list-group-item text-danger"><b>${t.T("major-detail-thesis-requirement-header","Thesis requirements")}:</b></li>`;return p.getActiveMajor()!=e&&(a+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${t.T("major-detail-thesis-requirement-join","You must join this major, before you can do the thesis.")}</div>\n </div> \n </li>`),S.collectedCredits<p.requiredCreditsForGraduation&&(a+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${t.T("major-detail-thesis-requirement-credits","You must collect at least %s credits.",[p.requiredCreditsForGraduation])}</div>\n </div> \n </li>`),p.getActivePunishments().length>0&&(a+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${t.T("major-detail-thesis-requirement-punishments","You must finish all punishments.")}</div>\n </div> \n </li>`),I.prerequisitesDone(e)||(a+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${t.T("major-detail-thesis-requirement-prerequisites","You must finish all prerequisites.")}</div>\n </div> \n </li>`),x.isTaskActiveForObject(e,"major")&&(a+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${t.T("major-detail-thesis-requirement-thesis","You can only attend one thesis at a time.")}</div>\n </div> \n </li>`),`<ul class="list-group list-group-flush pb-3">${a}</ul>`}}class I{static getGrade(e){let t=0,a=0;const s=I.mandatoryClasses(e);for(const e of s)t+=S.getAssignedClass(e).attendances,a+=o.maxAttendances[p.classesData[e].tier];return n.getGrade(t,a)}static isJoinButtonAvailable(e){return!I.isAnyMajorActive()&&!I.isMajorFinished(e)}static isDropButtonAvailable(e){return I.isMajorActive(e)}static isMajorActive(e){return p.getActiveMajor()===e}static isMajorFinished(e){return p.getFinishedMajors().includes(e)}static mandatoryClasses(e){const t=p.majorsData[e].prerequisites;let a=[];a=a.concat(t);for(let e of t)a=a.concat(S.getClassPrerequisitesDeep(e));return a=a.sort((e,t)=>e-t),[...new Set(a)]}static isAnyMajorActive(){return void 0!==p.getActiveMajor()}static joinMajor(e){p.setActiveMajor(e),_.getInstance().inAppNavigation("?page=progress")}static dropMajor(){const e=s.getInstance();n.requestUserConfirmation(e.T("major-drop-confirm-header","Drop major"),e.T("major-drop-confirm-content","Are you sure that you want to drop your current major?"),()=>{p.setActiveMajor();for(let e=0;e<o.majorDropPunishments;e++){T.getRandomPunishment()||(e=o.majorDropPunishments)}n.scheduleToast(e.T("major-dropped-header","Major dropped"),e.T("major-dropped-content","For dropping the major punishments where drawn."),"danger"),_.getInstance().inAppNavigation("?page=majors")})}static startThesis(e,t){if(!I.isThesisAvailable(e))return!1;x.startTask(p.majorsData[e],"tasks",t)}static isThesisAvailable(e){return p.getActiveMajor()==e&&(!(S.collectedCredits<p.requiredCreditsForGraduation)&&(!(p.getActivePunishments().length>0)&&(!!I.prerequisitesDone(e)&&!x.isTaskActiveForObject(e,"major"))))}static prerequisitesDone(e){const t=p.majorsData[e];if(!t)return!1;if(0===t.prerequisites.length)return!0;return!(t.prerequisites.filter(e=>{const t=S.getAssignedClass(e);return!t||"finished"!==t.status}).length>0)}}class P{static get expectedParameters(){return{getView:[]}}static getView(){const e=p.classesData,t=n.getSetting("hideActiveClasses"),a=n.getSetting("hideFinishedClasses"),i=p.getActiveMajor(),o=I.mandatoryClasses(i),r=s.getInstance();let l="",d="",c="",u="",m=!0,g=!0,h=!0,v="beginner",b=1,f=2,k=3,y=[];for(const s in e){let i=S.getAssignedClass(s);if(i){if(t&&"active"===i.status)continue;if(a&&"finished"===i.status)continue}let n=e[s];n.assigned=i,y.push(n)}y=y.sort((e,t)=>e.name.localeCompare(t.name));for(const e of y){const t=o.some(t=>t==e.id),a=P.getCardHtml(e,e.assigned,t);"beginner"===e.tier?l+=a:"intermediate"===e.tier?d+=a:"advanced"===e.tier?c+=a:"master"===e.tier&&(u+=a)}return m=l.length>0,g=d.length>0,h=c.length>0,m||(v="intermediate",g||(v="advanced",h||(v="master"))),h||(k-=1),g||(k-=1,f-=1),m||(k-=1,f-=1,b-=1),`<div class="container-fluid pt-3">\n <div class="row">\n <div id="action-navigation" class="d-none d-lg-block col-lg-2 col-md-4 col-sm-12">\n <nav class="nav flex-column">\n <ul class="list-group pt-3">\n <li class="list-group-item active" aria-expanded="true">${r.T("tier-navigation","Navigation")}</li>\n ${m?`<a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="0"><i class="fas fa-chevron-circle-right"></i> ${r.T("class-tier-beginner","Beginner")}</a>`:""}\n ${g?`<a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="${b}"><i class="fas fa-chevron-circle-right"></i> ${r.T("class-tier-intermediate","Intermediate")}</a>`:""}\n ${h?`<a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="${f}"><i class="fas fa-chevron-circle-right"></i> ${r.T("class-tier-advanced","Advanced")}</a>`:""}\n <a class="list-group-item list-group-item-action onClickLink" data-target="#customCarousel" data-slide-to="${k}"><i class="fas fa-chevron-circle-right"></i> ${r.T("class-tier-master","Master")}</a>\n </ul>\n </nav>\n </div>\n <div class="col">\n <div id="customCarousel" class="carousel slide" data-interval="false" data-ride="carousel">\n <ol class="carousel-indicators top-carousel d-lg-none">\n ${m?`<li class="${"beginner"==v?"active":""}" data-target="#customCarousel" data-slide-to="0"></li>`:""}\n ${g?`<li class="${"intermediate"==v?"active":""}" data-target="#customCarousel" data-slide-to="${b}"></li>`:""}\n ${h?`<li class="${"advanced"==v?"active":""}" data-target="#customCarousel" data-slide-to="${f}"></li>`:""}\n <li class="${"master"==v?"active":""}" data-target="#customCarousel" data-slide-to="${k}"></li>\n </ol>\n <div class="carousel-inner">\n ${m?`<div class="carousel-item ${"beginner"==v?"active":""}" data-name="beginner">\n <h3>${r.T("class-beginner-header","Beginner classes")}</h3>\n <div class="row carouselCardPadding">\n ${l}\n </div>\n </div>`:""}\n ${g?`<div class="carousel-item ${"intermediate"==v?"active":""}" data-name="intermediate">\n <h3>${r.T("class-intermediate-header","Intermediate classes")}</h3>\n <h6 class="text-muted">${r.T("class-intermediate-requirement","You need at least %s credits and finish the prerequisites to unlock intermediate classes.",[p.requiredCreditsUnlockIntermediate])}</h6>\n <div class="row carouselCardPadding">\n ${d}\n </div>\n </div>`:""}\n ${h?`<div class="carousel-item ${"advanced"==v?"active":""}" data-name="advanced">\n <h3>${r.T("class-advanced-header","Advanced classes")}</h3>\n <h6 class="text-muted">${r.T("class-advanced-requirement","You need at least %s credits and finish the prerequisites to unlock advanced classes.",[p.requiredCreditsUnlockAdvanced])}</h6>\n <div class="row carouselCardPadding">\n ${c}\n </div>\n </div>`:""}\n <div class="${"master"==v?"active":"carousel-item"}" data-name="master">\n <h3>${r.T("class-master-header","Master classes")}</h3>\n <h6 class="text-muted">${r.T("class-master-requirement","You need at least %s credits and finish the prerequisites to unlock master classes.",[p.requiredCreditsUnlockMaster])}</h6> \n <div class="row carouselCardPadding">\n ${u}\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>`}static getCardHtml(e,t,a,i){let o=S.isClassLocked(e.id),r=x.getActiveTaskForObject(e.id,"class"),l=t?t.status:"",d=void 0!==r,c="finished"===l,u=c&&S.getGrade(e.id),p="active"===l,m=p&&S.getAttendanceRate(e.id),g=n.getRandomString(),h=s.getInstance(),v='<i class="fas fa-info-circle"></i>';return d&&(v='<i class="fas fa-play-circle"></i>'),`<div class="min-card-container ${i||"col-lg-2 col-md-3 col-sm-6 col-12 pb-5"}">\n <div class="card h-100 no-overflow">\n\n <div class="img-container-overview no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=class&classId="+e.id)})}">\n <img src="${e.image}" class="card-img-top${o?" img-locked":""}${p?" img-active":""}${c?" img-finished":""}">\n </a>\n ${p?n.getBackdropProgressContainer(h.T("class-status-active","Active")+' <i class="fas fa-heart"></i>',m):""}\n ${o?n.getBackdropContainer(h.T("class-status-locked","Locked")+' <i class="fas fa-lock"></i>',"danger"):""} \n ${c?n.getBackdropContainer(`${h.T("class-status-grade","Grade")} <b>${u}</b> <i class="fas fa-check-circle"></i>`,"info"):""} \n ${n.getButtonsContainer([c?"":`<a onClick="${wi.register(()=>{n.toggleCard("showDailyExams-"+g)})}" class="btn btn-dark text-white">${v}</a>`,P.getJoinButton(e.id),P.getDropButton(e.id),P.getRetakeButton(e.id)])}\n <div id="showDailyExams-${g}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n ${void 0!==r?x.getTaskProgressHtml(r):`\n <ul class="list-group list-group-flush">\n <li class="list-group-item text-info"><b>${h.T("class-card-tasks","Available tasks")}:</b></li>\n ${P.getTasksHtml(e,t.attendances||0)}\n <li class="list-group-item text-danger"><b>${h.T("class-card-exams","Available exams")}:</b>${p||c?S.getKnowledgePointsInfo(e.id):""}</li>\n\n ${S.isExamAvailable(e.id)?P.getExamsHtml(e):`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${h.T("class-card-exams-locked","Currently locked")}</div>\n </div> \n </li>`}\n\n </ul>`}\n </div>\n </div>\n </div>\n\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">\n ${e.name} \n ${a?'<i class="fas fa-star text-warning"></i> ':""}\n <small class="text-muted">${e.name2}</small>\n </h5>\n <p class="card-text">${e.description}</p>\n </div>\n </div>\n </div>`}static getJoinButton(e){return S.isJoinButtonAvailable(e)?`<button onClick="${wi.register(()=>{S.joinClass(e)})}" type="button" class="btn btn-dark text-success"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("class-card-join-button","Join")}</button>`:""}static getDropButton(e){return S.isDropButtonAvailable(e)?`<button onClick="${wi.register(()=>{S.dropAssignedClass(e)})}" type="button" class="btn btn-dark text-danger"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("class-card-drop-button","Drop")}</button>`:""}static getRetakeButton(e){return S.isRetakeButtonAvailable(e)?`<button onClick="${wi.register(()=>{S.retakeClass(e)})}" type="button" class="btn btn-dark text-warning"><i class="fas fa-undo"></i> ${s.getInstance().T("class-card-retake-button","Retake")}</button>`:""}static getTasksHtml(e,t){let a="",s=S.getTodaysTasks(e.tasks,t,e.taskListSize);for(let e of s)a+=` <li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-info"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${e.task}</div>\n </div> \n </li>`;return a}static getExamsHtml(e){let t="";for(let a in e.tasks)e.tasks[a].isExam&&(t+=` <li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${e.tasks[a].task}</div>\n </div> \n </li>`);return t}}class A{static get expectedParameters(){return{getView:["classId"]}}static getView(e){const t=e.classId,a=S.isClassLocked(t),i=S.getAssignedClass(t),o=i?i.status:"",r=x.getActiveTaskForObject(t,"class"),l=void 0!==r,d="finished"===o,c="active"===o,u=d&&S.getGrade(t),m=s.getInstance();let g=p.classesData[t];return`<div class="container-fluid">\n <h2 class="text-center py-3">${g.name}</h2>\n <div class="row">\n <div class="d-none d-xl-block d-lg-block col-xl-3 col-lg-2">\n \n </div>\n <div class="col-xl-3 col-lg-4 col-md-6">\n <div class="img-container-overview">\n <a onClick="${wi.register(()=>{_.navigateBack()})}">\n <img src="${g.image}" class="img-fluid img-rounded img-thumbnail">\n </a>\n ${a?`<div class="alert-danger text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${m.T("class-status-locked","Locked")}</b></div>`:""}\n ${d?`<div class="alert-info text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text">${m.T("class-status-grade","Grade")} <b>${u}</b></div>`:""}\n ${c?`<div class="alert-success text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text"><b>${m.T("class-status-active","Active")}</b></div>`:""}\n </div>\n </div>\n <div class="col-xl-3 col-lg-4 col-md-6">\n ${A.getPrerequisites(g.prerequisites)}\n <p><b>${m.T("class-detail-days","Classes on")}:</b> ${n.daysToString(g.days)}</p>\n ${A.getMandatoryInfoHtml(g.id)}\n ${A.getBonusAttendanceDisabledHtml(g)}\n <p>\n ${g.description}\n ${A.getCommentHtml(g.comment)}\n </p>\n ${l?x.getTaskProgressHtml(r):d?"":`\n <ul class="list-group list-group-flush">\n <li class="list-group-item text-info"><b>${m.T("class-detail-tasks","Available task options")}:</b></li>\n ${A.getTasksHtml(g,i.attendances||0)}\n </ul>\n ${S.isExamAvailable(g.id)?`<ul class="list-group list-group-flush">\n <li class="list-group-item text-danger"><b>${m.T("class-detail-exams","Available exam options")}:</b>${c||d?S.getKnowledgePointsInfo(g.id):""}</li>\n <li class="list-group-item text-danger">${m.T("class-detail-expected-grade","If you take the exam now the expected grade is %s.",[S.getGrade(g.id)])}</li> \n ${A.getExamsHtml(g)}\n </ul>`:A.getExamPlaceholder(i,g)}\n <div class="card-body">\n ${A.getJoinButton(g.id)}\n ${A.getDropButton(g.id)}\n </div>\n `}\n ${A.getRetakeInfo(g.id)}\n </div>\n <div class="d-none d-xl-block d-lg-block col-xl-3 col-lg-2">\n \n </div>\n </div>\n </div>`}static getMandatoryInfoHtml(e){return S.isClassMandatory(e)?`<p><span class="text-warning">${s.getInstance().T("class-detail-mandatory","This is a mandatory class %s for your currently active major.",['<i class="fas fa-star text-warning"></i>'])}</span></p>`:""}static getBonusAttendanceDisabledHtml(e){return e.disableBonusAttendance||!1?`<p><span class="text-warning">${s.getInstance().T("class-detail-bonus-attendance-disabled","This class disabled any bonus attendance modifiers!")}</span></p>`:""}static getTasksHtml(e,t){let a="",s=S.getTodaysTasks(e.tasks,t,e.taskListSize);for(let t of s)a+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-info"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${t.task} ${A.getDailyButton(e.id,t.id)}</div>\n </div> \n </li>`;return a}static getExamsHtml(e){let t="";for(let a in e.tasks)e.tasks[a].isExam&&(t+=` <li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${e.tasks[a].task}${A.getExamButton(e.id,a)}</div>\n </div> \n </li>`);return t}static getCommentHtml(e){return!e||null==e||e.length<=0?"":`<br><span class="text-muted">${e}</span>`}static getPrerequisites(e){const t=A.prerequisitesToString(e);return t.length>0?`<p class=""><b>${s.getInstance().T("prerequisites","Prerequisites")}:</b> ${t}</p>`:""}static prerequisitesToString(e){return e.map(e=>`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=class&classId="+e)})}">${p.classesData[e].name}</a>`).join(", ")}static getJoinButton(e){return S.isJoinButtonAvailable(e)?`<button onClick="${wi.register(()=>{S.joinClass(e)})}" type="button" class="btn btn-outline-success mr-2"><i class="fas fa-plus-circle"></i> ${s.getInstance().T("class-detail-join-button","Join class")}</button>`:""}static getDropButton(e){return S.isDropButtonAvailable(e)?`<button onClick="${wi.register(()=>{S.dropAssignedClass(e)})}" type="button" class="btn btn-outline-danger mr-2"><i class="fas fa-minus-circle"></i> ${s.getInstance().T("class-detail-drop-button","Drop class")}</button>`:""}static getRetakeInfo(e){const t=s.getInstance();return S.isRetakeButtonAvailable(e)?`\n <div>\n <div class="pb-2 text-warning">\n ${t.T("class-detail-retake-warning","You already finished this class and got a %s, there is still room for improvement. You may retake this class and collect additional knowledge before attempting the exam again to get a better grade.",[`<b>${S.getGrade(e)}</b>`])}\n </div>\n <button onClick="${wi.register(()=>{S.retakeClass(e)})}" type="button" class="btn btn-outline-warning mr-2"><i class="fas fa-undo"></i> ${t.T("class-detail-retake-button","Retake class")}</button>\n </div>\n `:""}static getDailyButton(e,t){return S.isDailyAvailable(e)?`<br><button onClick="${wi.register(()=>{S.startDaily(e,t)})}" type="button" class="btn btn-outline-info mt-1" style="width: 100%;"><i class="fas fa-play-circle"></i> ${s.getInstance().T("class-detail-start-task-button","Start daily")}</button>`:""}static getExamButton(e,t){return S.isExamAvailable(e)?`<br><button onClick="${wi.register(()=>{S.startExam(e,t)})}" type="button" class="btn btn-outline-danger mt-1" style="width: 100%;"><i class="fas fa-play-circle"></i> ${s.getInstance().T("class-detail-start-exam-button","Start exam")}</button>`:""}static getExamPlaceholder(e,t){const a=s.getInstance();let i=`<li class="list-group-item text-danger"><b>${a.T("class-detail-exam-requirement-header","Exam unlock requirements")}:</b></li>`;return"active"!==e.status&&(i+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${a.T("class-detail-exam-requirement-join","You must join this class, before you can do any tasks or exams.")}</div>\n </div> \n </li>`),(S.wasAttendedToday(t.id)||x.isTaskActiveForObject(t.id,"class"))&&(i+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${a.T("class-detail-exam-requirement-attended","You already attended this class today, you can't do a task and the exam on the same day.")}</div>\n </div> \n </li>`),e.attendances<o.expectedAttendance[t.tier]&&(i+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${a.T("class-detail-exam-requirement-attendance","You must have collected at least %s knowledge points, you accumulated %s knowledge points.",[o.expectedAttendance[t.tier],e.attendances])}</div>\n </div> \n </li>`),e.attendances<Object.entries(t.tasks).filter(e=>!e[1].isExam).length&&(i+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${a.T("class-detail-exam-requirement-attendance-tasks","You must have done all available tasks.")}</div>\n </div> \n </li>`),p.getActivePunishments().length>0&&(i+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${a.T("class-detail-exam-requirement-punishment","You must finish all punishments.")}</div>\n </div> \n </li>`),n.hasMatchingDate(t.days)||(i+=`<li class="list-group-item">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">${a.T("class-detail-exam-requirement-days","You can only attend this exam on the defined days.")}</div>\n </div> \n </li>`),`<ul class="list-group list-group-flush pt-3">${i}</ul>`}}class S{static getKnowledgePointsInfo(e){return`<div style="float: right;"><a class="text-info" data-toggle="popover" data-content="${s.getInstance().T("class-knowledge-points-info","%s knowledge points collected.",[`<b>${S.getAttendance(e)} / ${S.getMaxAttendances(e)}</b>`])}"><i class="fas fa-question-circle"></i></a></div>`}static getAttendance(e){return S.getAssignedClass(e).attendances}static getMaxAttendances(e){return o.maxAttendances[p.classesData[e].tier]}static getAttendanceRate(e){return S.getAttendance(e)/S.getMaxAttendances(e)*100}static getGrade(e){return n.getGrade(S.getAttendance(e),S.getMaxAttendances(e))}static startDaily(e,t){if(!S.isDailyAvailable(e))return!1;x.startTask(p.classesData[e],"tasks",t)}static startExam(e,t){if(!S.isExamAvailable(e))return!1;x.startTask(p.classesData[e],"tasks",t)}static isJoinButtonAvailable(e){const t=S.getAssignedClass(e),a=S.isClassLocked(e);return!S.isMaxClassesReached()&&!a&&(!t||"active"!==t.status&&"finished"!==t.status)}static isRetakeButtonAvailable(e){const t=S.getAssignedClass(e);return t&&"finished"===t.status&&"A+"!==S.getGrade(e)}static isDropButtonAvailable(e){const t=S.getAssignedClass(e);return t&&"active"===t.status}static getAssignedClass(e){const t=p.getAssignedClasses().filter(t=>t.id==e);return 0!==t.length&&t[0]}static getActiveClasses(){return p.getAssignedClasses().filter(e=>"active"==e.status)}static getFinishedClasses(){return p.getAssignedClasses().filter(e=>"finished"==e.status)}static updateAssignedClassStatus(e,t){const a=p.getAssignedClasses().map(a=>(a.id===e&&(a.status=t),a));p.setAssignedClasses(a)}static addAssignedClass(e){if(S.getAssignedClass(e))return void S.updateAssignedClassStatus(e,"active");const t={id:e,status:"active",attendances:0};let a=p.getAssignedClasses();a.push(t),p.setAssignedClasses(a)}static getActiveTags(){let e=S.getActiveClasses(),t=p.classesData,a=[];for(let s of e){let e=t[s.id].tasks;for(let t of Object.keys(e))a=a.concat(e[t].tags)}return[...new Set(a)]}static addClassSkipToken(e){if(!Number.isInteger(e))return;let t=p.getClassSkipTokens()+e;t>o.maxConcurrentClasses&&(t=o.maxConcurrentClasses),t<0&&(t=0),p.setClassSkipTokens(t)}static wasAttendedToday(e){if(n.getSetting("enableEasyMode"))return!1;const t=S.getAssignedClass(e);if(!t)return!1;let a=t.lastAttended;return!(!a||void 0===a)&&n.isToday(a)}static setLastAttended(e,t){let a=p.getAssignedClasses();a=a.map(a=>(a.id==e&&(a.lastAttended=t),a)),p.setAssignedClasses(a)}static addClassAttendance(e,t){let a=p.getAssignedClasses();a=a.map(a=>(a.id==e&&(a.attendances+=t),a)),p.setAssignedClasses(a)}static isClassMandatory(e){return I.mandatoryClasses(p.getActiveMajor()).some(t=>t==e)}static getClassPrerequisitesDeep(e){const t=p.classesData[e].prerequisites;let a=[];a=a.concat(t);for(let e of t)a=a.concat(S.getClassPrerequisitesDeep(e));return a}static isClassLocked(e){const t=p.classesData[e];let a=0,s=S.collectedCredits;return"intermediate"===t.tier?a=p.requiredCreditsUnlockIntermediate:"advanced"===t.tier?a=p.requiredCreditsUnlockAdvanced:"master"===t.tier&&(a=p.requiredCreditsUnlockMaster),s<a||!S.prerequisitesDone(e)}static get collectedCredits(){return S.getFinishedClasses().reduce((e,t)=>e+(o.classesExamCredits[p.classesData[t.id].tier]||0),0)}static isMaxClassesReached(){return S.getActiveClasses().length>=o.maxConcurrentClasses}static retakeClass(e){const t=s.getInstance();S.isMaxClassesReached()?n.showToast(t.T("class-max-classes-header","Maximum classes active"),t.T("class-max-classes-content","You are only allowed to join %s classes at a time.",[o.maxConcurrentClasses])):n.requestUserConfirmation(t.T("class-retake-confirmation-header","Retake class"),t.T("class-retake-confirmation-content","Are you sure that you want to retake this class? You will not loose any accumulated experience but must attend the exam again."),()=>{S.updateAssignedClassStatus(e,"active"),S.setLastAttended(e,n.getNow().add(-1,"day").startOf("day").unix()),_.getInstance().inAppNavigation("?page=class&classId="+e)})}static joinClass(e){const t=s.getInstance();S.isMaxClassesReached()?n.showToast(t.T("class-max-classes-header","Maximum classes active"),t.T("class-max-classes-content","You are only allowed to join %s classes at a time.",[o.maxConcurrentClasses])):(S.addAssignedClass(e),_.getInstance().inAppNavigation("?page=class&classId="+e))}static dropAssignedClass(e){const t=s.getInstance();n.requestUserConfirmation(t.T("class-drop-confirmation-header","Drop class"),t.T("class-drop-confirmation-content","Are you sure that you want to drop this class?"),()=>{S.updateAssignedClassStatus(e,"dropped"),_.getInstance().inAppNavigation("?page=classes")})}static finishAssignedClass(e){S.updateAssignedClassStatus(e,"finished"),_.getInstance().inAppNavigation("?page=class&classId="+e)}static isDailyAvailable(e){const t=S.getAssignedClass(e);if(!t||"active"!==t.status)return!1;if(S.wasAttendedToday(e))return!1;const a=p.classesData[e];return!!n.hasMatchingDate(a.days)&&!x.isTaskActiveForObject(e,"class")}static prerequisitesDone(e){const t=p.classesData[e];if(!t)return!1;if(0===t.prerequisites.length)return!0;return!(t.prerequisites.filter(e=>{const t=S.getAssignedClass(e);return!t||"finished"!==t.status}).length>0)}static isExamAvailable(e){const t=S.getAssignedClass(e);if(!t)return!1;if("active"!==t.status)return!1;if(S.wasAttendedToday(e))return!1;const a=p.classesData[e];return!(t.attendances<o.expectedAttendance[a.tier])&&(!(t.attendances<Object.entries(a.tasks).filter(e=>!e[1].isExam).length)&&(!!n.hasMatchingDate(a.days)&&(!x.isTaskActiveForObject(e,"class")&&!(p.getActivePunishments().length>0))))}static getTodaysTasks(e,t,a){(!a||a<=0)&&(a=o.defaultTaskListSize);const s=(e=Object.entries(e).filter(e=>!e[1].isExam).map(e=>e[1])).length;if(s<=a)return e;let i=[],n=t;for(;i.length<a;)i.push(e[n%s]),n++;return i}}class j{static get expectedParameters(){return{getView:[]}}static getView(){const e=s.getInstance();let t="",a=0,i=u.getInstance();if(a++,t+=j.getHelpEntryHtml(e.T("help-overview-header","Game overview"),`\n <ul class="list-group list-group-flush">\n <li class="list-group-item"><b>1.</b> ${e.T("help-overview-1","Join a %smajor%s",[`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=majors")})}">`,"</a>"])}</li>\n <li class="list-group-item"><b>2.</b> ${e.T("help-overview-2","Join %smandatory classes%s %sMandatory classes are highlighted with a yellow star%s and %sextra side classes%s",[`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=progress")})}">`,"</a>",'<a class="text-info" data-toggle="popover" data-content="','<i class=\'fas fa-star text-warning\'></i>"><i class="fas fa-question-circle"></i></a>',`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=classes")})}">`,"</a>"])}</li>\n <li class="list-group-item"><b>3.</b> ${e.T("help-overview-3","Get involved with %sclubs%s and %spartners%s to adjust the difficulty and add additional challenges",[`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=clubs")})}">`,"</a>",`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=partners")})}">`,"</a>"])}</li>\n <li class="list-group-item"><b>4.</b> ${e.T("help-overview-4","Attend and pass classes to get %s credits",[p.requiredCreditsForGraduation])}</li>\n <li class="list-group-item"><b>5.</b> ${e.T("help-overview-5","Don't attend classes and you'll get %spunished%s",[`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=punishments")})}">`,"</a>"])}</li>\n <li class="list-group-item"><b>6.</b> ${e.T("help-overview-6","Graduate")}</li>\n </ul>\n `,"anchor"+a,void 0,"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-notice-header","Game notice"),`\n <p>\n ${e.T("help-notice-content",'\n Your health takes top priority. In case you start a task that turns out to be unhealthy,\n you should absolutely "finish" it without actually doing it! If you aren\'t feeling well,\n stop whatever you are currently doing and make sure you are fine. Contact a doctor if required.\n On the same notion, suicide is not an option, you are great the way you are and before doing \n anything that cannot be undone - <a href="https://suicidepreventionlifeline.org/">take a helping hand</a>.')}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-progress-header","Game progress notice"),`\n <p>\n ${e.T("help-progress-content","\n All game data is stored locally in your browser. Nothing is sent or stored online.\n Private browsing mode doesn't save data and sets limits, for the best experience it is therefore recommended to play in a normal \n browser window. In case you do play in a private window make sure to export your progress and keep the map file you used handy.\n You will need the map and save game file to continue playing where you left of.")}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-majors-header","Majors"),`\n <p>\n ${e.T("help-majors-content","\n Choose a %sMajor%s depending on what you'd like to specialise in. \n Each one has specific mandatory classes (prerequisites) and each of those classes has \n prerequisite classes of their own. Once you join a major go to the \n %sProgress page%s to see your mandatory classes. You need to \n get %s credits before attempting to graduate. If you decide to\n change your major you can go to it's page and drop it however this will result in a \n penalty of %s random punishments.",['<a href="?page=majors">',"</a>",'<a href="?page=progress">',"</a>",p.requiredCreditsForGraduation,o.majorDropPunishments])}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-classes-header","Classes and schedule"),`\n <p>\n ${e.T("help-classes-content","\n Every class has a weekly schedule that you need to keep track of. \n You'll need to complete one of the two daily tasks or exams to receive a knowledge point.\n If you fail to attend a class by the end of the day a punishment will be automatically \n rolled for that class. To unlock its exam you need to have attended it at least a few times \n and to not have any pending punishments. You will see a full set of the requirements if you open the class.\n Once you pass a class you can immediately enroll in another one if you wish. You can NOT join a class again or do their tasks once you've passed it. \n You can not enroll in more than %s classes at once. \n Do not enroll in too many classes if you can't keep up with the schedule. \n <b>Classes can also be done during the weekend. Weekend classes are optional and you won't be punished for skipping them.</b>",[o.maxConcurrentClasses])}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-punishments-header","Punishments"),`\n <ul class="list-group list-group-flush">\n <li class="list-group-item">${e.T("help-punishments-1","If you fail to attend a class by the end of the day a punishment will be automatically rolled for you.")}</li>\n <li class="list-group-item">${e.T("help-punishments-2",'If you have an active task and you fail to do it, you must press "Fail" in order to get punished.')}</li>\n <li class="list-group-item">${e.T("help-punishments-3","If you pause a task for more than %s you will get punished. If the task has multiple steps you will get additional pause time but this includes the time you need to finish the other tasks.",[moment.duration(o.maxTaskPause,"minutes").humanize()])}</li>\n <li class="list-group-item">${e.T("help-punishments-4","Punishments can be completed whenever you like.")}</li>\n <li class="list-group-item">${e.T("help-punishments-5","You can get a maximum of %s punishments.",[o.maxConcurrentPunishments])}</li>\n <li class="list-group-item">${e.T("help-punishments-6","You can't attend exams or graduate if you have any pending punishments, so make sure you don't stack too many of them.")}</li>\n <li class="list-group-item">${e.T("help-punishments-7","You get %s punishment for failing a daily task, %s for failing an exam and %s for failing your final thesis.",[o.failDailyPunishment,o.failExamPunishment,o.failThesisPunishment])}</li>\n </ul>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-clubs-header","Clubs"),`\n <ul class="list-group list-group-flush">\n <li class="list-group-item">${e.T("help-clubs-1","Clubs provide you with different bonuses such as free class skips and reduced task requirements for classes.")}</li>\n <li class="list-group-item">${e.T("help-clubs-2","Clubs are supplementary to your classes so you can combine them however you want.")}</li>\n <li class="list-group-item">${e.T("help-clubs-3","It is not possible to deactivate perks. They will last the rest of the day.")}</li>\n <li class="list-group-item">${e.T("help-clubs-4","Some club tasks overlap with tasks from classes. In this case you can you can use the club perk for free.")}</li>\n <li class="list-group-item">\n ${e.T("help-clubs-5","\n Make sure that when doing a class task and you have some club perks activated, you can stay true to the \n club's requirements. If you have activated a perk that requires you to wear a gag and you have a class\n that lasts 8 hours, it's highly unlikely that you'd wear the gag for those 8 hours, so make sure you only activated once only \n tasks are left that you can fulfill this requirement with.")}\n </li>\n <li class="list-group-item">${e.T("help-clubs-6","You can join a maximum of %s clubs and have a maximum of %s club perks active.",[o.maxConcurrentClubs,o.maxConcurrentClubPerks])}</li>\n <li class="list-group-item">${e.T("help-clubs-7","Later in the game you'll unlock Elite Clubs that provide greater benefits but have harder requirements.")}</li>\n <li class="list-group-item">${e.T("help-clubs-8","Keep in mind that you're limited to only %s elite club.",[o.maxConcurrentEliteClubs])}</li>\n <li class="list-group-item d-none">${e.T("help-clubs-9","Normal club perks can be toggled on and off while Elite club perks can be activated only once a day.")}</li>\n <li class="list-group-item">${e.T("help-clubs-10","All active perks are reset on the next day.")}</li>\n <li class="list-group-item d-none">${e.T("help-clubs-11","Keep in mind that the maximum reduction you can get from clubs is capped at 50%. If you have 50% reduction from your normal clubs and another 25% from an elite one you'll still get only 50% instead of 75%.")}</li>\n </ul>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-partners-header","Partners"),`\n <p>\n ${e.T("help-partners-content","\n Partners are very similar to clubs with the only difference being that they make \n things harder instead of easier but in return you'll get extra points. \n You can have up to %s partners. \n Their perks can be activated once per day and deactivate automatically on the next.",[o.maxConcurrentPartner])}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-tasks-header","Tasks and timers"),`\n <p>\n ${e.T("help-tasks-content",'\n The game has a tasks system for majors and classes. On your schedule page you\'ll \n be able to select which task to do for your active classes. Each task can have multiple sub-tasks\n Once you start a task you\'ll see the new task show up where you can either start its timer or \n press "Complete" if the task is not time dependent. Timers can be paused/resumed\n and they carry over between days. You won\'t get punished if the task is running from\n yesterday. If you fail to do a task, you must press "Fail" in order to get punished.\n The active timers for all your classes are also visible on the schedule page for\n each class.')}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-grades-header","Grades"),`\n <p>\n ${e.T("help-grades-content","\n Whenever you attend and finish class task you will receive one knowledge point, if you have modifiers active (from partners / clubs)\n you can receive additional ones. Based on these knowledge points you will receive a grade once you have taken the exam. It's up\n to you to decide how well you want to perform during the exam. The grades you receive will be used to calculate your major grade.\n In case you already finished a class and want to improve your grade, you can retake them anytime. If you retake a class you need to repeat the\n exam and loose the credits related to the class but you will keep the already collect knowledge points.\n If you re-roll a punishment you will loose knowledge points to a random class you are currently taking.\n <br> To make sure you don't fail, you need to collect at least:")}\n </p>\n ${j.getGradeHtml()}\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-credits-header","Credits"),`\n <p>\n ${e.T("help-credits-content","\n Graduating requires that you have at least %s credits.\n You first need to attend classes to unlock\n the exam. Once you finish the exam for a class you will get matching credits.",[p.requiredCreditsForGraduation])}\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item"><b>${e.T("class-beginner-header","Beginner classes")}:</b> ${o.classBeginnerCredits} ${e.T("credits","Credits")}</li>\n <li class="list-group-item"><b>${e.T("class-intermediate-header","Intermediate classes")}:</b> ${o.classIntermediateCredits} ${e.T("credits","Credits")}</li>\n <li class="list-group-item"><b>${e.T("class-advanced-header","Advanced classes")}:</b> ${o.classAdvancedCredits} ${e.T("credits","Credits")}</li>\n <li class="list-group-item"><b>${e.T("class-master-header","Master classes")}:</b> ${o.classMasterCredits} ${e.T("credits","Credits")}</li>\n </ul>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),a++,t+=j.getHelpEntryHtml(e.T("help-graduating-header","Graduating"),`\n <p>\n ${e.T("help-graduating-content",'\n Once you have completed all your mandatory classes and collected %s\n credits through attending extra classes you may attempt to graduate.\n Some Majors could take up to a month for the final task. If you fail to do one of the thesis requirements \n you must press "Fail" which will roll %s punishments. Some majors have an additional punishment option which \n increases the timer\'s duration. After you graduate your major will be marked as Completed and your progress will be preserved.\n You can sign up for another one in another field which will become very \n useful in the future when you start building your career.',[p.requiredCreditsForGraduation,o.failThesisPunishment])}\n </p>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1)),i.map.rouletteOptions){const s=i.map.rouletteOptions;let n="";for(let e in s){const t=s[e];n+=`<li class="list-group-item"><b>${100*t.probability}%</b> ${t.description}</li>`}a++,t+=j.getHelpEntryHtml(e.T("help-roulette-header","Orgasm roulette system"),`\n <p>\n ${e.T("help-roulette-content","\n The only way you are allowed to cum is either through classes or from the %sOrgasm Roulette%s. \n Every day you'll get the chance to spin is once and test your luck out.",['<a href="?page=roulette">',"</a>"])}\n </p>\n <ul class="list-group list-group-flush">\n ${n}\n </ul>\n `,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1))}if(void 0!==i.map.general.help&&i.map.general.help.length>0)for(const e of i.map.general.help)a++,t+=j.getHelpEntryHtml(e.title,e.text,"anchor"+a,"anchor"+(a-1),"anchor"+(a+1));return`<div class="container pb-3">\n ${t}\n </div>`}static getHelpEntryHtml(e,t,a,s,i){return`\n <div class="row pt-3">\n <div class="col">\n <a id="${a}" class="anchor"></a>\n <h4 class="py-3 text-warning">\n ${void 0!==i?`<a href="#${i}"><i class="fas fa-chevron-circle-down"></i></a> `:""}\n ${void 0!==s?`<a href="#${s}"><i class="fas fa-chevron-circle-up"></i></a> `:""}\n ${e}\n </h4>\n ${t}\n </div>\n </div>\n `}static getGradeHtml(){const e=s.getInstance();let t=o.maxAttendances.beginner,a=o.maxAttendances.intermediate,i=o.maxAttendances.advanced,n=o.maxAttendances.master,r="",l=t!==a,d=a!==i,c=i!==n;for(const e of Object.entries(o.grades))r+=`\n <tr>\n <td>${e[0]}</td>\n <td>${100*e[1]}%</td>\n <td>${e[1]*t}</td>\n ${l?`<td>${e[1]*a}</td>`:""}\n ${d?`<td>${e[1]*i}</td>`:""}\n ${c?`<td>${e[1]*n}</td>`:""}\n </tr>\n `;return`\n <table class="table table-hover">\n <thead>\n <tr>\n <th scope="col">${e.T("help-table-grade","Grade")}</th>\n <th scope="col">${e.T("help-table-threshold","Threshold")}</th>\n <th scope="col">${e.T("help-table-min-knowledge","Minimum Knowledge")}</th>\n ${l?`<th scope="col">${e.T("help-table-min-knowledge-intermediate","Minimum Knowledge Intermediate")}</th>`:""}\n ${d?`<th scope="col">${e.T("help-table-min-knowledge-advanced","Minimum Knowledge Advanced")}</th>`:""}\n ${c?`<th scope="col">${e.T("help-table-min-knowledge-master","Minimum Knowledge Master")}</th>`:""}\n </tr>\n </thead>\n <tbody>\n ${r}\n </tbody>\n </table>\n `}}class D{static get expectedParameters(){return{getView:[],import_save:["savegame"]}}static import_save(e){return _.replaceHistory("?page=settings"),E.importSaveGamePrivateBin(atob(e.savegame)),D.getView()}static import_save(e){return _.replaceHistory("?page=settings"),E.importSaveGamePrivateBin(atob(e.savegame)),D.getView()}static getView(){document.querySelector("html").addEventListener("navigationFinished",(function(e){$("#settings-container select[multiple]").selectpicker(),E.updateServiceWorkerStatus()}));const e=s.getInstance(),t=n.canCompress();return`<div class="container" id="settings-container">\n <h4 class="py-3 text-warning">${e.T("settings-header","Settings")}</h4>\n <div class="row pt-3">\n <div id="serviceworker-status" class="col">\n \x3c!-- filled by delayed function call --\x3e\n </div> \n </div>\n <div class="row pt-3">\n <div id="zlib-status" class="col">\n ${t?"":D.getStatusAlert(e.T("settings-compression-status-available","The compression library is not available!"),"danger")}\n </div>\n </div>\n <div class="row pt-3">\n <div class="col">\n <div id="settings-form-group" class="form-group">\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="enableEasyMode" ${n.getSetting("enableEasyMode")?"checked":""}>\n <label class="custom-control-label" for="enableEasyMode">${e.T("settings-easy-mode-checkbox","Easy mode")}</label>\n </div>\n <p>${e.T("settings-easy-mode-description",'Easy mode allows classes to be attended on any day, removes punishments for skipping them. It can be used as a "Pause" for the game.')}</p>\n \n <div class="${t?"":"d-none"}">\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="compressExports" ${n.getSetting("compressExports")?"checked":""}>\n <label class="custom-control-label" for="compressExports">${e.T("settings-compress-exports-checkbox","Compress Exports")}</label>\n </div>\n <p>${e.T("settings-compress-exports-description","Make it just a little bit easier to share large files by compressing them slightly. %sINFO:%s This should help on most apple devices!%s",['<span class="text-info"><b>',"</b>","</span>"])}</p>\n </div>\n\n <div class="${t?"":"d-none"}">\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="useImageExport" ${n.getSetting("useImageExport")?"checked":""}>\n <label class="custom-control-label" for="useImageExport">${e.T("settings-image-exports-checkbox","Image Exports")}</label>\n </div>\n <p>${e.T("settings-image-exports-description","Let's share an image instead of a random text file. Requires compression! %sWARNING:%s This is only EXPERIMENTAL, big files can turn unreadable! Most messengers will remove the added data making them useless!%s",['<span class="text-warning"><b>',"</b>","</span>"])}</p>\n </div>\n\n <div class="${t?"":"d-none"}">\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="completeExports" ${n.getSetting("completeExports")?"checked":""}>\n <label class="custom-control-label" for="completeExports">${e.T("settings-complete-exports-checkbox","Complete Exports")}</label>\n </div>\n <p>${e.T("settings-complete-exports-description","No need to import maps and saves one by one Everything will be exported as a single file. Requires compression! %sINFO:%s This will result in giant saves!%s",['<span class="text-info"><b>',"</b>","</span>"])}</p>\n </div>\n\n <input type="range" class="form-control-range" id="timeOffsetSelection" min="-12" max="12" step="0.5" value="${n.getSetting("timeOffsetSelection")||0}" onchange="document.getElementById('timeOffsetSelectionInfo').innerHTML = this.value + '${e.T("settings-time-offset-hour","h")}'">\n <p>${e.T("settings-time-offset-selection","Choose a custom time offset and make the night your day. Current game time is: ")} ${n.getNow().format("YYYY-MM-DD HH:mm:ss")} (<span id="timeOffsetSelectionInfo">${n.getSetting("timeOffsetSelection")||0}${e.T("settings-time-offset-hour","h")}</span>)</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="enableHardcorePunishments" ${n.getSetting("enableHardcorePunishments")?"checked":""}>\n <label class="custom-control-label" for="enableHardcorePunishments">${e.T("settings-hardcore-punishments-checkbox","Hardcore punishments")}</label>\n </div>\n <p>${e.T("settings-hardcore-punishments-description","Hardcore punishments are crueler than the rest and exist just for the novelty. They are disabled by default and don't provide any benefits when enabled besides enhanced gameplay.")}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="enableAutoHide" ${n.getSetting("enableAutoHide")?"checked":""}>\n <label class="custom-control-label" for="enableAutoHide">${e.T("settings-auto-hide-checkbox","Auto hide")}</label>\n </div>\n <p>${e.T("settings-auto-hide-description","Make sure nobody sees your progress by accident. The page automatically hides it's content after %s.",[moment.duration(o.autoHideTimer,"minutes").humanize()])}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="enableBackToTop" ${n.getSetting("enableBackToTop")?"checked":""}>\n <label class="custom-control-label" for="enableBackToTop">${e.T("settings-back-top-checkbox","Back to top button")}</label>\n </div>\n <p>${e.T("settings-back-top-description",'Especially on mobile it is easy to get lost on long pages. You can enable this handy "Back to top" button to easily return to the start.')}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="hideActiveClasses" ${n.getSetting("hideActiveClasses")?"checked":""}>\n <label class="custom-control-label" for="hideActiveClasses">${e.T("settings-hide-active-classes-checkbox","Hide active classes in the classes overview")}</label>\n </div>\n <p>${e.T("settings-hide-active-classes-description","Once you started classes they will be shown in the %sschedule%s. To make the %sclasses overview%s cleaner you can therefore choose to hide them.",[`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=schedule")})}">`,"</a>",`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=classes")})}">`,"</a>"])}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="hideFinishedClasses" ${n.getSetting("hideFinishedClasses")?"checked":""}>\n <label class="custom-control-label" for="hideFinishedClasses">${e.T("settings-hide-finished-classes-checkbox","Hide finished classes in the classes overview")}</label>\n </div>\n <p>${e.T("settings-finished-active-classes-description","Once you finished classes they will be shown in the %sprogress overview%s. To make the %sclasses overview%s cleaner you can therefore choose to hide them.",[`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=progress")})}">`,"</a>",`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=classes")})}">`,"</a>"])}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="autoExtendCarousel" ${n.getSetting("autoExtendCarousel")?"checked":""}>\n <label class="custom-control-label" for="autoExtendCarousel">${e.T("settings-skip-tier-checkbox","Automatically skip to the next tier")}</label>\n </div>\n <p>${e.T("settings-skip-tier-description","Pages that use carousels to display multiple tiers of content will automatically switch to the next tier once you reach the bottom of the page.")}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="showConfirmations" ${n.getSetting("showConfirmations")?"checked":""}>\n <label class="custom-control-label" for="showConfirmations">${e.T("settings-show-confirmations-checkbox","Show confirmation requests for critical changes.")}</label>\n </div>\n <p>${e.T("settings-show-confirmations-description","Some actions have a bigger impact, to make sure you don't do them by accident you will get a confirmation request first.")}</p>\n\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="useTableView" ${n.getSetting("useTableView")?"checked":""}>\n <label class="custom-control-label" for="useTableView">${e.T("settings-detailed-table-view-checkbox","Use the detailed table view.")}</label>\n </div>\n <p>${e.T("settings-detailed-table-view-description","Switch between the original 'card'-view (which emulates the end result) and the new 'table'-view for the Mapping-Tool.")}</p>\n\n <select class="form-control btn-secondary" id="style">\n ${D.getStyleSelection()}\n </select>\n <p>${e.T("settings-theme-description","Select your prefered theme for this website.")}</p>\n\n <select class="form-control btn-secondary" id="language">\n ${D.getLanguageSelection()}\n </select>\n <p>${e.T("settings-language-description","Select your prefered language for this website.")}</p>\n\n </div>\n <button class="btn btn-outline-success" onclick="${wi.register(()=>{E.saveSettings()})}"><i class="fas fa-save"></i> ${e.T("settings-save-button","Save settings")}</button>\n <button class="btn btn-outline-danger" onclick="${wi.register(()=>{E.resetSettings()})}"><i class="fas fa-undo"></i> ${e.T("settings-reset-button","Reset settings")}</button>\n </div>\n </div>\n \n ${u.getInstance().isMapLoaded?D.saveManagementFormView():""}\n ${u.getInstance().isMapLoaded?D.mapManagementFormView():""}\n\n <div class="row pb-3">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("settings-global-actions-header","Global actions")}</h4>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">\n <a class="btn btn-outline-info mr-3" target="_blank" href="https://discord.gg/vTDJPnhdvx"><i class="fas fa-link"></i> ${e.T("settings-join-discord-button","Join Discord")}</a>\n ${e.T("settings-join-discord-description","Feel free to join the official PU-Discord.")}\n </li>\n <li class="list-group-item">\n <button class="btn btn-outline-warning mr-3" onclick="${wi.register(()=>{E.resetTutorial()})}"><i class="fas fa-undo"></i> ${e.T("settings-reset-tutorial-button","Reset tutorial")}</button>\n ${e.T("settings-reset-tutorial-description","Reset the introduction tutorials.")}\n </li>\n <li class="list-group-item">\n <button class="btn btn-outline-danger mr-3" onclick="${wi.register(()=>{E.resetGame()})}"><i class="fas fa-undo"></i> ${e.T("settings-reset-game-button","Reset game")}</button>\n ${e.T("settings-reset-game-description","Reset game completely.")}\n </li>\n </ul>\n </div>\n </div>\n </div>`}static saveManagementFormView(){const e=s.getInstance(),t=n.canCompress();return`<div class="row">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("settings-save-selection-header","Save game actions")}</h4>\n <div class="form-group">\n ${n.getSetting("sessionToken")?`\n <button class="btn btn-outline-success mr-1" onclick="${wi.register(()=>{u.getInstance().checkOnlineSaveGame(()=>{_.reload()})})}"><i class="fas fa-import"></i> ${e.T("settings-save-sync-button","Sync this device now")}</button>\n <button class="btn btn-outline-danger" onclick="${wi.register(()=>{E.resetSaveSync()})}"><i class="fas fa-trash"></i> ${e.T("settings-reset-save-sync-button","Remove save sync for this device")}</button><br>\n `:""}\n </div>\n <div class="form-group">\n <input type="file" id="settings-import" accept=".puSave,.puc" style="display:none" onchange="${wi.register(()=>{E.importSaveGameFile(event)})}"/>\n <a onClick="$('#settings-import').click()" class="btn btn-outline-warning text-warning"><i class="fas fa-file-import"></i> ${e.T("settings-import-save-button","Import save")}</a>\n <button class="btn btn-outline-warning ${t?"":"d-none"}" onclick="${wi.register(()=>{E.importSaveGameClipboard()})}"><i class="fas fa-paste"></i> ${e.T("settings-import-save-clipboard-button","Paste save from clipboard")}</button>\n <button class="btn btn-outline-success" onclick="${wi.register(()=>{E.exportSaveGameFile()})}"><i class="fas fa-file-export"></i> ${e.T("settings-export-save-button","Export save")}</button>\n <button class="btn btn-outline-success ${t?"":"d-none"}" onclick="${wi.register(()=>{E.exportSaveGameClipboard()})}"><i class="fas fa-copy"></i> ${e.T("settings-export-save-clipboard-button","Copy save to clipboard")}</button>\n \n \x3c!--\n <button class="btn btn-outline-success mr-3" onclick="${wi.register(()=>{E.exportSavegamePrivateBin()})}"><i class="fas fa-file-export"></i> ${e.T("settings-share-save-button","Export save without file")}</button>\n <div id="savegameQR"></div>\n --\x3e\n </div>\n </div>\n </div>`}static mapManagementFormView(){const e=s.getInstance();return`<div class="row">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("settings-maps-header","Map management")}</h4>\n <div class="form">\n <div class="form-group">\n <a href="?page=mapping" class="btn btn-outline-info mr-3"><i class="fas fa-toolbox"></i> ${e.T("settings-map-tool-button","Mapping tool")}</a>\n ${e.T("settings-map-tool-button-description","Open the mapping tool to create your own courses.")}\n </div>\n <div class="form-group">\n <input type="file" id="map-import" accept=".pu,.puc" style="display:none" onChange="${wi.register(()=>{u.getInstance().importMapFile(event)})}"/>\n <a onClick="$('#map-import').click()" class="btn btn-outline-success text-success mr-3"><i class="fas fa-file-import"></i> ${e.T("settings-map-import-button","Import a map")}</a>\n ${e.T("settings-map-import-button-description","Import a new map or update an existing map.")}\n </div>\n <div class="form-group">\n <label for="mapSelection">${e.T("settings-map-selection-header","Map selection")}</label>\n <select class="form-control btn-secondary" id="mapSelection">\n ${D.getMapSelection()}\n </select>\n <small class="form-text text-muted">${e.T("settings-map-selection-info-1","Switch to a different map (the current progress is saved for each map separately).")}</small>\n <small class="form-text text-muted">${e.T("settings-map-selection-info-2","The map is tied to your current save game. If you want to import/export a save game you will need to do so for each map separatly, selected Sub-Maps are included in the active map.")}</small> \n\n <button class="btn btn-outline-success" onclick="${wi.register(()=>{E.switchMap()})}"><i class="fas fa-exchange-alt"></i> ${e.T("settings-switch-map-button","Switch to selected map")}</button>\n <button class="btn btn-outline-danger" onclick="${wi.register(()=>{E.deleteMap()})}"><i class="fas fa-trash"></i> ${e.T("settings-delete-map-button","Delete selected map")}</button>\n </div>\n <div class="form-group">\n <label for="assigned-submaps">${e.T("settings-submap-selection-header","Sub-Map selection")}</label>\n <select multiple class="form-control assigned-submaps" id="assigned-submaps" data-live-search="true" data-style="btn-secondary">\n ${D.getSubMapSelection()}\n </select>\n <small class="form-text text-muted">${e.T("settings-submap-selection-description",'Please select all maps you want to use as a Sub-Map and import the related content. You must import maps using the "Import a new map" function above before you can select them here. Make sure to select the main map carefully.')}</small>\n <button class="btn btn-outline-success" onclick="${wi.register(()=>{E.setSubMaps()})}"><i class="fas fa-exchange-alt"></i> ${e.T("settings-submap-selection-button","Save Sub-Map selection")}</button>\n </div>\n </div>\n </div>\n </div>`}static getMapSelection(){const e=u.getInstance().maps,t=u.getInstance().mapId;let a="";for(const s of e)a+=`<option value="${s.mapData.mapId}" ${s.mapData.mapId==t?"selected":""}>${s.mapData.general.title}</option>`;return a}static getSubMapSelection(){const e=u.getInstance(),t=e.maps,a=e.mapId,s=e.subMaps;let i="";for(const e of t)e.mapData.mapId!=a&&(i+=`<option value="${e.mapData.mapId}" ${s.includes(e.mapData.mapId)?"selected":""}>${e.mapData.general.title}</option>`);return i}static getStyleSelection(){let e="";const t=n.getSetting("style");for(const a of Object.keys(o.styles))e+=`<option value="${a}" ${a==t?"selected":""}>${a.charAt(0).toUpperCase()}${a.slice(1)}</option>`;return e}static getLanguageSelection(){const e=n.getSetting("language");let t=`<option value="en" ${e?"":"selected"}>English</option>`;for(const a of s.availableLanguages)t+=`<option value="${a.id}" ${a.id==e?"selected":""}>${a.text}</option>`;return t}static getStatusAlert(e,t="info"){s.getInstance();return`\n <div class="alert alert-${t}" role="alert">\n ${e}\n </div>\n `}}class E{static resetTutorial(){n.setTutorial(""),_.inAppReload()}static switchMap(){const e=$("#mapSelection").val(),t=u.getInstance();e!=t.mapId&&(t.registerLoadFinished(()=>{_.reload()}),t.changeMap(e))}static setSubMaps(){const e=$("#assigned-submaps").val(),t=u.getInstance();t.subMaps=e,t.setSubMaps(()=>{_.navigate("?page=home")})}static deleteMap(){const e=s.getInstance();n.requestUserConfirmation(e.T("settings-delete-map-confirmation-header","Delete map"),e.T("settings-delete-map-confirmation-content","Are you sure that you want delete the currently selected map? You will loose the save as well."),()=>{const e=$("#mapSelection").val();u.getInstance().deleteMap(e,()=>{_.reload()})})}static saveSettings(){let e={};for(let t of $('#settings-form-group input[type="checkbox"]'))e[t.id]=t.checked;for(let t of $("#settings-form-group select"))e[t.id]=t.value;for(let t of $('#settings-form-group input[type="range"]'))e[t.id]=parseFloat(t.value);e.enableEasyMode!==n.getSetting("enableEasyMode")&&p.resetAttendanceDeadline(u.getInstance().getSavegame()),n.setSettings(e),_.reload()}static resetSettings(){n.setSettings(n.getDefaultSettings()),_.reload()}static async updateServiceWorkerStatus(e="#serviceworker-status"){const t=s.getInstance(),a=t.T("settings-serviceworker-status-loading","Your ServiceWorker status is currently loading..."),i=D.getStatusAlert(a);$(e).empty().append(i);const o=await n.getServiceWorkerStatus();let r="";r="inactive"===o.id?t.T("settings-serviceworker-status-inactive","ServiceWorker feature is disabled!"):"active"===o.id?t.T("settings-serviceworker-status-active","Your ServiceWorker is active!"):"waiting"===o.id?t.T("settings-serviceworker-status-waiting","Your ServiceWorker is waiting for activation."):"installing"===o.id?t.T("settings-serviceworker-status-installing","Your ServiceWorker is currently installing."):"unregistered"===o.id?t.T("settings-serviceworker-status-unregistered","The ServiceWorker is not registered!"):t.T("settings-serviceworker-status-unknown","Your ServiceWorker is in an unknown state.");const l=D.getStatusAlert(r,o.type);$(e).empty().append(l)}static resetGame(){const e=s.getInstance();n.requestUserConfirmation(e.T("settings-reset-game-confirmation-header","Reset game"),e.T("settings-reset-game-confirmation-content","Are you sure that you want delete all local data and progress? You will loose your complete progress."),()=>{localStorage.clear(),r.getInstance().drop(()=>{_.navigate("?page=home")})})}static importSaveGame(e){const t=s.getInstance();let a=u.getInstance().importSaveGame(e,()=>{_.navigate("?page=home")});a&&n.showToast(t.T("settings-save-import-failed-header","SaveGame import failed"),a,"danger")}static async importSaveGameFile(e){const t=s.getInstance(),a={title:t.T("settings-save-import-confirmation-header","Import a save"),text:t.T("settings-save-import-confirmation-content","Are you sure that you want to overwrite your current progress? Make sure to only load saves from trusted sources. Proceed at your own risk.")},i={title:t.T("settings-save-import-failed-header","Save import failed"),text:t.T("settings-save-import-failed-content","During the import for the selected file an error occured. You can check the console for more information.")},n={title:t.T("settings-file-import-compression-error-header","Compression unavailable"),text:t.T("settings-file-import-compression-error-content","The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.")},o={title:t.T("settings-file-import-aborted-header","Import aborted"),text:t.T("settings-file-import-aborted-content","The file you tried to import included untrusted code. To protect you the import was aborted.")},r=await _.runImport(e,i,a,n,o);E.importSaveGame(r)}static async exportSaveGameFile(){const e=s.getInstance(),t={title:e.T("settings-save-export-maps-failed-header","Save export interrupted"),text:e.T("settings-save-export-maps-failed-content","During the export an error occured while loading the active maps for a full export.")};await _.runExport("pu_save",await _.getSavegameData(t))}static async importSaveGameClipboard(){const e=s.getInstance(),t={title:e.T("settings-save-import-confirmation-header","Import a save"),text:e.T("settings-save-import-confirmation-content","Are you sure that you want to overwrite your current progress? Make sure to only load saves from trusted sources. Proceed at your own risk.")},a={title:e.T("settings-save-import-failed-header","Save import failed"),text:e.T("settings-save-import-failed-content","During the import for the selected file an error occured. You can check the console for more information.")},i={title:e.T("settings-file-import-aborted-header","Import aborted"),text:e.T("settings-file-import-aborted-content","The file you tried to import included untrusted code. To protect you the import was aborted.")},n=await _.runClipboardImport("pu_save",a,t,i);E.importSaveGame(n)}static async exportSaveGameClipboard(){const e=s.getInstance(),t={title:e.T("mapping-export-clipboard-success-header","Exported to clipboard"),text:e.T("mapping-export-clipboard-success-content","The data was exported to the clipboard.")},a={title:e.T("mapping-export-clipboard-error-header","Clipboard export failed"),text:e.T("mapping-export-clipboard-error-content","Failed to export the data to the clipboard.")},i={title:e.T("settings-save-export-maps-failed-header","Save export interrupted"),text:e.T("settings-save-export-maps-failed-content","During the export an error occured while loading the active maps for a full export.")};await _.runClipboardExport("pu_save",i,t,a)}static async exportSavegamePrivateBin(){const e=s.getInstance(),t={title:e.T("settings-save-export-maps-failed-header","Save export interrupted"),text:e.T("settings-save-export-maps-failed-content","During the export an error occured while loading the active maps for a full export.")},a=JSON.stringify(await _.getSavegameData(t));let i=function(t){if(t.error||!t.url)return void n.showToast(e.T("settings-save-export-failed-header","Save game export failed"),e.T("settings-save-export-failed-content","Sharing your save file was not successful."));const a=`${n.getCurrentUrl()}?page=settings&pageType=import_save&savegame=${btoa(t.url)}`;c.insertQR(a,"PU.save","#savegameQR")}.bind(this);c.uploadToPastebin(a,i)}static importSaveGamePrivateBin(e){const t=s.getInstance();let a=function(e){!e.error&&e.data?E.importSaveGame(e.data):n.showToast(t.T("settings-import-share-save-failed-header","Save game import failed"),t.T("settings-import-share-save-failed-content","Importing the shared save file was not successful."))}.bind(this),i=function(){c.downloadFromPastebin(e,a)}.bind(this);n.requestUserConfirmation(t.T("settings-import-share-save-confirmation-header","Import a save"),t.T("settings-import-share-save-confirmation-content","Are you sure that you want to overwrite your current progress? Make sure to only install saves from trusted sources. Proceed at your own risk."),i)}static importMapPrivateBin(e){const t=s.getInstance();let a=function(e){!e.error&&e.data?u.getInstance().importMap(e.data):n.showToast(t.T("settings-import-share-map-failed-header","Map import failed"),t.T("settings-import-share-map-failed-content","Importing the shared map file was not successful."))}.bind(this),i=function(){c.downloadFromPastebin(e,a)}.bind(this);n.requestUserConfirmation(t.T("settings-import-share-map-confirmation-header","Import a map"),t.T("settings-import-share-map-confirmation-content","Are you sure that you want to import this map? Make sure to only install maps from trusted sources. Proceed at your own risk."),i)}static resetSaveSync(){n.setSetting("sessionToken",void 0),_.reload()}static setupSaveSync(e){const t=s.getInstance();let a=function(e){!e.error&&e.data?(n.scheduleToast(t.T("settings-import-save-sync-header","Save-Sync setup finished"),t.T("settings-import-save-sync-content","Your saves will now be synced, if the required maps are available.")),_.reload()):n.showToast(t.T("settings-import-save-sync-failed-header","Save-Sync setup failed"),t.T("settings-import-save-sync-failed-content","The provided key was not accepted by the backend. You can try to create a new session or try again later."))}.bind(this);c.getInstance().exchangeSaveGameToken(e,a)}}class N{static get expectedParameters(){return{getView:[],feedback:[],import_map:["map"],save_sync:["exchangeToken"]}}static save_sync(e){return _.replaceHistory("?page=home"),E.setupSaveSync(e.exchangeToken),N.getView()}static feedback(){const e=s.getInstance();return _.replaceHistory("?page=home"),n.showToast(e.T("home-feedback-title","Feedback"),`${e.T("home-feedback-description",'Thank you very much for providing a quick "heartbeat"!')} <a target="_blank" href="https://discord.gg/YHb2GPw">${e.T("home-feedback-discord","In case you want to join the PU Discord - click here.")}</a> `,"success"),N.getView()}static import_map(e){return _.replaceHistory("?page=home"),E.importMapPrivateBin(atob(e.map)),N.getView()}static getView(){const e=s.getInstance();return`<div class="container pb-3">\n <div class="row pt-3">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("home-welcome-header","Welcome to Project University")}</h4>\n <p>\n ${e.T("home-welcome-info-1","This project aims to let you either create your very own university or \n join a university that was created by somebody else. As such this website doesn't provide any content \n itself and you will need to provide a PU-map. A PU-map is a user generated file that contains the data \n required to define a university. This website runs completly on the users device - no user generated content\n is stored on the server hosting this website. Therefore please make sure to make backups of your progress,\n think twice before importing unknown content, stay safe and legal.")}\n </p>\n <p>\n ${e.T("home-welcome-info-2","\n The general idea of your university life consists of enrolling in a major, choosing classes and do the\n daily tasks required. To finish a class attend the exam and unlock new classes or the finals.\n Once all prerequisites are fulfilled you can attempt the final major exam. You can join clubs or get yourself \n a partner to change the difficulty for specific classes and gain small perks. You will be punished if you fail \n to meet any requests.")}\n </p>\n <p>\n ${e.T("home-welcome-info-3","So import a map sign up for a major, join some classes and let the journey begin!")}\n </p>\n <input type="file" id="map-import" accept=".pu,.puc" style="display:none" onChange="${wi.register(()=>{u.getInstance().importMapFile(event)})}"/>\n <a onClick="$('#map-import').click()" class="btn btn-info pb-2 pt-2 w-100 mb-2"><i class="fas fa-file-import"></i> ${e.T("home-load-map-button","Load a university map!")}</a>\n <a class="btn btn-info pb-2 pt-2 w-100" onClick="${wi.register(()=>{_.navigate("?page=mapping")})}"><i class="fas fa-pen"></i> ${e.T("home-create-map-button","Create your own map!")}</a>\n </div>\n </div>\n <div class="row">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("home-changelog-header","Changelog")}</h4>\n \n <div class="card mb-3">\n <h5 class="card-header">2025-07-31</h5>\n <div class="card-body">\n <p>\n Just added some missing chinese translations. Thanks a lot to Nebulas Astra (github:@nebulas-star)!\n </p>\n </div>\n </div>\n\n <a class="btn btn-info w-100 mb-3" data-toggle="collapse" href="#collapseChangelog" role="button" aria-expanded="false" aria-controls="collapseChangelog">${e.T("home-show-changelogs-button","Previous changelogs")}</a>\n\n <div class="collapse" id="collapseChangelog">\n \n <div class="card mb-3">\n <h5 class="card-header">2024-09-24</h5>\n <div class="card-body">\n <p>\n This update was mainly aimed to support exports on iOS devices again, but there were lots of attempts and none were quite what I wanted. The inital start of these changes was July, took longer than expected.\n The most interesting changes are the option to get a full export of a save and the auto-save function for the map editor.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">New file type PUC, this is a complete save including all active maps, submaps and settings.</li>\n <li class="list-group-item">Exports: Major rework, a lot happened here, hope nothing broke. This affects Maps, Saves, Tasks.</li>\n <li class="list-group-item">Settings page: Visual updates, new export settings, Service Worker (offline mode) status and compression support status.</li>\n <li class="list-group-item">Fixed class-skip-tokens not activating.</li>\n <li class="list-group-item">Added counter for the currently available class-skip tokens.</li>\n <li class="list-group-item">You can now undo a perk activation.</li>\n <li class="list-group-item">Make it easier to share large files by enabling compressed exports - and might help with some iOS export issues.</li>\n <li class="list-group-item">Share file-less using clipboard exports.</li> \n <li class="list-group-item">Fixed the visual timer problem thanks a lot @Pink_3D for the resolution (active task).</li>\n <li class="list-group-item">Fixed time output for anything that takes a month or longer (task selection).</li>\n <li class="list-group-item">Added auto-save to the map editor, might help if you forgot to manually export it. However don't rely on it - think of it as a backup in case you mess up.</li>\n <li class="list-group-item">The map editor is now a little bit better integrated into the general UI, using the same settings page.</li>\n <li class="list-group-item">The map editor supports now: taskListSize, disableBonusAttendance</li>\n <li class="list-group-item">Updated jQuery, moment.js, Bootstrap, Bootstrap Select, Bootstrap Table, Bootstrap Darkly.</li>\n <li class="list-group-item">Experimental: Make your saves prettier by using Image exports. Sadly sharing these will be quite hard because most providers will delete the save data included.</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2023-11-30.1</h5>\n <div class="card-body">\n <p>\n Looks like the old hosting option for the Map-Editor is no longer available. Now it is included on-site and should work just as it did before.\n </p>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2023-10-20</h5>\n <div class="card-body">\n <p>\n Some small bugfixes.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Fixed language navigation option for english.</li>\n <li class="list-group-item">Fixed an incorrect help description.</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2023-03-18</h5>\n <div class="card-body">\n <p>\n Some small bugfixes.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Replaced PWA images, to allow better icons.</li>\n <li class="list-group-item">Fixed a bug with save game imports for a missing active major.</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2023-02-28</h5>\n <div class="card-body">\n <p>\n Allow more advanced maps.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Removed alt texts from images.</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2023-02-09.1</h5>\n <div class="card-body">\n <p>\n More QOL updates.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">You can now join more clubs, partners and classes. Enjoy.</li>\n <li class="list-group-item">Added fix for removing finished class content.</li>\n <li class="list-group-item">Added reference verification for maps (majors, classes).</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2022-09-17.1</h5>\n <div class="card-body">\n <p>\n Some small QOL updates.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Hidden tasks should now work as expected and follow the parameter order instead of the type and then the parameter order.</li>\n <li class="list-group-item">Updated the error reporting, at least you should now see a reason which you can share for why the page is just empty.</li>\n <li class="list-group-item">Classes are now filtered for itself to stop accidental loops which crash the site.</li>\n <li class="list-group-item">Punishments should now be filtered if deleted tasks are assigned to them.</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2022-01-15</h5>\n <div class="card-body">\n <p>\n Custom timezones even if you don't travel ;)\n It is now possible to manipulate the time to make it easier for night owles to stay on schedule.\n </p>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2021-11-10</h5>\n <div class="card-body">\n <p>\n Synced Saves Service added - check the Settings.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Join the PU Discord and use the new Save-Sync service.</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2021-10-28</h5>\n <div class="card-body">\n <p>\n Small fixes and improvments.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Fixed translation engine language selection</li>\n <li class="list-group-item">Added info for collected knowledge points</li>\n <li class="list-group-item">Added function to unlock and blacklist punishments</li>\n </ul>\n </div>\n </div>\n\n <div class="card mb-3">\n <h5 class="card-header">2021-08-29</h5>\n <div class="card-body">\n <p>\n Please move your save to <a href="https://old.projectuniversity.net/">the backup instance</a> since I plan to implement a big update for the main version which will make your current saves incompatible.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">CORS check failed and required a fix.</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-10-13.2</h5>\n <div class="card-body">\n <p>\n Chinese translation is now available. In case you want to see PU in your own language feel free to contact me.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Fixed some stuff for pink theme.</li>\n <li class="list-group-item">Added setting to change the language.</li>\n <li class="list-group-item">Based on the language provided by your browser, PU will localize the website. Currently only a chinese translation is available, which was provided by Carlotta. Keep in mind this will only work for the framework and not for map content.</li>\n <li class="list-group-item">Old changelogs are now collapsed.</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-09-30.1</h5>\n <div class="card-body">\n <p>\n SubMaps are great!\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">If you already have a map active and add a new one - the new one will be added as a SubMap to the active one.</li>\n <li class="list-group-item">If you visit the page for the first time, you will be redirected after confirming the disclaimer.</li>\n <li class="list-group-item">Updated feedback info for October.</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-09-02</h5>\n <div class="card-body">\n <p>\n Roulette is now up to the map creator.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Disclaimer and Data Protection notice added.</li>\n <li class="list-group-item">Roulette is now up to the map creator</li>\n <li class="list-group-item">If you disable Easy Mode you will not be bombarded with punishments</li>\n <li class="list-group-item">A small performance improvement by skipping tour loading</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-08-05.1</h5>\n <div class="card-body">\n <p>\n Some nice new features all over the place.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Maps: Automatically lower required credits if a map doesn't provide enough content to collect 160 credits.</li>\n <li class="list-group-item">Settings: Handle missing save data - you can now disable sub-maps if you don't want them anymore without breaking the save. (probably)</li>\n <li class="list-group-item">Classes: New indicator for the progress in your classes</li>\n <li class="list-group-item">Settings: Importing using quick share should now show an error instead of silently failing</li>\n <li class="list-group-item">Mapping Tool: It's now possible to easily create new items (Save & New) and sometimes even to copy an existing item (Duplicate)</li>\n <li class="list-group-item">Mapping Tool: Shortcuts: To save current input you can now use CTRL + S (make sure to select the modal) or discard the current input ESC</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-08-02</h5>\n <div class="card-body">\n <p>\n Mapping Tool fixes.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Mapping Tool: Share option fixed</li>\n <li class="list-group-item">Mapping Tool: AutoHide is now disabled inside the Mapping Tool</li>\n <li class="list-group-item">Mapping Tool: Added warning if functionality is limited by Third-Party Cookie block</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-08-01.1</h5>\n <div class="card-body">\n <p>\n Mapping Tool extracted.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Mapping Tool: Image Hosting improved</li>\n <li class="list-group-item">Mapping Tool: Extracted for sparated access</li>\n <li class="list-group-item">Settings: Moved Mapping Tool specific settings</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-27</h5>\n <div class="card-body">\n <p>\n Let's get the map size down and allow quick-start.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Mapping Tool: Optional automatic upload of images to a image hoster to reduce the map size [EXPERIMENTAL]</li>\n <li class="list-group-item">Mapping Tool: Quick share maps (users can click / scan a QR code and will directly be able to run the map) [EXPERIMENTAL]</li>\n <li class="list-group-item">Mapping Tool: Improved direct share for tasks (users can still click on a link / scan a QR code and will be able to start the task) [EXPERIMENTAL]</li>\n <li class="list-group-item">Navigation: Fixed a potential bug for people that start a fresh game</li>\n <li class="list-group-item">Settings: Theme pink (small fixes)</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-26.1</h5>\n <div class="card-body">\n <p>\n Themes and grades.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Progress: Added grades</li>\n <li class="list-group-item">Settings: Themes (Pink theme updated)</li>\n <li class="list-group-item">Settings: New SaveGame import/export without files [EXPERIMENTAL]</li>\n <li class="list-group-item">Mapping Tool: added task export into the new table view</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-23</h5>\n <div class="card-body">\n <p>\n Graduation. Yay.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Progress: You can now graduate</li>\n <li class="list-group-item">Mapping Tool: Added click-able task reference to punishment table</li>\n <li class="list-group-item">Mapping Tool: Retain scroll position</li>\n <li class="list-group-item">Mapping Tool: Auto resize images</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-21.1</h5>\n <div class="card-body">\n <p>\n Small fixes for the Mapping Tool und quicker map check.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Map: Map import is a lot faster and works again in FF</li>\n <li class="list-group-item">Map: Show map description on import</li>\n <li class="list-group-item">Mapping Tool: Fixed punishment task selection</li>\n <li class="list-group-item">Mapping Tool: Fixed the modifier values to increase/decrease difficulty</li>\n <li class="list-group-item">Mapping Tool: Retain hidden columns settings</li>\n <li class="list-group-item">Mapping Tool: Show a warning before resetting the map </li>\n <li class="list-group-item">Mapping Tool: Full width tables</li>\n <li class="list-group-item">Mapping Tool: Updated edit buttons in table view</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-20</h5>\n <div class="card-body">\n <p>\n Lots of changes, especially in the background.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Maps: Added support for "Sub-Maps" (allows loading content of maps or map modules into a "main" map, therefore extending the existing content)</li>\n <li class="list-group-item">Progress: Show current credits (even if you have 0)</li>\n <li class="list-group-item">Tasks: Fixed counter / timer rounding error (not in sync with the task description)</li>\n <li class="list-group-item">Mapping Tool: Support to set custom help entries (and removed SU specific help entries)</li>\n <li class="list-group-item">Mapping Tool: Added support to export "partial" Maps (called modules)</li>\n <li class="list-group-item">Mapping Tool: Added option to "hide"/"disable" the orgasm roulette (in case you want to handle it using classes or don't need it)</li>\n <li class="list-group-item">Mapping Tool: Restore filter, sort, page while you edit items using the table view</li>\n <li class="list-group-item">Mapping Tool: Fixed inputs with '"'</li>\n <li class="list-group-item">Mapping Tool: Fixed sorting (was broken by direct access links)</li>\n <li class="list-group-item">Mapping Tool: Format the "Available On" dates as a readable string instead of a representative number</li>\n <li class="list-group-item">Mapping Tool: Fixed "Save" button hidden by back to top in full-screen modal</li>\n <li class="list-group-item">Mapping Tool: Changed style for new multi-selections</li>\n <li class="list-group-item">Schedule: In case you already have max. classes you will not be tempted to add another one if you already finished all classes for the day.</li>\n <li class="list-group-item">Schedule: Easy mode warning added</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-14</h5>\n <div class="card-body">\n <p>\n Small fix for the Mapping Tool (Long items were pushed to the side).\n </p>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-13</h5>\n <div class="card-body">\n <p>\n Some small changes.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Mapping Tool: uses now a better UI to select multiple items</li>\n <li class="list-group-item">Help: your health is priority #1</li>\n <li class="list-group-item">added "Update Now" info in case an update is pending</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-07-11</h5>\n <div class="card-body">\n <p>\n Various quality of life changes.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Mapping Tool: added table view to make it easier to create/edit maps</li>\n <li class="list-group-item">Mapping Tool: punishment tasks now preview the degree as well</li>\n <li class="list-group-item">Schedule: Weekend info added</li>\n <li class="list-group-item">Tasks: Added confirmation for using the "fail" button on tasks</li>\n <li class="list-group-item">Mobile Navigation: hide the "hamburger" icon if there is nothing to navigate to (no map loaded / no major chosen)</li>\n <li class="list-group-item">Mobile Navigation: auto collapses the "hamburger" menu after a successful selection</li>\n <li class="list-group-item">Other: SW will now skip waiting (immediate update)</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-06-30.3</h5>\n <div class="card-body">\n <p>\n Attempt to fix download on iOS.\n </p>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-06-21</h5>\n <div class="card-body">\n <p>\n Updated dependencies: jQuery, Bootstrap, Popper.js, Font Awesome and Moment.js.\n </p>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-06-20</h5>\n <div class="card-body">\n <p>\n Fixed some minor problems.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Counter tasks finished the complete task regardless if something else was still required.</li>\n <li class="list-group-item">Offset for images in the detail view was off</li>\n <li class="list-group-item">Manual tasks were required to be finished on the same day, can now also be done after the main task.</li>\n <li class="list-group-item">Probably fixed the check for skipped classes.</li>\n </ul>\n </div>\n </div>\n <div class="card mb-3">\n <h5 class="card-header">2020-06-16</h5>\n <div class="card-body">\n <p>\n This is the new release of PU. It ports most of the functions present in\n the original and adds a new flare at some parts.\n </p>\n <ul class="list-group list-group-flush">\n <li class="list-group-item">Allow <a href="?page=mapping">creating</a> and running custom maps</li>\n <li class="list-group-item">Option to create and share simple tasks</li>\n <li class="list-group-item">Darkmode</li>\n <li class="list-group-item">UI Tutorial</li>\n <li class="list-group-item">Tasks can have sub-tasks</li>\n <li class="list-group-item">Counter tasks (Keep track of your progress)</li>\n <li class="list-group-item">Auto hide the page from curious eyes</li>\n <li class="list-group-item">Run multiple different maps at the same time</li>\n <li class="list-group-item">Navigation works now as it should</li>\n <li class="list-group-item">Mobile support</li>\n <li class="list-group-item">Offline mode (continues working without an internet connection)</li>\n </ul>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class="row">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("home-special-thanks-header","Special thanks to")}</h4>\n <ul class="list-group list-group-flush">\n <li class="list-group-item"><a href="https://www.patreon.com/mayatrap">Maya</a> for inspiring this framework.</li>\n <li class="list-group-item">Nebulas Astra (github:@nebulas-star) and Carlotta (Twi:@Carl84534787) for providing the chinese translation.</li>\n <li class="list-group-item">Sam, BG, ClaireTorres, Lieutenant Bites and aspiringanalslut for beta testing.</li>\n </ul>\n </div>\n </div>\n <div class="row">\n <div class="col">\n <h4 class="py-3 text-warning">${e.T("home-external-sources-header","Used external sources")}</h4>\n <ul class="list-group list-group-flush">\n <li class="list-group-item"><a href="https://getbootstrap.com/">Bootstrap 4.6.2</a></li>\n <li class="list-group-item"><a href="https://bootswatch.com/darkly/">Darkly (for Bootstrap 4.6.2)</a></li>\n <li class="list-group-item"><a href="https://bootstrap-table.com/">Bootstrap Table 1.23.4</a></li>\n <li class="list-group-item"><a href="https://developer.snapappointments.com/bootstrap-select/">Bootstrap Select 1.13.18</a></li>\n <li class="list-group-item"><a href="https://jquery.com/">jQuery 3.7.1</a></li>\n <li class="list-group-item"><a href="https://popper.js.org/">Popper.js 2.4.2</a></li>\n <li class="list-group-item"><a href="https://fontawesome.com/">Font Awesome 5.13.1</a></li>\n <li class="list-group-item"><a href="https://momentjs.com/">Moment.js 2.30.1</a></li>\n <li class="list-group-item"><a href="https://shepherdjs.dev/">Shepherd.js 8.0.1</a></li>\n <li class="list-group-item"><a href="https://webpack.js.org/">webpack 4.43.0</a></li>\n <li class="list-group-item"><a href="https://larsjung.de/kjua/">kjua 0.9.0</a></li>\n <li class="list-group-item"><a href="https://github.com/cryptocoinjs/base-x">base-x 3.0.7</a></li>\n <li class="list-group-item"><a href="https://zlib.net/manual.html">zlib 1.2.11</a></li>\n </ul>\n </div>\n </div>\n <div class="row">\n <div class="col">\n <div class="card-body">\n <a href="?page=disclaimer">\n ${e.T("home-disclaimer-link",'In case you want to change your acknowledgement of the "Disclaimer and Data Protection" or want to take a look at it again, please click here.')}\n </a>\n </div>\n </div>\n </div>\n </div>`}}class q{static get expectedParameters(){return{getView:[]}}static getView(){const e=s.getInstance();return`<div class="container">\n <h3 class="text-center py-3 text-warning">${e.T("roulette-header","Orgasm Roulette")}</h3>\n <div class="container">\n <div class="row">\n <div class="col-lg-6 col-sm-6">\n <div class="img-container-overview">\n <img src="${n.extendDistPath()}img/roulette.png" class="img-fluid img-rounded img-thumbnail">\n </div>\n </div>\n <div class="col-lg-6 col-sm-6">\n <p>\n <b>${e.T("roulette-info-1","Do you want to cum?")}</b> <span class="text-success">${e.T("roulette-info-2","Try your luck!")}</span><br>\n <b>${e.T("roulette-info-3","Did you cum without asking Random-chan?")}</b> <span class="text-danger">${e.T("roulette-info-4","Get your punishment!")}</span>\n </p>\n ${B.getLastRouletteHtml()}\n <div class="card-body">\n ${B.getRollRouletteButton()}\n <div class="row">\n <div class="col">\n <button class="btn btn-outline-danger" style="width:100%" onclick="${wi.register(()=>{T.getRandomPunishmentAction()})}">${e.T("roulette-punishment-button","I came without permission!")}</button> \n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}}class B{static rollRoulette(){if(B.wasRouletteRolledToday())return!1;let e=Math.random(),t=0,a=p.rouletteData,s=void 0;for(let i in a)if(s=a[i],t+=s.probability,e<t)break;s&&(s.rollPunishment&&T.getRandomPunishment(),p.setRoulette({id:s.id,rolledOn:n.getNowUnix()}),_.inAppReload())}static lastRouletteResult(){return p.getRoulette()||!1}static wasRouletteRolledToday(){const e=B.lastRouletteResult();return!(!e||!e.rolledOn)&&!!n.isToday(e.rolledOn)}static getLastRouletteHtml(){if(!B.wasRouletteRolledToday())return"";const e=B.lastRouletteResult();if(!e||!e.hasOwnProperty("id"))return"";const t=p.rouletteData[e.id];return`<div class="card">\n <div class="card-body">\n <h5 class="card-title text-warning">${t.title}</h5>\n <p class="card-text">${t.description}</p>\n </div>\n </div>`}static getRollRouletteButton(){return B.wasRouletteRolledToday()?"":`<button class="btn btn-outline-success mr-3 mb-3" style="width:100%" onclick="${wi.register(()=>{B.rollRoulette()})}">\n ${s.getInstance().T("roulette-spin-button","Spin the roulette!")}\n </button>`}}class R{static get expectedParameters(){return{getView:[],import_task:["customTask"]}}static import_task(e){return _.replaceHistory("?page=schedule"),O.handleImport(atob(e.customTask)),R.getView()}static getView(){const e=p.getActiveMajor(),t=(p.majorsData[e],I.mandatoryClasses(e),S.getActiveClasses()),a=(S.getFinishedClasses(),n.getTodaysDay()),i=p.getActivePunishments(),o=p.getActiveTasks(),r=p.clubsData,l=p.getAssignedClubs(),d=p.partnersData,c=p.getActivePartners(),u=s.getInstance();return O.requestFeedback(),`<div class="container">\n ${o.length>0?`\n <h4 class="py-3 text-success">\n ${u.T("schedule-active-tasks-header","Active tasks")}\n </h4>\n <div class="container">\n <div class="row">\n ${R.getActiveTasks(o)}\n </div>\n </div>`:""}\n\n ${i.length>0&&i.length-o.filter(e=>"punishment"==e.object.type).length>0?`\n <h4 class="py-3 text-success">\n ${u.T("schedule-active-punishments-header","Active punishments")} <a class="text-info" data-toggle="popover" data-content="${u.T("schedule-active-punishments-info","You need to attend your punishments before you can start an exam or thesis.")}"><i class="fas fa-question-circle"></i></a>\n </h4>\n <div class="container">\n <div class="row">\n ${R.getPunishmentsHtml(i)}\n </div>\n </div>`:""}\n\n <input type="file" id="task-import" accept=".putask" style="display:none" onChange="${wi.register(()=>{O.importTask(event)})}"/>\n <h4 class="py-3 text-success">\n ${u.T("schedule-todays-classes-header","Todays classes")} <a class="header-add" onClick="$('#task-import').click()"><i class="fas fa-plus-circle"></i></a>\n </h4>\n <div class="container">\n <div class="row">\n ${R.getTodaysClasses(t,a)}\n </div>\n </div>\n\n ${l.length>0||c.length>0?`\n <h4 class="py-3 text-success">\n ${u.T("schedule-clubs-partner-header","Clubs & Partner")}\n </h4>\n <div class="container">\n <div class="row">\n ${R.getAssignedClubsAndPartners(r,l,d,c)}\n </div>\n </div>`:""}\n\n <h4 class="py-3 text-success">\n ${u.T("schedule-weekly-header","Weekly overview")}\n </h4>\n <div class="container">\n <div class="row">\n ${R.getWeekOverviewHtml(t,a)}\n </div>\n </div>\n </div>\n `}static getAssignedClubsAndPartners(e,t,a,s){let i="";for(let a of t){const t=e[a.id];i+=`\n <div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-12 pb-4"> \n <div class="card no-overflow">\n <div class="row no-gutters">\n <div class="col-md-4 img-container-left no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=club&clubId="+t.id)})}">\n <img src="${t.image}" class="card-img">\n </a>\n </div>\n <div class="col-md-8">\n <div class="card-body custom-side-card">\n <h5 class="card-title">${t.name}</h5>\n <div class="card-text">\n <ul class="list-group list-group-flush">\n ${g.getPerksHtml(t.id)}\n </ul>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>`}for(let e of s){const t=a[e.id];i+=`\n <div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-12 pb-4"> \n <div class="card no-overflow">\n <div class="row no-gutters">\n <div class="col-md-4 img-container-left no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=partner&partnerId="+t.id)})}">\n <img src="${t.image}" class="card-img">\n </a>\n </div>\n <div class="col-md-8">\n <div class="card-body custom-side-card">\n <h5 class="card-title">${t.name}</h5>\n <div class="card-text">\n <ul class="list-group list-group-flush">\n ${b.getPerksHtml(t.id)}\n </ul>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>`}return i}static getTodaysClasses(e,t){const a=p.classesData,i=s.getInstance();let o="";const r=p.getClassSkipTokens();n.getSetting("enableEasyMode")?o+=`\n <div class="col-12 pb-4 text-warning text-center"> \n ${i.T("schedule-easy-mode-info",'Currently the "Easy mode" is active. You may attend any class on any day and skip whenever you want.')}\n </div>\n `:0===t||6===t?o+=`\n <div class="col-12 pb-4 text-warning text-center"> \n ${i.T("schedule-weekend-info","During the weekend, you are free to do any classes you want. You will not be punished for skipping them today.")}\n </div>\n `:r>0&&(o+=`\n <div class="col-12 pb-4 text-warning text-center"> \n ${i.T("class-skip-token-info","You have %s class-skip tokens. Each allows you to skip one class today.",[r])}\n </div>\n `);for(const t of e){let e=a[t.id];S.wasAttendedToday(e.id)||n.hasMatchingDate(e.days)&&void 0===x.getActiveTaskForObject(e.id,"class")&&(o+=`\n <div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-12 pb-4"> \n <div class="card no-overflow">\n <div class="row no-gutters">\n <div class="col-md-4 img-container-left no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=class&classId="+e.id)})}">\n <img src="${e.image}" class="card-img">\n </a>\n </div>\n <div class="col-md-8">\n <div class="card-body custom-side-card">\n <h5 class="card-title">${e.name}</h5>\n <div class="card-text">\n <ul class="list-group list-group-flush">\n <li class="list-group-item text-info"><b>${i.T("class-detail-tasks","Available task options")}:</b></li>\n ${A.getTasksHtml(e,t.attendances||0)}\n\n ${S.isExamAvailable(e.id)?`\n <li class="list-group-item text-danger"><b>${i.T("class-detail-exams","Available exam options")}:</b></li>\n ${A.getExamsHtml(e)} `:""}\n </ul>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>`)}return o.length<=0&&S.isMaxClassesReached()?o=`<span>${i.T("schedule-no-class-left","No classes left for today. Please visit tomorrow again.")}</span>`:o.length<=0&&(o=`<span><a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=classes")})}">${i.T("schedule-no-class-left-add","No classes left for today. Maybe you want to join a new class?")}</a></span>`),o}static getActiveTasks(e){let t="";for(let a of e)t+=`<div class="col-sm-12 col-md-6 col-lg-6">${x.getTaskProgressHtml(a)}</div>`;return t}static getPunishmentsHtml(e){const t=p.punishmentsData;let a="";for(let s of e){if(void 0!==x.getActiveTaskForObject(s,"punishment"))continue;let e=t[s];a+=`\n <div class="col-xl-6 col-lg-12 col-md-12 col-sm-12 col-12 pb-4"> \n <div class="card no-overflow">\n <div class="row no-gutters">\n <div class="col-md-4 img-container-left no-overflow">\n <a onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=punishment&punishmentId="+e.id)})}">\n <img src="${e.image}" class="card-img">\n </a>\n </div>\n <div class="col-md-8">\n <div class="card-body custom-side-card">\n <h5 class="card-title">${e.name}</h5>\n <div class="card-text">${w.getPunishmentTasksHtml(e)}</div>\n </div>\n </div>\n </div>\n </div>\n </div>`}return a}static getWeekOverviewHtml(e,t){let a={1:[],2:[],3:[],4:[],5:[]};const i=s.getInstance(),o=n.weekdays;for(const t of e){const e=p.classesData[t.id],s=`<a class="onClickLink" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page=class&classId="+e.id)})}">${e.name}</a>`;for(const t of e.days)t<1||t>5||a[t].push(s)}for(const e in a)a[e].length<=0&&a[e].push(i.T("schedule-no-classes","No classes"));return`<table class="d-none d-lg-table table table-hover">\n <thead>\n <tr>\n <th scope="col" ${1==t?'class="text-warning"':""}>${o[1]}</th>\n <th scope="col" ${2==t?'class="text-warning"':""}>${o[2]}</th>\n <th scope="col" ${3==t?'class="text-warning"':""}>${o[3]}</th>\n <th scope="col" ${4==t?'class="text-warning"':""}>${o[4]}</th>\n <th scope="col" ${5==t?'class="text-warning"':""}>${o[5]}</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>${a[1].join("<br>")}</td>\n <td>${a[2].join("<br>")}</td>\n <td>${a[3].join("<br>")}</td>\n <td>${a[4].join("<br>")}</td>\n <td>${a[5].join("<br>")}</td>\n </tr>\n </tbody>\n </table>\n <table class="table table-hover d-lg-none">\n <tbody>\n <tr>\n <th scope="row" ${1==t?'class="text-warning"':""}>${o[1]}</th>\n <td>${a[1].join("<br>")}</td>\n </tr>\n <tr>\n <th scope="row" ${2==t?'class="text-warning"':""}>${o[2]}</th>\n <td>${a[2].join("<br>")}</td>\n </tr>\n <tr>\n <th scope="row" ${3==t?'class="text-warning"':""}>${o[3]}</th>\n <td>${a[3].join("<br>")}</td>\n </tr>\n <tr>\n <th scope="row" ${4==t?'class="text-warning"':""}>${o[4]}</th>\n <td>${a[4].join("<br>")}</td>\n </tr>\n <tr>\n <th scope="row" ${5==t?'class="text-warning"':""}>${o[5]}</th>\n <td>${a[5].join("<br>")}</td>\n </tr>\n </tbody>\n </table>`}static requestUserFeedback(){let e="requestUserFeedback-"+n.getRandomString();const t=s.getInstance();let a=`<div class="modal" id="${e}">\n <div class="modal-dialog" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">${t.T("schedule-feedback-header","Feedback request")}</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="${t.T("user-confirmation-close","Close")}">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <p>\n ${t.T("schedule-feedback-1",'\n Hello!\n\n I wish you a good day and hope you enjoy your "university" challenges.\n Since I personally don\'t like the extensive tracking all over the web,\n I opted to not use any on this page. This website is selfcontained and works without external scripts.\n However I still need to know if people actually use this page or if I should shut it down.\n If you are interested in providing me a small "heartbeat" - a confirmation that at least you use this page,\n please %sclick on this bitly link%s.',['<a href="https://bit.ly/3mlJGuQ">',"</a>"])}\n </p>\n <p>\n ${t.T("schedule-feedback-2","\n Additionally I created a discord especially for this website, there you can share and download maps, provide feedback and feature requests as well as get notified for the latest changes.\n %sClick here to join the discord.%s",['<a target="_blank" href="https://discord.gg/YHb2GPw">',"</a>"])}\n </p>\n <p class="text-info">\n ${t.T("schedule-feedback-3",'This request for a "heartbeat" will show up once a month.')}\n </p>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">${t.T("user-confirmation-close","Close")}</button>\n </div>\n </div>\n </div>\n </div>`;$("#confirmationContainer").append(a),$("#"+e).modal({keyboard:!1}).modal("show").on("hidden.bs.modal",(function(t){$("#"+e).modal("dispose").remove()}))}}class O{static async importTask(e){const t=s.getInstance(),a={title:t.T("schedule-task-import-failed-header","Task import failed"),text:t.T("sschedule-task-import-failed-content","During the import for the selected file an error occured. You can check the console for more information.")},i={title:t.T("schedule-file-import-compression-error-header","Compression unavailable"),text:t.T("schedule-file-import-compression-error-content","The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.")},n={title:t.T("schedule-file-import-aborted-header","Import aborted"),text:t.T("schedule-file-import-aborted-content","The file you tried to import included untrusted code. To protect you the import was aborted.")},o=await _.runImport(e,a,void 0,i,n);O.importTaskData(o)}static importTaskData(e){let t={type:"custom",name:l.T("schedule-custom-task-default-name","Custom Task"),tasks:{}};t.tasks[""+e.id]=e,t=d.applyParametersToObject(t),x.startTask(t,"tasks",""+e.id)}static handleImport(e){const t=s.getInstance();let a=function(e){if(e.error||!e.data)return void n.showToast(t.T("schedule-import-task-error-header","Task import failed"),t.T("schedule-import-task-error-content","Importing the shared task was not successful."));const a=e.data;O.importTaskData(a)}.bind(this),i=function(){c.downloadFromPastebin(e,a)}.bind(this);n.requestUserConfirmation(t.T("schedule-import-custom-task-confirmation-header","Import a custom task"),t.T("schedule-import-custom-task-confirmation-content","Are you sure that you want to import this custom task? The task will be started automatically. Make sure to only load tasks from trusted sources. Proceed at your own risk."),i)}static requestFeedback(){let e=n.getlastRequestedFeedback();moment.unix(e).isValid()||(e=n.getNow().add(3,"day").startOf("day").unix(),n.setlastRequestedFeedback(e)),e<=n.getNowUnix()&&(e=n.getNow().add(1,"month").startOf("day").unix(),R.requestUserFeedback(),n.setlastRequestedFeedback(e))}}class H{static startTour(e){let t=H.tour,a=[];if(!t.hasOwnProperty(e))return!1;const i=s.getInstance(),o=new Shepherd.Tour({tourName:""+e,useModalOverlay:!0,defaultStepOptions:{classes:"shadow-md bg-purple-dark",scrollTo:!1,buttons:[{text:i.T("tour-navigation-close","Close"),action(){return this.complete()}},{text:i.T("tour-navigation-next","Next"),action(){return this.next()}}]}});t[e]&&(a=t[e].filter(e=>$(e.attachTo.element).is(":visible"))),a.length>0&&(n.setTutorial(n.getTutorial().concat(e,";")),o.addSteps(a),o.start())}static get tour(){const e=s.getInstance();return{majors:[{id:"majors-0",title:e.T("tour-majors-0-title","Navigation"),text:e.T("tour-majors-0-text","This is the global navigation. Here you can switch to different areas of the game."),attachTo:{element:"body nav.navbar",on:"bottom"}},{id:"majors-1",title:e.T("tour-majors-1-title","Mobile navigation"),text:e.T("tour-majors-1-text","On mobile not all items are always visible, use this button to show the available entries."),attachTo:{element:"nav .navbar-toggler",on:"bottom"}},{id:"majors-2",title:e.T("tour-majors-2-title","Major cards"),text:e.T("tour-majors-2-text","Each major available will be displayed as a card to allow interacting with it."),attachTo:{element:".min-card-container",on:"bottom"}},{id:"majors-3",title:e.T("tour-majors-3-title","Details"),text:e.T("tour-majors-3-text","You can access a detail view by clicking on the image of the major."),attachTo:{element:".min-card-container .img-container-overview",on:"bottom"}},{id:"majors-4",title:e.T("tour-majors-4-title","Card actions"),text:e.T("tour-majors-4-text","The major cards can have multiple actions related to it."),attachTo:{element:".min-card-container .img-button-container-left-corner",on:"bottom"}},{id:"majors-5",title:e.T("tour-majors-5-title","Display information"),text:e.T("tour-majors-5-text","This information button will allow you quick access to some detail information related to the major."),attachTo:{element:".min-card-container .img-button-container-left-corner .text-white",on:"bottom"}},{id:"majors-6",title:e.T("tour-majors-6-title","Card information"),text:e.T("tour-majors-6-text","The bottom of the card will give a small description for the major."),attachTo:{element:".min-card-container .card-body",on:"bottom"}}],classes:[{id:"classes-0",title:e.T("tour-classes-0-title","Tier navigation"),text:e.T("tour-classes-0-text","Sometimes there are multiple tiers, you can navigate to a different one using these items."),attachTo:{element:"#action-navigation",on:"right"}},{id:"classes-1",title:e.T("tour-classes-1-title","Tier navigation"),text:e.T("tour-classes-1-text","Sometimes there are multiple tiers, you can navigate to a different one using these items or swipe to the side."),attachTo:{element:"#customCarousel .carousel-indicators",on:"bottom"}},{id:"classes-2",title:e.T("tour-classes-2-title","Card"),text:e.T("tour-classes-2-text","Each class available will be displayed as a card to allow interacting with it."),attachTo:{element:".min-card-container",on:"bottom"}},{id:"classes-3",title:e.T("tour-classes-3-title","Image"),text:e.T("tour-classes-3-text","You can access a detail view by clicking on the image of the class."),attachTo:{element:".min-card-container .img-container-overview",on:"bottom"}},{id:"classes-4",title:e.T("tour-classes-4-title","Card actions"),text:e.T("tour-classes-4-text","The class cards can have multiple actions related to it."),attachTo:{element:".min-card-container .img-button-container-left-corner",on:"bottom"}},{id:"classes-5",title:e.T("tour-classes-5-title","Display information"),text:e.T("tour-classes-5-text","This information button will allow you quick access to some detail information related to the class."),attachTo:{element:".min-card-container .img-button-container-left-corner .text-white",on:"bottom"}},{id:"classes-6",title:e.T("tour-classes-6-title","Card information"),text:e.T("tour-classes-6-text","These information will give a small description for the class."),attachTo:{element:".min-card-container .card-body",on:"bottom"}}],mapping:[{id:"mapping-0",title:"Global actions",text:"Use the actions to export and import the complete map.",attachTo:{element:"#action-navigation nav ul.list-group",on:"bottom"}},{id:"mapping-0-info",title:"Hide actions",text:'If you need additional space (e.g. on mobile) you can click on the header "Actions" to collapse them.',attachTo:{element:"#action-navigation ul li.list-group-item",on:"bottom"}},{id:"mapping-0-desktop",title:"Navigation",text:"Use the navigation menu to switch to different sub menues.",attachTo:{element:"#action-navigation nav ul.list-group:nth-child(3)",on:"right"}},{id:"mapping-0-mobile",title:"Mobile navigation",text:"Use these buttons or swipe to switch between the different sub menues.",attachTo:{element:"#mappingCarousel .carousel-indicators",on:"bottom"}},{id:"mapping-1",title:"Save map",text:"Make sure to save your progress regularly!",attachTo:{element:"#action-navigation nav ul div.btn-group",on:"bottom"}},{id:"mapping-2",title:"Export finished map",text:"Once you are done export a playable version.",attachTo:{element:"#action-navigation nav ul div.btn-group",on:"bottom"}},{id:"mapping-3",title:"Save your changes",text:"Each change needs to be saved. If you don't want to keep the changes use the 'Close' button instead.",attachTo:{element:"#generalCardContainer .btn-primary",on:"bottom"}},{id:"mapping-4",title:"Add new items",text:"To add new items you will find such a 'plus' button next to the corresponding items, use it to open a new sub menu to create another one. If you create a new item related to something else it will automatically be linked.",attachTo:{element:".header-add",on:"bottom"}},{id:"mapping-5",title:"Created items",text:"You will find created items below the corresponding header.",attachTo:{element:"#helpCardContainer",on:"bottom"}}]}}}class F{static get expectedParameters(){return{getView:["majorId"]}}static getView(e){const t=e.majorId,a=I.getGrade(t),i=p.majorsData[t],n=s.getInstance();return p.getFinishedMajors().includes(t)||_.navigateBack(),`<div class="container">\n <h4 class="py-3 text-success">\n ${n.T("graduated-certificate-header","Certificate for %s",[i.name])}\n </h4>\n <div class="container">\n <div class="row">\n <div class="col-lg-6 col-sm-6">\n <div class="img-container-overview">\n <a onClick="${wi.register(()=>{_.navigateBack()})}">\n <img src="${i.image}" class="img-fluid img-rounded img-thumbnail">\n </a>\n <div class="alert-info text-white text-center pt-1 pb-1 img-locked-text img-locked-overview-text">${n.T("major-status-grade","Grade")} <b>${a}</b></div>\n </div>\n </div>\n <div class="col-lg-6 col-sm-6 pb-3">\n <h5 class="text-info">${n.T("graduated-congratulations-header","Congratulations for graduating %s.",[i.name])}</h5>\n <p>${n.T("graduated-congratulations-1","Many take on this journey but only few manage to finish it successfully. I am glad to see that you are one of those few!")}</p>\n <p>${n.T("graduated-congratulations-2","There is still a lot todo and I am sure you didn't have enough yet. Once you are ready for the next challenge start one of the other majors or import a new map.")}</p>\n <p>${n.T("graduated-congratulations-3","Since you are now very experienced you may also consider to create your own classes for fellow students to enrich their university life.")}</p>\n <div class="">\n ${F.getGradeTable(t)}\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static getGradeTable(e){let t=0,a=0,i="";const r=s.getInstance();for(const s of I.mandatoryClasses(e)){const e=p.classesData[s],r=S.getAssignedClass(s),l=o.maxAttendances[e.tier];i+=`\n <tr>\n <td>${e.name}</td>\n <td>${r.attendances}/${l}</td>\n <td>${n.getGrade(r.attendances,l)}</td>\n </tr>\n `,t+=r.attendances,a+=l}return`\n <table class="table table-hover">\n <thead>\n <tr>\n <th scope="col">${r.T("graduated-table-class","Class")}</th>\n <th scope="col">${r.T("graduated-table-knowledge-points","Knowledge Points")}</th>\n <th scope="col">${r.T("graduated-table-grade","Grade")}</th>\n </tr>\n </thead>\n <tbody>\n ${i}\n </tbody>\n <tfoot>\n <tr class="text-info">\n <td></td>\n <td>${t}/${a}</td>\n <td>${n.getGrade(t,a)}</td>\n </tr>\n </tfoot>\n </table>\n `}}class U{static get expectedParameters(){return{getView:[]}}static getView(){const e=s.getInstance();return`<div class="container">\n <h3 class="text-center py-3 text-warning">${e.T("disclaimer-header","Disclaimer and Data Protection")}</h3>\n <div class="container pb-3">\n <div class="row">\n <div class="col">\n <p>\n ${e.T("disclaimer-block-1","Websites that can be accessed publicly are required to fulfill some legal requirements. \n Which in turn makes us both suffer through this short introduction what this website does and so on.\n You can find a link to this page at the bottom of the home page.\n If you are okay with the following you may continue using this website and agree using the button at the bottom. \n If you disagree we will clean up all the stored data and send you on your way.")}\n </p>\n\n <h3>${e.T("disclaimer-overview-header","Overview")}</h3>\n <ul>\n <li>${e.T("disclaimer-overview-1",'In the following the term "User" is used to reference you and "We" for us the provider.')}</li>\n <li>${e.T("disclaimer-overview-2","This website is a free tool you may use as-is as long as we want to provide it. You might come back here tomorrow and everything is gone.")}</li>\n <li>${e.T("disclaimer-overview-3","This website doesn't use any back-end besides a simple Web-Service used to provide the website content.")}</li>\n <li>${e.T("disclaimer-overview-4","This website runs completely on your device. If you lose it or clear the device/website storage everything will be gone.")}</li>\n <li>${e.T("disclaimer-overview-5","This website is only a framework, therefore requires user generated content which is manually created and provided by the users. We don't know what anybody uses this service for. \n You can compare this to a local text editor - you can create or open text files, while everything stays on your device. You may use this tool to access files created by other users.")}</li>\n <li>${e.T("disclaimer-overview-6","The source code required to run this website is served completely from this domain. There are some links which leave this page, we have no influence on whatever they are doing.")}</li>\n <li>${e.T("disclaimer-overview-7","User generated content may be malicious, we do attempt to protect you from obvious attacks - but in the end you are responsible for the external content you load.")}</li>\n </ul>\n\n <h3>${e.T("disclaimer-inner-header","Disclaimer")}</h3>\n <p>\n ${e.T("disclaimer-inner","Using this website is free of charge, there are no ad-services or tracking-services active. There is no aim to make any money using this framework.\n\n Since this website uses user generated content and doesn't store any information about the users, the provider is unable to audit any \n user generated content you are loading. You will therefore need to take full reponsibiltiy for any user generated content you are adding.\n Additionally the provider is not responsible for the content of linked pages, if there is a legal requirement to remove any reference from\n the framework it will be complied to.")}\n </p>\n\n <h3>${e.T("disclaimer-data-protection-header","Data protection notice")}</h3>\n <p>\n ${e.T("disclaimer-data-protection",'No data is collected and stored outside of the user devices. Progress, settings, content and feedback marker are stored on your local device. \n User generated content stays on the device of the user. If you decide to provide a "heartbeat" using the feedback option you are leaving \n this website and the Bitly Privacy Policy applies. The feedback is optional and Bitly-Links will be highlighted.')}\n <a href="https://bitly.com/pages/privacy">${e.T("disclaimer-bitly-link","Click here to view the Bitly Privacy Policy on bitly.com")}</a>. \n </p>\n\n <h3>${e.T("disclaimer-stored-data-header","Stored data")}</h3>\n <p>\n ${e.T("disclaimer-stored-data-description","Multiple technologies are used to store your progress, settings and added user generated content.")}\n <ul>\n <li>${e.T("disclaimer-stored-data-local","Local storage: Settings, Delayed notifications, Last feedback information request, Accepted disclaimer, Already displayed tutorials")}</li>\n <li>${e.T("disclaimer-stored-data-indexeddb","IndexedDB: User generated content, Current progress")}</li>\n <li>${e.T("disclaimer-stored-data-cache","Cache: The website framework data to allow offline mode")}</li>\n </ul>\n </p>\n </div>\n </div>\n <div class="row">\n <div class="col">\n <button class="btn btn-outline-danger w-100" onclick="${wi.register(()=>{L.rejectedDisclaimer()})}">${e.T("disclaimer-accept-button","I don't accept")}</button> \n </div>\n <div class="col">\n <button class="btn btn-outline-success w-100" onclick="${wi.register(()=>{L.acceptedDisclaimer()})}">${e.T("disclaimer-reject-button","I understand and accept")}</button> \n </div>\n </div> \n </div>\n </div>\n `}}class L{static acceptedDisclaimer(){n.setAcceptedDisclaimer();const e=new URLSearchParams(window.location.search.replace(/#.*?$/,""));if(e.get("redirect")){let t=atob(decodeURIComponent(e.get("redirect")));if(t.startsWith("?"))return void _.navigate(""+t)}_.navigate("?page=home")}static rejectedDisclaimer(){localStorage.clear(),sessionStorage.clear(),"serviceWorker"in navigator&&(caches.keys().then((function(e){for(let t of e)caches.delete(t)})),navigator.serviceWorker.getRegistrations().then((function(e){for(let t of e)t.unregister()}))),r.getInstance().drop(function(){_.navigate("about:blank")}.bind(this))}}let V,G=0;class J{static get expectedParameters(){return{getView:[]}}static getView(e){return Y.reset(),Y.registerWarning(),Y.autoSaveRestore(),document.querySelector("html").addEventListener("mappingRefreshed",(function(e){$("#majorsTable").bootstrapTable(),$("#classesTable").bootstrapTable(),$("#tasksTable").bootstrapTable(),$("#punishmentsTable").bootstrapTable(),$("#clubsTable").bootstrapTable(),$("#partnersTable").bootstrapTable(),$("#modifiersTable").bootstrapTable(),$("#tagsTable").bootstrapTable(),$("#helpTable").bootstrapTable(),$("#rouletteTable").bootstrapTable()})),document.querySelector("html").addEventListener("navigationFinished",(function(e){Y.refreshView()})),`<div class="container-fluid pb-3" id="mapping-container">\n <div id="modalContainer"></div>\n <div class="container-fluid">\n <div class="row">\n <div id="action-navigation" class="col-lg-2 col-md-4 col-sm-12">\n <nav class="nav flex-column">\n <input type="file" id="map-import" accept=".pumap" style="display:none" onChange="${wi.register(()=>{Y.import(event)})}"/>\n <ul class="list-group pt-3">\n <li class="list-group-item active" data-toggle="collapse" href=".collapseAction" aria-expanded="true">Actions</li>\n <a class="list-group-item list-group-item-action collapse show collapseAction onClickLink" onClick="$('#map-import').click()"><i class="fas fa-file-import"></i> Import editable map</a>\n <div class="btn-group list-group-item" role="group">\n <a id="btnExportGroup" class="dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <i class="fas fa-file-export"></i> Export\n </a>\n <div class="dropdown-menu" aria-labelledby="btnExportGroup">\n <a class="dropdown-item onClickLink" onClick="${wi.register(()=>{Y.export()})}"><i class="fas fa-file-export"></i> Export editable map</a>\n <a class="dropdown-item onClickLink" onClick="${wi.register(()=>{Y.exportPlayable(Y.getPlayable())})}"><i class="fas fa-file-export"></i> Export playable map</a>\n <a class="dropdown-item onClickLink" onClick="${wi.register(()=>{Y.openCustomExport(Y.exportPlayable)})}"><i class="fas fa-file-export"></i> Export playable module</a>\n </div>\n </div>\n <a class="list-group-item list-group-item-action collapse show collapseAction onClickLink" onClick="${wi.register(()=>{Y.resetButton()})}"><i class="fas fa-undo"></i> Reset map</a>\n <a class="list-group-item list-group-item-action collapse show collapseAction onClickLink ${window.self!==window.top?"":"d-none"}" onClick="${wi.register(()=>{Y.gotoFullPage()})}"><i class="fas fa-share-square"></i> Goto Mapping Tool</a>\n </ul>\n <ul class="list-group pt-3 d-none d-lg-block">\n <li class="list-group-item active" aria-expanded="true">Navigation</li>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="0"><i class="fas fa-chevron-circle-right"></i> General</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="1"><i class="fas fa-chevron-circle-right"></i> Majors</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="2"><i class="fas fa-chevron-circle-right"></i> Classes</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="3"><i class="fas fa-chevron-circle-right"></i> Tasks</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="4"><i class="fas fa-chevron-circle-right"></i> Punishments</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="5"><i class="fas fa-chevron-circle-right"></i> Clubs</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="6"><i class="fas fa-chevron-circle-right"></i> Partners</a>\n <a class="list-group-item list-group-item-action onClickLink" data-target="#mappingCarousel" data-slide-to="7"><i class="fas fa-chevron-circle-right"></i> Modifiers</a>\n </ul>\n </nav>\n </div>\n <div class="col">\n <div id="mappingCarousel" class="carousel slide" data-interval="false" data-ride="carousel">\n <ol class="carousel-indicators top-carousel d-lg-none">\n <li data-target="#mappingCarousel" data-slide-to="0" class="active"></li>\n <li data-target="#mappingCarousel" data-slide-to="1"></li>\n <li data-target="#mappingCarousel" data-slide-to="2"></li>\n <li data-target="#mappingCarousel" data-slide-to="3"></li>\n <li data-target="#mappingCarousel" data-slide-to="4"></li>\n <li data-target="#mappingCarousel" data-slide-to="5"></li>\n <li data-target="#mappingCarousel" data-slide-to="6"></li>\n <li data-target="#mappingCarousel" data-slide-to="7"></li>\n </ol>\n <div class="carousel-inner">\n <div class="carousel-item active pt-3">\n <h3>General</h3>\n <div id="generalCardContainer" class="container-fluid"> \n ${J.getGeneralView()}\n </div>\n <h3 class="pt-4">Roulette <a class="header-add" onClick="${wi.register(()=>{Y.addRoulette()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Add custom roulette options.</small>\n <div id="rouletteCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n <h3 class="pt-4">Help <a class="header-add" onClick="${wi.register(()=>{Y.addHelp()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Help texts are used to guide the player inside your map. You could define rules, tutorials or guides.</small>\n <div id="helpCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n <h3 class="pt-4">Tags <a class="header-add" onClick="${wi.register(()=>{Y.addTag()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Tags are used to apply modifiers to tasks. If a task has at least one matching tag with an active modifier it is applied.</small>\n <div id="tagsCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n <div class="carousel-item pt-3">\n <h3>Majors <a class="header-add" onClick="${wi.register(()=>{Y.addMajor()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Majors direct the user on the path they choose. Based on the major the required classes are defined.</small>\n <div id="majorsCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n <div class="carousel-item pt-3">\n <h3>Classes <a class="header-add" onClick="${wi.register(()=>{Y.addClass()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Classes should ramp in difficulty by providing increasingly difficult tasks to the user.</small>\n <div id="classesCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n <div class="carousel-item pt-3">\n <h3>Tasks <a class="header-add" onClick="${wi.register(()=>{Y.addTask()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Tasks defined requirements that the player will have to follow based on the classes active.</small>\n <div id="tasksCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div> \n <div class="carousel-item pt-3">\n <h3>Punishments <a class="header-add" onClick="${wi.register(()=>{Y.addPunishment()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">If the player misses classes or fails tasks punishments will be rolled. If required manual rolling is possible as well.</small>\n <div id="punishmentsCardContainer" class="container-fluid carouselCardPadding">\n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n <div class="carousel-item pt-3">\n <h3>Clubs <a class="header-add" onClick="${wi.register(()=>{Y.addClub()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Clubs allow to enable modifiers that make matching tasks easier (shorter / less actions required) but add an additional challenge.</small>\n <div id="clubsCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n <div class="carousel-item pt-3">\n <h3>Partners <a class="header-add" onClick="${wi.register(()=>{Y.addPartner()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Partners allow to make tasks harder by providing modifiers that increase requirements but provide special perks. (Especially useful for people that are not new to the theme and want to progress faster.)</small>\n <div id="partnersCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n <div class="carousel-item pt-3">\n <h3>Modifiers <a class="header-add" onClick="${wi.register(()=>{Y.addModifier()})}"><i class="fas fa-plus-circle"></i></a></h3>\n <small class="text-muted">Modifiers can be added to clubs or partners and activated as the player wishes.</small>\n <div id="modifiersCardContainer" class="container-fluid carouselCardPadding"> \n \x3c!-- will be auto-filled --\x3e\n </div>\n </div>\n </div> \n </div>\n </div>\n </div>\n </div>\n </div>`}static generateImageSelection(e){return`\n <div class="image-selection-container pt-3 pb-3 mt-3 mb-3 bg-secondary container-fluid">\n <div class="row">\n <div class="col-md-auto d-flex justify-content-center">\n <img class="image-preview" src="${e.image||""}" alt="Provided image" data-optimized-url="${e.imageUrl||""}" onerror="if (!this.src.endsWith('unknown.jpg')) { this.src='${n.extendDistPath()}img/unknown.jpg'; }"/>\n </div>\n <div class="col-sm image-selection-content-right">\n <div class="w-100">\n <div class="image-selection-info text-info p-2 text-center">\n Optimal size is 600 x 800 px.\n </div>\n\n <input type="file" class="form-control-file image-selection" accept="image/jpg" style="display:none">\n <button class="btn btn-outline-light btn-block image-selection-btn mb-2">Select an image</button>\n <div class="image-selection-upload-link ${e.imageUrl?"":"d-none"}">\n <input class="form-control image-upload-input" type="text" placeholder="No uploaded version available." readonly value="${e.imageUrl||""}" onClick="this.select(); document.execCommand('copy');">\n <small class="text-muted">Click on the link above to automatically copy it into your clipboard.</small>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateRouletteTableEntry(e){return`\n <tr id="${n.getRandomString()}">\n <td>${e.id}</td>\n <td>${e.title}</td> \n <td>${e.description}</td> \n <td>${e.probability}</td> \n <td>${e.rollPunishment}</td> \n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editRoulette(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeRoulette(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateHelpTableEntry(e){return`\n <tr id="${n.getRandomString()}">\n <td>${e.id}</td>\n <td>${e.title}</td> \n <td>${e.text}</td> \n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editHelp(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeHelp(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateTagTableEntry(e){return`\n <tr id="${n.getRandomString()}">\n <td>${e}</td> \n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editTag(e)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeTag(e)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateRouletteCard(e){return`<div class="col-sm-12 pb-5">\n <div class="card h-100">\n <div class="card-body">\n <h5 class="card-title">Roulette ${e.id}: ${e.title}</h5>\n <p class="card-text">${e.description}</p>\n </div>\n <div class="card-body">\n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.editRoulette(e.id)})}"><i class="fas fa-edit"></i> Edit</a>\n </div>\n </div>\n </div>`}static generateHelpCard(e){return`<div class="col-sm-12 pb-5">\n <div class="card h-100">\n <div class="card-body">\n <h5 class="card-title">Help ${e.id}: ${e.title}</h5>\n <p class="card-text">${e.text}</p>\n </div>\n <div class="card-body">\n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.editHelp(e.id)})}"><i class="fas fa-edit"></i> Edit</a>\n </div>\n </div>\n </div>`}static generateTagCard(e){return`<div class="col-sm-2 pb-5">\n <div class="card h-100">\n <div class="card-body">\n <h5 class="card-title">${e}</h5>\n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.editTag(""+e)})}"><i class="fas fa-edit"></i> Edit</a>\n </div>\n </div>\n </div>`}static generateParamCard(e){return`<div class="min-card-container col-lg-3 col-sm-3 pb-5">\n <div class="card h-100 no-overflow">\n <div class="card-body">\n <h5 class="card-title">$param${e.id}</h5>\n <p class="card-text">${e.value} ${"punishments"==e.valueType?e.punTier:""} ${e.valueType} ${e.description.length>0?`<small class="text-muted">(${e.description})</small>`:""}</p>\n </div> \n <ul class="list-group list-group-flush">\n ${e.applyMultiplier?'<li class="list-group-item"><b><i class="fas fa-check-circle"></i> Apply multiplier<b/></li>':""}\n ${e.spawnTimer?'<li class="list-group-item"><b><i class="fas fa-check-circle"></i> Spawn timer</b></li>':""}\n ${e.startTimerAutomatically?'<li class="list-group-item"><b><i class="fas fa-check-circle"></i> Start timer automatically</b></li>':""}\n ${e.provideCounter?'<li class="list-group-item"><b><i class="fas fa-check-circle"></i> Provide counter</b></li>':""}\n ${e.punishTime?'<li class="list-group-item"><b><i class="fas fa-check-circle"></i> Provide punish option</b></li>':""}\n ${e.hiddenTask?'<li class="list-group-item"><b><i class="fas fa-check-circle"></i> Delayed sub-task</b></li>':""}\n </ul>\n <div class="card-body"> \n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.editParam(e.id)})}"><i class="fas fa-edit"></i> Edit</a>\n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.insertParam(e.id,e.taskId)})}"><i class="fas fa-plus-circle"></i> Insert</a>\n </div>\n </div></div>`}static generateTaskTableEntry(e){let t=Y.applyParamsToString(e.id,e.description),a="",s=n.getRandomString();if(void 0!==e.tags&&e.tags.length>0)for(const t of e.tags)a+=`<span class="badge badge-pill badge-light mr-1">${t}</span>`;return`\n <tr id="${s}">\n <td>${e.id}</td>\n <td><a onClick="${wi.register(()=>{Y.editTask(e.id)})}" class="customLink">Task ${e.id}</a></td> \n <td>${t}</td>\n <td>${a}</td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editTask(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeTask(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.directExportTask(e.id)})}"><i class="fas fa-file-download"></i></button>\n </div>\n </td>\n </tr>\n `}static generateTaskCard(e){let t=Y.applyParamsToString(e.id,e.description),a="";if(void 0!==e.tags&&e.tags.length>0)for(const t of e.tags)a+=`<span class="badge badge-pill badge-light mr-1">${t}</span>`;return`<div class="min-card-container col-lg-3 col-sm-3 pb-5">\n <div class="card h-100 no-overflow">\n <div class="card-body">\n <h5 class="card-title">Task ${e.id}</h5>\n <p class="card-text">${t}</p>\n ${a.length>0?`<p class="card-text">${a}</p>`:""}\n </div>\n <div class="card-body"> \n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.editTask(e.id)})}"><i class="fas fa-edit"></i> Edit Task</a>\n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.directExportTask(e.id)})}"><i class="fas fa-edit"></i> Export Task</a>\n </div>\n </div></div>`}static generateClassTableEntry(e){let t="",a="",s="",i=n.getRandomString();for(const s of V.tasks.sort((e,t)=>e.id-t.id))e.tasks.includes(s.id)&&(t+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editTask(s.id)})}" class="customLink">Task ${s.id}:</a><br>\n ${Y.applyParamsToString(s.id,s.description)}\n </li>`),e.exams.includes(s.id)&&(a+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editTask(s.id)})}" class="customLink">Task ${s.id}:</a><br>\n ${Y.applyParamsToString(s.id,s.description)}\n </li>`);for(const t of V.classes.sort((e,t)=>e.id-t.id))e.prerequisites.includes(t.id)&&(s+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editClass(t.id)})}" class="customLink">Class ${t.id}:</a><br>\n ${t.title}\n </li>`);return`\n <tr id="${i}">\n <td>${e.id}</td>\n <td>${e.tier}</td>\n <td>${e.title}</td>\n <td>${e.subtitle}</td>\n <td>${e.comment}</td>\n <td>${e.description}</td>\n <td>${e.days.map(e=>n.weekdays[e]).join(", ")}</td>\n <td class="p-0"><ul class="list-group list-group-flush">${s}</ul></td>\n <td class="p-0"><ul class="list-group list-group-flush">${t}</ul></td>\n <td class="p-0"><ul class="list-group list-group-flush">${a}</ul></td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editClass(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeClass(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateClassCard(e){let t="",a="",s="",i=n.getRandomString();for(const s of V.tasks.sort((e,t)=>e.id-t.id))e.tasks.includes(s.id)&&(t+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-info"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Task ${s.id} <small class="text-muted">${Y.applyParamsToString(s.id,s.description)}</small></div>\n </div> \n </li>`),e.exams.includes(s.id)&&(a+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Task ${s.id} <small class="text-muted">${Y.applyParamsToString(s.id,s.description)}</small></div>\n </div> \n </li>`);for(const t of V.classes.sort((e,t)=>e.id-t.id))e.prerequisites.includes(t.id)&&(s+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-warning"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Class ${t.id} <small class="text-muted">${t.title}</small></div>\n </div> \n </li>`);return`<div class="min-card-container col-lg-2 col-md-3 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <img src="${void 0===e.image||e.image.length<=0?n.extendDistPath()+"img/unknown.jpg":e.image}" class="card-img-top">\n \n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("classesCard-"+i)})}" class="btn btn-dark text-white"><i class="fas fa-info-circle"></i></a>`,`<a onClick="${wi.register(()=>{Y.editClass(e.id)})}" class="btn btn-dark text-info"><i class="fas fa-edit"></i> Edit</a>`])}\n\n <div id="classesCard-${i}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n <ul class="list-group list-group-flush">\n ${s.length>0?'<li class="list-group-item text-warning"><b>Prerequisites:</b></li>\n '+s:""}\n ${a.length>0?'<li class="list-group-item text-danger"><b>Available Exam Tasks:</b></li>\n '+a:""} \n ${t.length>0?'<li class="list-group-item text-info"><b>Available Tasks:</b></li>\n '+t:""}\n </ul>\n </div>\n </div>\n </div>\n\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">\n Class ${e.id}: ${e.title} \n <small class="text-muted">${e.subtitle}</small>\n ${void 0!==e.comment?`<small class="text-muted">${e.comment}</small>`:""}\n </h5>\n <p class="card-text">${e.description}</p>\n </div> \n </div>\n </div>`}static generateMajorTableEntry(e){let t="",a="",s=n.getRandomString();for(const a of V.tasks.sort((e,t)=>e.id-t.id))e.exams.includes(a.id)&&(t+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editTask(a.id)})}" class="customLink">Task ${a.id}:</a><br>\n ${Y.applyParamsToString(a.id,a.description)}\n </li>`);for(const t of V.classes.sort((e,t)=>e.id-t.id))e.prerequisites.includes(t.id)&&(a+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editClass(t.id)})}" class="customLink">Class ${t.id}:</a><br>\n ${t.title}\n </li>`);return`\n <tr id="${s}">\n <td>${e.id}</td>\n <td>${e.title}</td>\n <td>${e.subtitle}</td>\n <td>${e.description}</td>\n <td class="p-0"><ul class="list-group list-group-flush">${a}</ul></td>\n <td class="p-0"><ul class="list-group list-group-flush">${t}</ul></td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editMajor(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeMajor(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateMajorCard(e){let t="",a="",s=n.getRandomString();for(const a of V.tasks.sort((e,t)=>e.id-t.id))e.exams.includes(a.id)&&(t+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-danger"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Task ${a.id} <small class="text-muted">${Y.applyParamsToString(a.id,a.description)}</small></div>\n </div> \n </li>`);for(const t of V.classes.sort((e,t)=>e.id-t.id))e.prerequisites.includes(t.id)&&(a+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-warning"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Class ${t.id} <small class="text-muted">${t.title}</small></div>\n </div> \n </li>`);return`<div class="min-card-container col-lg-2 col-md-3 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <img src="${void 0===e.image||e.image.length<=0?n.extendDistPath()+"img/unknown.jpg":e.image}" class="card-img-top">\n \n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("classesCard-"+s)})}" class="btn btn-dark text-white"><i class="fas fa-info-circle"></i></a>`,`<a onClick="${wi.register(()=>{Y.editMajor(e.id)})}" class="btn btn-dark text-info"><i class="fas fa-edit"></i> Edit</a>`])}\n\n <div id="classesCard-${s}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n <ul class="list-group list-group-flush">\n ${a.length>0?'<li class="list-group-item text-warning"><b>Prerequisites:</b></li>\n '+a:""}\n ${t.length>0?'<li class="list-group-item text-danger"><b>Available Exam Tasks:</b></li>\n '+t:""}\n </ul>\n </div>\n </div>\n </div>\n\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">\n Major ${e.id}: ${e.title}\n <small class="text-muted">${e.subtitle}</small>\n </h5>\n <p class="card-text">${e.description}</p>\n </div> \n </div>\n </div>`}static generatePunishmentTableEntry(e){let t=V.tasks.filter(t=>t.id===e.punishment)[0],a=Y.applyParamsToString(t.id,t.description);return`\n <tr id="${n.getRandomString()}">\n <td>${e.id}</td>\n <td>${e.tier}</td>\n <td>${e.title}</td>\n <td><a onClick="${wi.register(()=>{Y.editTask(t.id)})}" class="customLink">Task ${t.id}:</a><br> ${a}</td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editPunishment(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removePunishment(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generatePunishmentCard(e){let t=V.tasks.filter(t=>t.id===e.punishment)[0],a=Y.applyParamsToString(t.id,t.description);return`<div class="min-card-container col-lg-2 col-md-3 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <img src="${void 0===e.image||e.image.length<=0?n.extendDistPath()+"img/unknown.jpg":e.image}" class="card-img-top"> \n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{Y.editPunishment(e.id)})}" class="btn btn-dark text-info"><i class="fas fa-edit"></i> Edit</a>`])}\n </div>\n\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">\n Punishment ${e.id}: ${e.title}\n <small class="text-muted">${e.tier}</small>\n </h5>\n <p class="card-text">${a}</p>\n </div> \n </div>\n </div>`}static generatePartnerTableEntry(e){let t="",a=n.getRandomString();for(const a of V.modifiers.sort((e,t)=>e.id-t.id))e.modifiers.includes(a.id)&&(t+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editModifier(a.id)})}" class="customLink">Modifier ${a.id}:</a><br>\n ${Y.getModifierText(a)}\n </li>`);return`\n <tr id="${a}">\n <td>${e.id}</td>\n <td>${e.name}</td>\n <td>${e.description}</td>\n <td class="p-0"><ul class="list-group list-group-flush">${t}</ul></td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editPartner(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removePartner(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generatePartnerCard(e){let t="",a=n.getRandomString();for(const a of V.modifiers.sort((e,t)=>e.id-t.id))e.modifiers.includes(a.id)&&(t+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-warning"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Modifier ${a.id} <small class="text-muted">${Y.getModifierText(a)}</small></div>\n </div> \n </li>`);return`<div class="min-card-container col-lg-2 col-md-3 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <img src="${void 0===e.image||e.image.length<=0?n.extendDistPath()+"img/unknown.jpg":e.image}" class="card-img-top">\n \n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("partnerCard-"+a)})}" class="btn btn-dark text-white"><i class="fas fa-info-circle"></i></a>`,`<a onClick="${wi.register(()=>{Y.editPartner(e.id)})}" class="btn btn-dark text-info"><i class="fas fa-edit"></i> Edit</a>`])}\n\n <div id="partnerCard-${a}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n <ul class="list-group list-group-flush">\n ${t.length>0?'<li class="list-group-item text-info"><b>Modifiers:</b></li>\n '+t:""}\n </ul>\n </div>\n </div>\n </div>\n\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">\n Partner ${e.id}: ${e.name}\n </h5>\n <p class="card-text">${e.description}</p>\n </div>\n </div>\n </div>`}static generateClubTableEntry(e){let t="",a=n.getRandomString();for(const a of V.modifiers.sort((e,t)=>e.id-t.id))e.modifiers.includes(a.id)&&(t+=`<li class="list-group-item" style="background-color: transparent;">\n <a onClick="${wi.register(()=>{Y.editModifier(a.id)})}" class="customLink">Modifier ${a.id}:</a><br>\n ${Y.getModifierText(a)}\n </li>`);return`\n <tr id="${a}">\n <td>${e.id}</td>\n <td>${e.tier}</td>\n <td>${e.name}</td>\n <td>${e.comment}</td>\n <td>${e.description}</td>\n <td class="p-0"><ul class="list-group list-group-flush">${t}</ul></td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editClub(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeClub(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateClubCard(e){let t="",a=n.getRandomString();for(const a of V.modifiers.sort((e,t)=>e.id-t.id))e.modifiers.includes(a.id)&&(t+=`<li class="list-group-item text-muted">\n <div class="row pl-2">\n <div class="col" style="max-width: 10px; padding-right: 0px;">\n <b class="text-warning"><i class="fas fa-chevron-right"></i></b>\n </div> \n <div class="col">Modifier ${a.id} <small class="text-muted">${Y.getModifierText(a)}</small></div>\n </div> \n </li>`);return`<div class="min-card-container col-lg-2 col-md-3 col-sm-6 col-12 pb-5">\n <div class="card h-100 no-overflow">\n <div class="img-container-overview no-overflow">\n <img src="${void 0===e.image||e.image.length<=0?n.extendDistPath()+"img/unknown.jpg":e.image}" class="card-img-top">\n \n ${n.getButtonsContainer([`<a onClick="${wi.register(()=>{n.toggleCard("clubCard-"+a)})}" class="btn btn-dark text-white"><i class="fas fa-info-circle"></i></a>`,`<a onClick="${wi.register(()=>{Y.editClub(e.id)})}" class="btn btn-dark text-info"><i class="fas fa-edit"></i> Edit</a>`])}\n\n <div id="clubCard-${a}" class="img-button-content-container">\n <div class="img-button-inner-content-container">\n <ul class="list-group list-group-flush">\n ${t.length>0?'<li class="list-group-item text-info"><b>Modifiers:</b></li>\n '+t:""}\n </ul>\n </div>\n </div>\n </div>\n\n <div class="card-body img-backdrop-card-body">\n <h5 class="card-title">\n Club ${e.id}: ${e.name} <small class="text-success">${e.tier}</small>\n ${void 0!==e.comment?`<small class="text-muted">${e.comment}</small>`:""}\n </h5>\n <p class="card-text">${e.description}</p>\n </div> \n </div>\n </div>`}static generateModifierTableEntry(e){let t=Y.getModifierTags(e.tags),a="",s=n.getRandomString();if(void 0!==e.tags&&e.tags.length>0)for(const t of e.tags)a+=`<span class="badge badge-pill badge-light mr-1">${t}</span>`;return`\n <tr id="${s}">\n <td>${e.id}</td>\n <td><a onClick="${wi.register(()=>{Y.editModifier(e.id)})}" class="customLink">Modifier ${e.id}</a></td>\n <td>${Y.getModifierMod(e,t)}</td>\n <td>${Y.getModifierPerk(e,t)}</td>\n <td>\n <div class="btn-group" role="group">\n <button type="button" class="btn btn-secondary text-info" onClick="${wi.register(()=>{Y.editModifier(e.id)})}"><i class="fas fa-edit"></i></button>\n <button type="button" class="btn btn-secondary text-danger" onClick="${wi.register(()=>{Y.removeModifier(e.id)})}"><i class="fas fa-trash"></i></button>\n </div>\n </td>\n </tr>\n `}static generateModifierCard(e){let t=Y.getModifierText(e),a="";if(void 0!==e.tags&&e.tags.length>0)for(const t of e.tags)a+=`<span class="badge badge-pill badge-light mr-1">${t}</span>`;return`<div class="min-card-container col-lg-3 col-sm-3 pb-5">\n <div class="card h-100 no-overflow">\n <div class="card-body">\n <h5 class="card-title">Modifier ${e.id}</h5>\n <p class="card-text">${t}</p>\n ${a.length>0?`<p class="card-text">${a}</p>`:""}\n </div>\n <div class="card-body"> \n <a class="btn btn-dark text-info" onClick="${wi.register(()=>{Y.editModifier(e.id)})}"><i class="fas fa-edit"></i> Edit Task</a>\n </div>\n </div></div>`}static getGeneralView(){return`<div class="form">\n <div class="form-group">\n <label for="mapTitle">Map title</label>\n <input type="text" class="form-control" id="mapTitle" value="" placeholder="Please give this map a title">\n </div>\n <div class="form-group">\n <label for="mapDescription">Map description</label>\n <textarea class="form-control" id="mapDescription" placeholder="Please describe the map, classes, tasks whatever you want to mention."></textarea> \n </div>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.resetGeneral()})}">Reset</button>\n <button type="button" class="btn btn-primary" onClick="${wi.register(()=>{Y.saveGeneral()})}">Save</button>\n </div>`}static generateCustomExportModal(e){let t="",a="",s="",i="",n="",o="";if(void 0!==V.majors&&Array.isArray(V.majors)&&V.majors.length>0)for(const e of V.majors.sort((e,t)=>e.id-t.id))t+=`<option value="${e.id}">Major ${e.id}: ${e.title}</option>`;if(void 0!==V.classes&&Array.isArray(V.classes)&&V.classes.length>0)for(const e of V.classes.sort((e,t)=>e.id-t.id))a+=`<option value="${e.id}">Class ${e.id}: ${e.title}</option>`;if(void 0!==V.clubs&&Array.isArray(V.clubs)&&V.clubs.length>0)for(const e of V.clubs.sort((e,t)=>e.id-t.id))s+=`<option value="${e.id}">Club ${e.id}: ${e.name}</option>`;if(void 0!==V.partners&&Array.isArray(V.partners)&&V.partners.length>0)for(const e of V.partners.sort((e,t)=>e.id-t.id))i+=`<option value="${e.id}">Partner ${e.id}: ${e.name}</option>`;if(void 0!==V.punishments&&Array.isArray(V.punishments)&&V.punishments.length>0)for(const e of V.punishments.sort((e,t)=>e.id-t.id))n+=`<option value="${e.id}">Punishment ${e.id}: ${e.title}</option>`;if(void 0!==V.rouletteOptions&&Array.isArray(V.rouletteOptions)&&V.rouletteOptions.length>0)for(const e of V.rouletteOptions.sort((e,t)=>e.id-t.id))o+=`<option value="${e.id}">Roulette option ${e.id}: ${e.title}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="customExportModal">\n <div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Club</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="customExportModal-moduleId">Module identifier</label>\n <input type="text" class="form-control customExportModal-moduleId" value="${/-([a-zA-ZZ0-9]+)$/gm.exec(Y.uuidv4())[1]}" placeholder="Choose a unique ID.">\n <small class="form-text text-muted">This identifier will be used to make sure the module export is not handled like a normal map. If you want to update a module you exported before, use the same ID!</small>\n </div>\n <div class="form-group">\n <label for="customExportModal-moduleTitle">Module title</label>\n <input type="text" class="form-control customExportModal-moduleTitle" value="${Y.escapeQuote(V.general.title)}" placeholder="Please give this module export a title.">\n </div>\n <div class="form-group">\n <label for="customExportModal-moduleDescription">Module description</label>\n <textarea class="form-control customExportModal-moduleDescription" placeholder="Please describe the module.">${V.general.description||""}</textarea> \n </div>\n \n <div class="form-group">\n <label for="customExportModal-majors">Export majors</label>\n <select multiple class="form-control customExportModal-majors" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all majors you want to export in this bundle. Related classes will be added automatically.</small>\n </div>\n <div class="form-group">\n <label for="customExportModal-classes">Export classes</label>\n <select multiple class="form-control customExportModal-classes" data-live-search="true" data-style="btn-secondary">\n ${a}\n </select>\n <small class="form-text text-muted">Please select all classes you want to export in this bundle which are not related to a selected major.</small>\n </div>\n <div class="form-group">\n <label for="customExportModal-clubs">Export clubs</label>\n <select multiple class="form-control customExportModal-clubs" data-live-search="true" data-style="btn-secondary">\n ${s}\n </select>\n <small class="form-text text-muted">Please select all clubs you want to export in this bundle.</small>\n </div>\n <div class="form-group">\n <label for="customExportModal-partners">Export partners</label>\n <select multiple class="form-control customExportModal-partners" data-live-search="true" data-style="btn-secondary">\n ${i}\n </select>\n <small class="form-text text-muted">Please select all partners you want to export in this bundle.</small>\n </div>\n <div class="form-group">\n <label for="customExportModal-punishments">Export punishments</label>\n <select multiple class="form-control customExportModal-punishments" data-live-search="true" data-style="btn-secondary">\n ${n}\n </select>\n <small class="form-text text-muted">Please select all punishments you want to export in this bundle.</small>\n </div>\n <div class="form-group">\n <label for="customExportModal-rouletteOptions">Export roulette options</label>\n <select multiple class="form-control customExportModal-rouletteOptions" data-live-search="true" data-style="btn-secondary">\n ${o}\n </select>\n <small class="form-text text-muted">Please select all roulette options you want to export in this bundle.</small>\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-primary customExportModal-export" onClick="${wi.register(()=>{Y.exportPlayableModule(e)})}">Export</button>\n </div>\n </div>\n </div>\n </div>\n `}static generateRouletteModal(e){return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="rouletteModal${e.id}">\n <div class="modal-dialog modal-xl modal-fullscreen modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Roulette option</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="rouletteModal-title">Title</label>\n <input type="text" class="form-control rouletteModal-title" placeholder="Give your roulette entry a good title." value="${Y.escapeQuote(e.title)}">\n </div>\n <div class="form-group">\n <label for="rouletteModal-description">Description</label>\n <input type="text" class="form-control rouletteModal-description" placeholder="Give your roulette entry a description." value="${Y.escapeQuote(e.description)}">\n </div>\n <div class="form-group">\n <label for="rouletteModal-probability${e.id}">Probability <span id="rouletteModal-probability-value${e.id}">(${e.probability})</span></label>\n <input type="range" class="form-control-range" id="rouletteModal-probability${e.id}" min="0" max="1" step="0.01" value="${e.probability}" onchange="document.getElementById('rouletteModal-probability-value${e.id}').innerHTML = '(' + this.value + ')'">\n </div>\n <div class="form-group custom-control custom-switch">\n <input type="checkbox" class="custom-control-input" id="rouletteModal-rollPunishment${e.id}" ${e.rollPunishment?"checked":""}>\n <label class="custom-control-label" for="rouletteModal-rollPunishment${e.id}">Roll a punishment.</label>\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeRoulette(e.id)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveRoulette(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveRouletteAndDuplicate(e.id)})}">Save and duplicate</a>\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveRouletteAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateHelpModal(e){return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="helpModal${e.id}">\n <div class="modal-dialog modal-xl modal-fullscreen modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Help</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="helpModal-title">Title</label>\n <input type="text" class="form-control helpModal-title" placeholder="Give your help entry a good title." value="${Y.escapeQuote(e.title)}">\n </div>\n <div class="form-group">\n <label for="helpModal-text">Content</label>\n <textarea class="form-control helpModal-text" placeholder="Please define your help text.">${e.text}</textarea>\n <small class="form-text text-muted">Feel free to use simple HTML.</small>\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeHelp(e.id)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveHelp(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveHelpAndDuplicate(e.id)})}">Save and duplicate</a>\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveHelpAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateTagModal(e){return`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="modal-title" aria-hidden="true" data-backdrop="static" id="tagModal">\n <div class="modal-dialog modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Tag</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body"> \n <div class="form-group">\n <label for="tagModal-tag">Tag</label>\n <input type="text" class="form-control tagModal-tag" placeholder="Insert a single tag." value="${Y.escapeQuote(e)}">\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeTag(e)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveTag(e)})}">Save</button> \n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveTagAndNew(e)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateParamModal(e){let t="seconds"===e.valueType||"minutes"===e.valueType||"hours"===e.valueType||"days"===e.valueType,a="punishments"===e.valueType,s=!t&&!a,i=e.value;return e.timeUnit&&"hours"==e.timeUnit?i/=60:e.timeUnit&&"days"==e.timeUnit&&(i/=1440),`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="paramModalTitle" aria-hidden="true" data-backdrop="static" id="paramModal${e.id}">\n <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Param</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="paramModal-description">Description</label>\n <input type="text" class="form-control paramModal-description" placeholder="Describe the param if required. (e.g. 'Lick a banana.')" value="${Y.escapeQuote(e.description)}">\n <small class="form-text text-muted">In case you have for example multiple timers for the same task, describe here for which step this timer is.</small>\n </div>\n <div class="form-group">\n <label for="paramModal-value">Value</label>\n <input type="number" class="form-control paramModal-value" placeholder="Please set the parameter value." value="${i}">\n <small class="form-text text-muted">In case your type is "minutes" insert the value as minutes, to use the counter option it must be an integer otherwise it's up to you but it must be a number.</small>\n </div>\n <div class="form-group">\n <label>Value type</label>\n <div class="custom-control custom-radio">\n <input type="radio" name="paramModal-valueType${e.id}" class="custom-control-input paramModal-valueType-time" id="paramModal-valueType-time${e.id}" value="time" ${t?"checked":""}>\n <label for="paramModal-valueType-time${e.id}" class="custom-control-label">\n <select class="form-control paramModal-timeUnit-time" id="paramModal-timeUnit-time${e.id}">\n <option value="seconds" ${e.timeUnit&&"seconds"==e.timeUnit?"selected":""}>Seconds</option>\n <option value="minutes" ${e.timeUnit&&"minutes"!=e.timeUnit?"":"selected"}>Minutes</option>\n <option value="hours" ${e.timeUnit&&"hours"==e.timeUnit?"selected":""}>Hours</option>\n <option value="days" ${e.timeUnit&&"days"==e.timeUnit?"selected":""}>Days</option>\n </select>\n </label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="paramModal-valueType${e.id}" class="custom-control-input paramModal-valueType-punishment" id="paramModal-valueType-punishment${e.id}" value="punishments" ${a?"checked":""}>\n <label for="paramModal-valueType-punishment${e.id}" class="custom-control-label form-inline" style="margin-top: 8px;">\n <select class="form-control paramModal-punishment-tier" id="paramModal-punishment-tier${e.id}">\n <option value="" ${e.punTier&&""!=e.punTier?"":"selected"}>Any</option>\n <option value="light" ${e.punTier&&"light"==e.punTier?"selected":""}>Light</option>\n <option value="hard" ${e.punTier&&"hard"==e.punTier?"selected":""}>Hard</option>\n <option value="hardcore" ${e.punTier&&"hardcore"==e.punTier?"selected":""}>Hardcore</option>\n </select>\n <span class="pl-2">Punishment</span>\n </label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="paramModal-valueType${e.id}" class="custom-control-input paramModal-valueType-others" id="paramModal-valueType-others${e.id}" value="others" ${s?"checked":""}>\n <label for="paramModal-valueType-others${e.id}" class="custom-control-label" style="margin-top: 8px;">\n <input type="text" style="margin-top: -3px;" class="form-control paramModal-valueType-others-text" value="${Y.escapeQuote(s?e.valueType:"")}">\n </label>\n </div>\n </div>\n <div class="form-group">\n <label for="paramModal-applyMultiplier">Parameter settings</label> \n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input paramModal-applyMultiplier" id="paramModal-applyMultiplier${e.id}" ${e.applyMultiplier?"checked":""} ${t||s?"":"disabled"}>\n <label class="custom-control-label" for="paramModal-applyMultiplier${e.id}">Apply multiplier to this parameter.</label>\n <small class="form-text text-muted">Keep in mind if a multiplier could make the value unhealty, e.g. enema.</small>\n </div>\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input paramModal-spawnTimer" id="paramModal-spawnTimer${e.id}" ${e.spawnTimer?"checked":""} ${t?"":"disabled"}>\n <label class="custom-control-label" for="paramModal-spawnTimer${e.id}">Show a timer for this parameter.</label>\n </div>\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input paramModal-startTimerAutomatically" id="paramModal-startTimerAutomatically${e.id}" ${e.startTimerAutomatically?"checked":""} ${t?"":"disabled"}>\n <label class="custom-control-label" for="paramModal-startTimerAutomatically${e.id}">Starts this timer the moment the task is started.</label>\n <small class="form-text text-muted">If you have multiple timers, you probably only want one to start directly.</small>\n </div>\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input paramModal-punishTime" id="paramModal-punishTime${e.id}" ${e.punishTime?"checked":""} ${s||a?"disabled":""}>\n <label class="custom-control-label" for="paramModal-punishTime${e.id}">In case the main task is failed, increase this timer by:</label>\n <input type="number" class="form-control paramModal-punishTime-minutes" placeholder="Please set the minutes that shall be added as punishment." value="${e.punishTimeMinutes}" ${t?"":"disabled"}>\n <small class="form-text text-muted">If you check this, the user will have the option to request a punishment which increases the task duration.</small>\n </div>\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input paramModal-provideCounter" id="paramModal-provideCounter${e.id}" ${e.provideCounter?"checked":""} ${s?"":"disabled"}>\n <label class="custom-control-label" for="paramModal-provideCounter${e.id}">The parameter should provide a counter option.</label>\n <small class="form-text text-muted">If you check this the user will have the option to count the given value down.</small>\n </div>\n <div class="custom-control custom-switch">\n <input type="checkbox" class="custom-control-input paramModal-hiddenTask" id="paramModal-hiddenTask${e.id}" ${e.hiddenTask?"checked":""} ${a?"disabled":""}>\n <label class="custom-control-label" for="paramModal-hiddenTask${e.id}">This parameter should only show up after the one before (based on ID) was finished.</label>\n <small class="form-text text-muted">If you check this this parameter will not be shown to the user as a sub-task until the one before was finished.</small>\n </div>\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeParam(e.id)})}">Delete</button>\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveParam(e.id,e.taskId)})}">Save</button>\n </div>\n </div>\n </div>\n </div>\n `}static generateTaskModal(e){let t="";if(void 0!==V.tags&&Array.isArray(V.tags)&&V.tags.length>0)for(const a of V.tags)void 0!==e.tags&&e.tags.includes(a)?t+=`<option value="${Y.escapeQuote(a)}" selected>${a}</option>`:t+=`<option value="${Y.escapeQuote(a)}">${a}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="taskModalTitle" aria-hidden="true" data-backdrop="static" id="taskModal${e.id}">\n <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Task</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="taskModal-description">Description</label>\n <textarea class="form-control taskModal-description" placeholder="Please describe the task.">${e.description}</textarea>\n <small class="form-text text-muted taskModal-description-preview"><b>Preview:</b> ...</small>\n <small class="form-text text-muted">In case you need to use parameters (timers / counters) please define them below and use them like this: "Do this $param0." </small> \n </div>\n <div class="form-group">\n <label for="taskModal-tags">Assigned Tags <a class="header-add" onClick="${wi.register(()=>{Y.addTag(`#taskModal${e.id} select.taskModal-tags`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control taskModal-tags" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all matching tags, these will be used to apply partner and club multipliers.</small>\n </div>\n <div class="form-group">\n <label for="taskModal-params">Params <a class="header-add" onClick="${wi.register(()=>{Y.addParam(e.id)})}"><i class="fas fa-plus-circle"></i></a></label>\n <div class="paramsCardContainer">\n ${Y.getParams(e.id)}\n </div>\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeTask(e.id)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveTask(e.id)})}">Save</button> \n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveTaskAndDuplicate(e.id)})}">Save and duplicate</a>\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveTaskAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateClassModal(e){let t="",a="",s="";if(void 0!==V.classes&&Array.isArray(V.classes)&&V.classes.length>0)for(const a of V.classes.sort((e,t)=>e.id-t.id))e.id!==a.id&&(e.prerequisites.includes(a.id)?t+=`<option value="${a.id}" selected>Class ${a.id}: ${a.title}</option>`:t+=`<option value="${a.id}">Class ${a.id}: ${a.title}</option>`);if(void 0!==V.tasks&&Array.isArray(V.tasks)&&V.tasks.length>0)for(const t of V.tasks.sort((e,t)=>e.id-t.id))e.tasks.includes(t.id)?a+=`<option value="${t.id}" selected>Task ${t.id}: ${Y.applyParamsToString(t.id,t.description)}</option>`:a+=`<option value="${t.id}">Task ${t.id}: ${Y.applyParamsToString(t.id,t.description)}</option>`,e.exams.includes(t.id)?s+=`<option value="${t.id}" selected>Task ${t.id}: ${Y.applyParamsToString(t.id,t.description)}</option>`:s+=`<option value="${t.id}">Task ${t.id}: ${Y.applyParamsToString(t.id,t.description)}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="classModalTitle" aria-hidden="true" data-backdrop="static" id="classModal${e.id}">\n <div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Class</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="classModal-title">Title</label>\n <input type="text" class="form-control classModal-title" placeholder="Please provide a title for this class." value="${Y.escapeQuote(e.title)}">\n </div>\n <div class="form-group">\n <label for="classModal-subtitle">Subtitle</label>\n <input type="text" class="form-control classModal-subtitle" placeholder="If you want you can provide a single line description for this class that will be displayed together with the title." value="${Y.escapeQuote(e.subtitle)}">\n </div>\n <div class="form-group">\n <label for="classModal-comment">Comment</label>\n <input type="text" class="form-control classModal-comment" placeholder="Please provide a comment for this class if required." value="${Y.escapeQuote(e.comment)}">\n </div>\n <div class="form-group">\n <label for="classModal-description">Description</label>\n <textarea class="form-control classModal-description" placeholder="Please describe the content of the class.">${e.description}</textarea>\n </div>\n <div class="form-group">\n <label for="classModal-tier">Tier</label>\n <div class="custom-control custom-radio">\n <input type="radio" name="classModal-tier${e.id}" class="custom-control-input classModal-tier-beginner" id="classModal-tier-beginner${e.id}" value="beginner" ${void 0===e.tier||""===e.tier||"beginner"===e.tier?"checked":""}>\n <label class="custom-control-label" for="classModal-tier-beginner${e.id}">Beginner</label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="classModal-tier${e.id}" class="custom-control-input classModal-tier-intermediate" id="classModal-tier-intermediate${e.id}" value="intermediate" ${"intermediate"===e.tier?"checked":""}>\n <label class="custom-control-label" for="classModal-tier-intermediate${e.id}">Intermediate</label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="classModal-tier${e.id}" class="custom-control-input classModal-tier-advanced" id="classModal-tier-advanced${e.id}" value="advanced" ${"advanced"===e.tier?"checked":""}>\n <label class="custom-control-label" for="classModal-tier-advanced${e.id}">Advanced</label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="classModal-tier${e.id}" class="custom-control-input classModal-tier-master" id="classModal-tier-master${e.id}" value="master" ${"master"===e.tier?"checked":""}>\n <label class="custom-control-label" for="classModal-tier-master${e.id}">Master</label>\n </div>\n </div>\n <div class="form-group">\n <label for="classModal-days">Available on</label>\n <select multiple class="form-control classModal-days" data-style="btn-secondary">\n <option value="1" ${e.days.includes(1)?"selected":""}>Monday</option>\n <option value="2" ${e.days.includes(2)?"selected":""}>Tuesday</option>\n <option value="3" ${e.days.includes(3)?"selected":""}>Wednesday</option>\n <option value="4" ${e.days.includes(4)?"selected":""}>Thursday</option>\n <option value="5" ${e.days.includes(5)?"selected":""}>Friday</option>\n <option value="6" ${e.days.includes(6)?"selected":""}>Saturday</option>\n <option value="0" ${e.days.includes(0)?"selected":""}>Sunday</option>\n </select>\n <small class="form-text text-muted">Define the days this class will be sheduled.</small>\n </div>\n <div class="form-group">\n <label for="classModal-prerequisites">Prerequisite classes <a class="header-add" onClick="${wi.register(()=>{Y.addClass(`#classModal${e.id} select.classModal-prerequisites`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control classModal-prerequisites" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all prerequisites classes that are required to graduate from this major.</small>\n </div>\n <div class="form-group custom-control custom-switch"> \n <input type="checkbox" class="custom-control-input" id="classModal-disable-bonus-attendance${e.id}" ${e.disableBonusAttendance?"checked":""}>\n <label class="custom-control-label" for="classModal-disable-bonus-attendance${e.id}">Disable bonus attendances</label>\n <small class="form-text text-muted">Disables the bonus attendance modifiers for this class. This is important if you want to make sure each task is done in order.</small>\n </div>\n <div class="form-group">\n <label for="classModal-task-list-size">Display a custom amount of tasks at once</label> \n <input type="number" min="1" class="form-control classModal-task-list-size" value="${e.taskListSize||o.defaultTaskListSize}">\n </div>\n <div class="form-group">\n <label for="classModal-tasks">Tasks <a class="header-add" onClick="${wi.register(()=>{Y.addTask(`#classModal${e.id} select.classModal-tasks`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control classModal-tasks" data-live-search="true" data-style="btn-secondary">\n ${a}\n </select>\n <small class="form-text text-muted">Please select all tasks that are available to participate in this class.</small>\n </div>\n <div class="form-group">\n <label for="classModal-exams">Exam tasks <a class="header-add" onClick="${wi.register(()=>{Y.addTask(`#classModal${e.id} select.classModal-exams`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control classModal-exams" data-live-search="true" data-style="btn-secondary">\n ${s}\n </select>\n <small class="form-text text-muted">Please select all exams tasks that are available to graduate from this class.</small>\n </div>\n <div class="form-group">\n ${J.generateImageSelection(e)}\n </div> \n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeClass(e.id)})}">Delete</button>\n <div class="btn-group dropup"> \n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveClass(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveClassAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateMajorModal(e){let t="",a="";if(void 0!==V.classes&&Array.isArray(V.classes)&&V.classes.length>0)for(const a of V.classes.sort((e,t)=>e.id-t.id))e.prerequisites.includes(a.id)?t+=`<option value="${a.id}" selected>Class ${a.id}: ${a.title}</option>`:t+=`<option value="${a.id}">Class ${a.id}: ${a.title}</option>`;if(void 0!==V.tasks&&Array.isArray(V.tasks)&&V.tasks.length>0)for(const t of V.tasks.sort((e,t)=>e.id-t.id))e.exams.includes(t.id)?a+=`<option value="${t.id}" selected>Task ${t.id}: ${Y.applyParamsToString(t.id,t.description)}</option>`:a+=`<option value="${t.id}">Task ${t.id}: ${Y.applyParamsToString(t.id,t.description)}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="majorModalTitle" aria-hidden="true" data-backdrop="static" id="majorModal${e.id}">\n <div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Major</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="majorModal-title">Title</label>\n <input type="text" class="form-control majorModal-title" placeholder="Please provide a title for this major." value="${Y.escapeQuote(e.title)}">\n </div>\n <div class="form-group">\n <label for="majorModal-subtitle">Subtitle</label>\n <input type="text" class="form-control majorModal-subtitle" placeholder="If you want you can provide a single line description for this major that will be displayed together with the title." value="${Y.escapeQuote(e.subtitle)}">\n </div>\n <div class="form-group">\n <label for="majorModal-description">Description</label>\n <textarea class="form-control majorModal-description" placeholder="Please describe the content of this major.">${e.description}</textarea>\n </div>\n <div class="form-group">\n <label for="majorModal-prerequisites">Prerequisite classes <a class="header-add" onClick="${wi.register(()=>{Y.addClass(`#majorModal${e.id} select.majorModal-prerequisites`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control majorModal-prerequisites" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all prerequisites classes that are required to graduate from this major.</small>\n </div>\n <div class="form-group">\n <label for="majorModal-exams">Exam tasks <a class="header-add" onClick="${wi.register(()=>{Y.addTask(`#majorModal${e.id} select.majorModal-exams`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control majorModal-exams" data-live-search="true" data-style="btn-secondary">\n ${a}\n </select>\n <small class="form-text text-muted">Please select all tasks that are available to graduate from this major.</small>\n </div>\n <div class="form-group">\n ${J.generateImageSelection(e)}\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeMajor(e.id)})}">Delete</button> \n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveMajor(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveMajorAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generatePunishmentModal(e){let t="";if(void 0!==V.tasks&&Array.isArray(V.tasks)&&V.tasks.length>0)for(const a of V.tasks.sort((e,t)=>e.id-t.id))e.punishment==a.id?t+=`<option value="${a.id}" selected>Task ${a.id}: ${Y.applyParamsToString(a.id,a.description)}</option>`:t+=`<option value="${a.id}">Task ${a.id}: ${Y.applyParamsToString(a.id,a.description)}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="punishmentModal${e.id}">\n <div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Punishment</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="punishmentModal-title">Title</label>\n <input type="text" class="form-control punishmentModal-title" placeholder="Please provide a title for this punishment." value="${Y.escapeQuote(e.title)}">\n </div>\n <div class="form-group">\n <label for="punishmentModal-tier">Tier</label>\n <div class="custom-control custom-radio">\n <input type="radio" name="punishmentModal-tier${e.id}" class="custom-control-input punishmentModal-tier-light" id="punishmentModal-tier-light${e.id}" value="light" ${void 0===e.tier||""===e.tier||"light"===e.tier?"checked":""}>\n <label class="custom-control-label" for="punishmentModal-tier-light${e.id}">Light</label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="punishmentModal-tier${e.id}" class="custom-control-input punishmentModal-tier-hard" id="punishmentModal-tier-hard${e.id}" value="hard" ${"hard"===e.tier?"checked":""}>\n <label class="custom-control-label" for="punishmentModal-tier-hard${e.id}">Hard</label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="punishmentModal-tier${e.id}" class="custom-control-input punishmentModal-tier-hardcore" id="punishmentModal-tier-hardcore${e.id}" value="hardcore" ${"hardcore"===e.tier?"checked":""}>\n <label class="custom-control-label" for="punishmentModal-tier-hardcore${e.id}">Hardcore</label>\n </div>\n </div>\n <div class="form-group">\n <label for="punishmentModal-punishment">Punishment task <a class="header-add" onClick="${wi.register(()=>{Y.addTask(`#punishmentModal${e.id} .punishmentModal-punishment`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select class="form-control punishmentModal-punishment">\n ${t}\n </select>\n </div>\n <div class="form-group">\n ${J.generateImageSelection(e)}\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removePunishment(e.id)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.savePunishment(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.savePunishmentAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generatePartnerModal(e){let t="";if(void 0!==V.modifiers&&Array.isArray(V.modifiers)&&V.modifiers.length>0)for(const a of V.modifiers.sort((e,t)=>e.id-t.id))e.modifiers.includes(a.id)?t+=`<option value="${a.id}" selected>Modifier ${a.id}: ${Y.getModifierText(a)}</option>`:t+=`<option value="${a.id}">Modifier ${a.id}: ${Y.getModifierText(a)}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="partnerModal${e.id}">\n <div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Partner</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="partnerModal-name">Name</label>\n <input type="text" class="form-control partnerModal-name" placeholder="Please provide a name for this partner." value="${Y.escapeQuote(e.name)}">\n </div>\n <div class="form-group">\n <label for="partnerModal-description">Description</label>\n <textarea class="form-control partnerModal-description" placeholder="Please describe the partner.">${e.description}</textarea>\n </div>\n <div class="form-group">\n <label for="partnerModal-modifiers">Available modifiers <a class="header-add" onClick="${wi.register(()=>{Y.addModifier(`#partnerModal${e.id} select.partnerModal-modifiers`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control partnerModal-modifiers" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all modifiers that are active for this partner.</small>\n </div>\n <div class="form-group">\n ${J.generateImageSelection(e)}\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removePartner(e.id)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.savePartner(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.savePartnerAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateClubModal(e){let t="";if(void 0!==V.modifiers&&Array.isArray(V.modifiers)&&V.modifiers.length>0)for(const a of V.modifiers.sort((e,t)=>e.id-t.id))e.modifiers.includes(a.id)?t+=`<option value="${a.id}" selected>Modifier ${a.id}: ${Y.getModifierText(a)}</option>`:t+=`<option value="${a.id}">Modifier ${a.id}: ${Y.getModifierText(a)}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="clubModal${e.id}">\n <div class="modal-dialog modal-fullscreen modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Club</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="clubModal-name">Name</label>\n <input type="text" class="form-control clubModal-name" placeholder="Please provide a name for this club." value="${Y.escapeQuote(e.name)}">\n </div>\n <div class="form-group">\n <label for="clubModal-comment">Comment</label>\n <input type="text" class="form-control clubModal-comment" placeholder="Please provide a comment for this club if required." value="${Y.escapeQuote(e.comment)}">\n </div>\n <div class="form-group">\n <label for="clubModal-description">Description</label>\n <textarea class="form-control clubModal-description" placeholder="Please describe the club.">${e.description}</textarea>\n </div>\n \n <div class="form-group">\n <label for="clubModal-tier">Tier</label>\n <div class="custom-control custom-radio">\n <input type="radio" name="clubModal-tier${e.id}" class="custom-control-input clubModal-tier-normal" id="clubModal-tier-normal${e.id}" value="normal" ${void 0===e.tier||""===e.tier||"normal"===e.tier?"checked":""}>\n <label class="custom-control-label" for="clubModal-tier-normal${e.id}">Normal</label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="clubModal-tier${e.id}" class="custom-control-input clubModal-tier-elite" id="clubModal-tier-elite${e.id}" value="elite" ${"elite"===e.tier?"checked":""}>\n <label class="custom-control-label" for="clubModal-tier-elite${e.id}">Elite</label>\n </div>\n </div>\n <div class="form-group">\n <label for="clubModal-modifiers">Available modifiers <a class="header-add" onClick="${wi.register(()=>{Y.addModifier(`#clubModal${e.id} select.clubModal-modifiers`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control clubModal-modifiers" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all modifiers that are active for this club.</small>\n </div>\n <div class="form-group">\n ${J.generateImageSelection(e)}\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeClub(e.id)})}">Delete</button> \n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveClub(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveClubAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n `}static generateModifierModal(e){let t="";if(void 0!==V.tags&&Array.isArray(V.tags)&&V.tags.length>0)for(const a of V.tags)void 0!==e.tags&&e.tags.includes(a)?t+=`<option value="${Y.escapeQuote(a)}" selected>${a}</option>`:t+=`<option value="${Y.escapeQuote(a)}">${a}</option>`;return`<div class="modal fade" tabindex="-1" role="dialog" aria-hidden="true" data-backdrop="static" id="modifierModal${e.id}">\n <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Customize Modifier</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form">\n <div class="form-group">\n <label for="modifierModal-tags">Assigned Tags <a class="header-add" onClick="${wi.register(()=>{Y.addTag(`#modifierModal${e.id} select.modifierModal-tags`)})}"><i class="fas fa-plus-circle"></i></a></label>\n <select multiple class="form-control modifierModal-tags" data-live-search="true" data-style="btn-secondary">\n ${t}\n </select>\n <small class="form-text text-muted">Please select all matching tags, these will be used to apply multipliers to tasks with the same tags (no tag = "all").</small>\n </div>\n\n <div class="form-group">\n <label for="modifierModal-modifier">Modifier</label>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-modifier${e.id}" class="custom-control-input modifierModal-modifier-text" id="modifierModal-modifier-text${e.id}" value="text" ${""===e.modType||"text"===e.modType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-modifier-text${e.id}">\n <input type="text" style="margin-top: -3px;" class="form-control modifierModal-modVal-text" value="${Y.escapeQuote("text"===e.modType?e.modVal:"")}">\n </label>\n <small class="form-text text-muted">This is a "free text" option it will not affect the game.</small>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-modifier${e.id}" class="custom-control-input modifierModal-modifier-difficulty" id="modifierModal-modifier-difficulty${e.id}" value="difficulty" ${"difficulty"===e.modType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-modifier-difficulty${e.id}">\n Change difficulty by applying a multiplier: <input type="number" min="1" max="100" style="margin-top: -3px;" class="form-control modifierModal-modVal-difficulty" value="${"difficulty"===e.modType?e.modVal:""}">\n </label>\n <small class="form-text text-muted">This option will make tasks (with a matching tag) harder, depending on the multiplier (in %) you set here.</small>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-modifier${e.id}" class="custom-control-input modifierModal-modifier-punishment" id="modifierModal-modifier-punishment${e.id}" value="punishment" ${"punishment"===e.modType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-modifier-punishment${e.id}">\n Roll a random punishment once this modifier is activated.\n </label>\n </div>\n </div>\n\n <div class="form-group">\n <label for="modifierModal-perk">Perk</label>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-perk${e.id}" class="custom-control-input modifierModal-perk-text" id="modifierModal-perk-text${e.id}" value="text" ${""===e.perkType||"text"===e.perkType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-perk-text${e.id}">\n <input type="text" style="margin-top: -3px;" class="form-control modifierModal-perkVal-text" value="${Y.escapeQuote("text"===e.perkType?e.perkVal:"")}">\n </label>\n <small class="form-text text-muted">This is a "free text" option it will not affect the game.</small>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-perk${e.id}" class="custom-control-input modifierModal-perk-difficulty" id="modifierModal-perk-difficulty${e.id}" value="difficulty" ${"difficulty"===e.perkType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-perk-difficulty${e.id}">\n Change difficulty by applying a multiplier: <input type="number" min="-100" max="-1" style="margin-top: -3px;" class="form-control modifierModal-perkVal-difficulty" value="${"difficulty"===e.perkType?e.perkVal:""}">\n </label>\n <small class="form-text text-muted">This option will make tasks (with a matching tag) easier, depending on the multiplier (in %) you set here.</small>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-perk${e.id}" class="custom-control-input modifierModal-perk-skip" id="modifierModal-perk-skip${e.id}" value="skip" ${"skip"===e.perkType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-perk-skip${e.id}">\n Allows to skip a class (without rolling a punishment for missing).\n </label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-perk${e.id}" class="custom-control-input modifierModal-perk-attendance" id="modifierModal-perk-attendance${e.id}" value="attendance" ${"attendance"===e.perkType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-perk-attendance${e.id}">\n Allows to add an additional knowledge point to classes that match the selected tag(s) (faster progression). \n </label>\n </div>\n <div class="custom-control custom-radio">\n <input type="radio" name="modifierModal-perk${e.id}" class="custom-control-input modifierModal-perk-resetRoulette" id="modifierModal-perk-resetRoulette${e.id}" value="resetRoulette" ${"resetRoulette"===e.perkType?"checked":""}>\n <label class="custom-control-label" for="modifierModal-perk-resetRoulette${e.id}">\n On activation: Reset the roulette and re-roll directly afterwards.\n </label>\n </div>\n </div>\n </div>\n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>\n <button type="button" class="btn btn-danger" onClick="${wi.register(()=>{Y.removeModifier(e.id)})}">Delete</button>\n <div class="btn-group dropup">\n <button type="button" class="btn btn-primary save-button" onClick="${wi.register(()=>{Y.saveModifier(e.id)})}">Save</button>\n <button type="button" class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">\n <span class="sr-only">Toggle Dropdown</span>\n </button>\n <div class="dropdown-menu dropdown-menu-right save-button-area">\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveModifierAndDuplicate(e.id)})}">Save and duplicate</a>\n <a class="dropdown-item customLink" onClick="${wi.register(()=>{Y.saveModifierAndNew(e.id)})}">Save and add a new one</a>\n </div>\n </div> \n </div>\n </div>\n </div>\n </div>\n `}static generateQrModal(e){return`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="QrModalTitle" aria-hidden="true" data-backdrop="static" id="qrModal${e}">\n <div class="modal-dialog modal-m modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="modal-title">Quick share</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n \n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> \n </div>\n </div>\n </div>\n </div>\n `}static generateImageModal(e,t){return`<div class="modal fade" tabindex="-1" role="dialog" aria-labelledby="ImageModalTitle" aria-hidden="true" data-backdrop="static" id="imageModal${e.id}">\n <div class="modal-dialog modal-m modal-dialog-centered modal-dialog-scrollable" role="document">\n <div class="modal-content">\n <div class="modal-header">\n <h5 class="image-modal-title">Quick share</h5>\n <button type="button" class="close" data-dismiss="modal" aria-label="Close">\n <span aria-hidden="true">×</span>\n </button>\n </div>\n <div class="modal-body">\n <div class="form-group">\n ${J.generateImageSelection(e)}\n </div> \n </div>\n <div class="modal-footer">\n <button type="button" class="btn btn-success" onClick="${wi.register(()=>{t(e.id)})}">Confirm</button>\n <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button> \n </div>\n </div>\n </div>\n </div>\n `}}class Y{static escapeQuote(e){return"string"!=typeof e?e:e.replace(/"/g,""")}static minutesToTime(e){let t=Math.floor(e/1440),a=Math.floor((e-1440*t)/60),s=Math.round(e%60),i="";return 1==t?i+=t+" day":t>1&&(i+=t+" days"),a>0&&(t&&!s?i+=" and":t&&(i+=";"),i+=1==a?` ${a} hour`:` ${a} hours`),s>0&&((t||a)&&(i+=" and"),i+=1==s?` ${s} minute`:` ${s} minutes`),i.trim()}static applyParamsToString(e,t){let a="".concat(t);for(const t of V.params.filter(t=>t.taskId==e)){let e="",s=Math.round(1*t.value);if("minutes"===t.valueType){Y.minutesToTime(s);let a=Y.minutesToTime(t.value);t.applyMultiplier,e=""+a}else"punishments"===t.valueType?(t.applyMultiplier,e=`${t.value} ${t.punTier?t.punTier:"random"} ${t.valueType}`):(t.applyMultiplier,e=`${t.value} ${t.valueType}`);a=a.replace("$param"+t.id,`<span class="text-info">${e}</span>`)}return a}static restoredTableSettings(e){let t=$("#"+e).bootstrapTable("getOptions");return"object"==typeof t&&t.hasOwnProperty("idField")||(t={searchText:"",sortName:"",sortOrder:"desc",pageNumber:1,pageSize:10,visibleColumns:!0}),"object"==typeof t&&Array.isArray(t.columns)&&t.columns.length>0&&(t.visibleColumns=t.columns[0].filter(e=>e.visible).map(e=>e.field)),t}static getModifierTags(e){return void 0!==e&&e.length>0?e.join(", "):"all"}static getModifierMod(e,t){return"difficulty"===e.modType?`Increase difficulty for ${t} tasks by ${e.modVal}%.`:"punishment"===e.modType?"Roll a punishment.":""+e.modVal}static getModifierPerk(e,t){return"difficulty"===e.perkType?`Decrease difficulty for ${t} tasks by ${e.perkVal}%.`:"skip"===e.perkType?"Allow skipping a class without punishment.":"attendance"===e.perkType?`Add an additional attendance for ${t} classes.`:"resetRoulette"===e.perkType?"Reset roulette.":""+e.perkVal}static getModifierText(e){let t="",a="";return a=Y.getModifierTags(e.tags),t+=`[${Y.getModifierMod(e,a)}]`,t+=`[${Y.getModifierPerk(e,a)}]`,t}static saveGeneral(){V.general.title=$("#mapTitle").val(),V.general.description=$("#mapDescription").val(),Y.autoSaveUpdate("general")}static resetGeneral(){$("#mapTitle").val(V.general.title),$("#mapDescription").val(V.general.description)}static addRoulette(){Y.openRoulette({id:V.globalIndex++,title:"",description:"",probability:0,rollPunishment:!1})}static editRoulette(e){Y.openRoulette(V.rouletteOptions.find((function(t){return t.id==e})))}static openRoulette(e){let t="#rouletteModal"+e.id;$("#modalContainer").append(J.generateRouletteModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .rouletteModal-title").trigger("focus")})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(""+t).modal("dispose").remove()}))}static removeRoulette(e){V.rouletteOptions=V.rouletteOptions.filter((function(t){return t.id!=e})),$("#rouletteModal"+e).is(":visible")?$("#rouletteModal"+e).modal("hide"):Y.refreshView()}static saveRouletteAndNew(e){Y.saveRoulette(e)&&Y.addRoulette()}static saveRouletteAndDuplicate(e){if(Y.saveRoulette(e)){let t=JSON.parse(JSON.stringify(V.rouletteOptions.find((function(t){return t.id==e}))));t.id=V.globalIndex++,Y.openRoulette(t)}}static saveRoulette(e){let t={id:e,title:$(`#rouletteModal${e} .rouletteModal-title`).val(),description:$(`#rouletteModal${e} .rouletteModal-description`).val(),probability:parseFloat(document.querySelector("#rouletteModal-probability"+e).value),rollPunishment:document.querySelector("#rouletteModal-rollPunishment"+e).checked};return void 0===t.title||t.title.length<=0?(n.showToast("Invalid title","Please provide a title.","warning"),!1):void 0===t.description||t.description.length<=0?(n.showToast("Invalid description","Please provide a description.","warning"),!1):(Y.removeRoulette(e),V.rouletteOptions.push(t),Y.autoSaveUpdate("roulette"),!0)}static addHelp(){Y.openHelp({id:V.globalIndex++,title:"",text:""})}static editHelp(e){Y.openHelp(V.help.find((function(t){return t.id==e})))}static handleModalKeyDown(e){if(e.ctrlKey||e.metaKey)switch(String.fromCharCode(e.which).toLowerCase()){case"s":const t=$("button.save-button",e.currentTarget);if(1==t.length)return t.trigger("click"),e.preventDefault(),!1}}static openHelp(e){let t="#helpModal"+e.id;$("#modalContainer").append(J.generateHelpModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .helpModal-title").trigger("focus")})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(""+t).modal("dispose").remove()}))}static removeHelp(e){V.help=V.help.filter((function(t){return t.id!=e})),$("#helpModal"+e).is(":visible")?$("#helpModal"+e).modal("hide"):Y.refreshView()}static saveHelpAndNew(e){Y.saveHelp(e)&&Y.addHelp()}static saveHelpAndDuplicate(e){if(Y.saveHelp(e)){let t=JSON.parse(JSON.stringify(V.help.find((function(t){return t.id==e}))));t.id=V.globalIndex++,Y.openHelp(t)}}static saveHelp(e){let t={id:e,title:$(`#helpModal${e} .helpModal-title`).val(),text:$(`#helpModal${e} .helpModal-text`).val()};if(void 0===t.title||t.title.length<=0)return n.showToast("Invalid title","Please provide a title.","warning"),!1;if(void 0===t.text||t.text.length<=0)return n.showToast("Invalid help text","Please provide a help text.","warning"),!1;let a=document.createElement("div");return a.innerHTML=t.text,t.text=a.innerHTML,Y.removeHelp(e),V.help.push(t),Y.autoSaveUpdate("help"),!0}static addTag(e){Y.openTag(""),Y.enableTagReference(e)}static enableTagReference(e){if(!e)return!1;$("#tagModal .save-button, #tagModal .save-button-area a.customLink").on("click",(function(t){let a=$("#tagModal .tagModal-tag").val();$(e).append(`<option value="${Y.escapeQuote(a)}" selected>${a}</option>`),$(e).selectpicker("refresh").selectpicker("refresh")})),$("#tagModal").attr("data-ref",e)}static editTag(e){Y.openTag(e)}static openTag(e){$("#modalContainer").append(J.generateTagModal(e)),$("#tagModal").modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$("#tagModal .tagModal-tag").trigger("focus")})).on("hidden.bs.modal",(function(e){Y.refreshView(),$("#tagModal").modal("dispose").remove(),document.querySelector("#modalContainer").dispatchEvent(new CustomEvent("removed-tag-modal",{}))}))}static removeTag(e){V.tags=V.tags.filter((function(t){return t!=e})),$("#tagModal").is(":visible")?$("#tagModal").modal("hide"):Y.refreshView()}static saveTagAndNew(e){if(Y.saveTag(e)){const e=$("#tagModal").attr("data-ref");document.querySelector("#modalContainer").addEventListener("removed-tag-modal",(function(t){Y.addTag(e)}),{once:!0})}}static saveTag(e){let t=$("#tagModal .tagModal-tag").val().toLowerCase();if(void 0===t||t.length<=0)return n.showToast("Invalid tag","Please insert a tag.","warning"),!1;if(V.tags.includes(t))return n.showToast("Invalid tag","This tag is already in use.","warning"),!1;let a=t.match(/\s/g);return null!==a&&a.length>0?(n.showToast("Invalid tag","Please insert a single tag (no spaces allowed).","warning"),!1):(Y.removeTag(e),V.tags.push(t),Y.autoSaveUpdate("tag"),!0)}static addParam(e){Y.openParam({id:V.globalIndex++,taskId:e,description:"",value:"",valueType:"minutes",timeUnit:"minutes",applyMultiplier:!1,spawnTimer:!1,startTimerAutomatically:!1,provideCounter:!1,punishTime:!1,punishTimeMinutes:0,hiddenTask:!1})}static insertParam(e,t){let a=$(`#taskModal${t} .taskModal-description`);a.val().indexOf(" ")===a.val().length-1?a[0].value+="$param"+e:a[0].value+=" $param"+e,a.trigger("focus")}static editParam(e){Y.openParam(V.params.find((function(t){return t.id==e})))}static openParam(e){let t="#paramModal"+e.id;$("#modalContainer").append(J.generateParamModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .paramModal-description").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(a){$(`#taskModal${e.taskId} .paramsCardContainer`).html(Y.getParams(e.taskId)),$(t).modal("dispose").remove()})),$(t+" .paramModal-timeUnit-time").on("change",(function(e){$(t+" .paramModal-valueType-time").prop("checked",!0).trigger("change")})),$(t+" .paramModal-valueType-others-text").on("change",(function(e){$(t+" .paramModal-valueType-others").prop("checked",!0).trigger("change")})),$(t+" .paramModal-punishment-tier").on("change",(function(e){$(t+" .paramModal-valueType-punishment").prop("checked",!0).trigger("change")})),$(t+" .paramModal-valueType-time").on("change",(function(e){e.target.checked&&($(t+" .paramModal-applyMultiplier").prop("disabled",!1),$(t+" .paramModal-spawnTimer").prop("disabled",!1),$(t+" .paramModal-startTimerAutomatically").prop("disabled",!1),$(t+" .paramModal-provideCounter").prop("disabled",!0),$(t+" .paramModal-punishTime").prop("disabled",!1),$(t+" .paramModal-punishTime-minutes").prop("disabled",!1),$(t+" .paramModal-hiddenTask").prop("disabled",!1))})),$(t+" .paramModal-valueType-others").on("change",(function(e){e.target.checked&&($(t+" .paramModal-applyMultiplier").prop("disabled",!1),$(t+" .paramModal-spawnTimer").prop("disabled",!0),$(t+" .paramModal-startTimerAutomatically").prop("disabled",!0),$(t+" .paramModal-provideCounter").prop("disabled",!1),$(t+" .paramModal-punishTime").prop("disabled",!0),$(t+" .paramModal-punishTime-minutes").prop("disabled",!0),$(t+" .paramModal-hiddenTask").prop("disabled",!1))})),$(t+" .paramModal-valueType-punishment").on("change",(function(e){e.target.checked&&($(t+" .paramModal-applyMultiplier").prop("disabled",!0),$(t+" .paramModal-spawnTimer").prop("disabled",!0),$(t+" .paramModal-startTimerAutomatically").prop("disabled",!0),$(t+" .paramModal-provideCounter").prop("disabled",!0),$(t+" .paramModal-punishTime").prop("disabled",!0),$(t+" .paramModal-punishTime-minutes").prop("disabled",!0),$(t+" .paramModal-hiddenTask").prop("disabled",!0))}))}static removeParam(e){V.params=V.params.filter((function(t){return t.id!=e})),$("#paramModal"+e).modal("hide")}static saveParam(e,t){let a="#paramModal"+e,s={id:e,taskId:t,description:$(a+" .paramModal-description").val(),value:Number.parseFloat($(a+" .paramModal-value").val()),valueType:$(`${a} input[name='paramModal-valueType${e}']:checked`).val(),timeUnit:$(a+" .paramModal-timeUnit-time").val(),punTier:$(a+" .paramModal-punishment-tier").val(),applyMultiplier:void 0!==$(a+" .paramModal-applyMultiplier:checked").val(),spawnTimer:void 0!==$(a+" .paramModal-spawnTimer:checked").val(),startTimerAutomatically:void 0!==$(a+" .paramModal-startTimerAutomatically:checked").val(),provideCounter:void 0!==$(a+" .paramModal-provideCounter:checked").val(),punishTime:void 0!==$(a+" .paramModal-punishTime:checked").val(),punishTimeMinutes:Number.parseFloat($(a+" .paramModal-punishTime-minutes").val()),hiddenTask:void 0!==$(a+" .paramModal-hiddenTask:checked").val()};if(s.value<=0)n.showToast("Invalid Parameter","Make sure to provide a value for this parameter.","warning");else{if("time"===s.valueType){if(!Number.isInteger(s.value))return void n.showToast("Invalid Parameter","Only integers are allowed while you use time.","warning");if("seconds"==s.timeUnit?s.valueType="seconds":"minutes"==s.timeUnit?s.valueType="minutes":"hours"==s.timeUnit?(s.valueType="minutes",s.value*=60):"days"==s.timeUnit&&(s.valueType="minutes",s.value*=1440),s.provideCounter=!1,s.punTier="",s.punishTime&&!Number.isInteger(s.punishTimeMinutes))return void n.showToast("Invalid Parameter","Only integers are allowed if you want to use the 'punish' option.","warning")}else if("others"===s.valueType){if(s.valueType=$(a+" .paramModal-valueType-others-text").val(),(""+s.valueType).length<=0)return void n.showToast("Invalid Parameter","If you use a custom type make sure to provide a name for it!","warning");if(s.spawnTimer=!1,s.startTimerAutomatically=!1,s.punishTime=!1,s.punishTimeMinutes=0,s.punTier="",s.provideCounter&&!Number.isInteger(s.value))return void n.showToast("Invalid Parameter","Only integers are allowed if you want to use the 'counter' option.","warning")}else"punishments"===s.valueType&&(s.spawnTimer=!1,s.startTimerAutomatically=!1,s.punishTime=!1,s.punishTimeMinutes=0,s.provideCounter=!1,s.hiddenTask=!1);Y.removeParam(e),V.params.push(s),Y.autoSaveUpdate("param")}}static openCustomExport(e){$("#modalContainer").append(J.generateCustomExportModal(e)),$("#customExportModal").modal({keyboard:!0}).modal("show").on("shown.bs.modal",(function(){$("#customExportModal .customExportModal-moduleTitle").trigger("focus"),$("#customExportModal select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){$("#customExportModal").modal("dispose").remove()}))}static addTask(e){let t=V.globalIndex++;Y.openTask({id:t,description:"",tags:[]}),Y.enableTaskReference(t,e)}static enableTaskReference(e,t){if(!e||!t)return!1;$(`#taskModal${e} .save-button, #taskModal${e} .save-button-area a.customLink`).on("click",(function(a){$(t).append(`<option value="${e}" selected>Task ${e}: ${Y.applyParamsToString(e,$(`#taskModal${e} .taskModal-description`).val())}</option>`),$(t).selectpicker("refresh").selectpicker("refresh")})),$("#taskModal"+e).attr("data-ref",t)}static editTask(e){Y.openTask(V.tasks.find((function(t){return t.id==e})))}static openTask(e){let t="#taskModal"+e.id;$("#modalContainer").append(J.generateTaskModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .taskModal-description").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(""+t).modal("dispose").remove()})),$(t+" .taskModal-description").on("input",(function(a){$(t+" .taskModal-description-preview").html("<b>Preview:</b> "+Y.applyParamsToString(e.id,a.target.value))}))}static removeTask(e){V.tasks=V.tasks.filter((function(t){return t.id!=e})),$("#taskModal"+e).is(":visible")?$("#taskModal"+e).modal("hide"):Y.refreshView()}static saveTaskAndNew(e){if(Y.saveTask(e)){const t=$("#taskModal"+e).attr("data-ref");Y.addTask(t)}}static saveTaskAndDuplicate(e){if(Y.saveTask(e)){let t=JSON.parse(JSON.stringify(V.tasks.find((function(t){return t.id==e}))));t.id=V.globalIndex++;let a=JSON.parse(JSON.stringify(V.params.filter((function(t){return t.taskId==e}))));for(const e of a){const a=e.id;e.id=V.globalIndex++,e.taskId=t.id,t.description=t.description.replace(new RegExp("\\$param"+a,"g"),"$param"+e.id),V.params.push(e)}Y.openTask(t),Y.enableTaskReference(t.id,$("#taskModal"+e).attr("data-ref"))}}static saveTask(e){let t={id:e,description:$(`#taskModal${e} .taskModal-description`).val(),tags:$(`#taskModal${e} select.taskModal-tags`).val()};return void 0===t.description||t.description.length<=0?(n.showToast("Invalid description","Please provide a description.","warning"),!1):(Y.removeTask(e),V.tasks.push(t),Y.autoSaveUpdate("task"),!0)}static handleImageSelection(e){$(".image-selection-btn",e).on("click",(function(t){$(".image-selection",e).click()})),$(".image-selection",e).on("change",(function(t){if(void 0!==t.target.files&&1===t.target.files.length){const a=new FileReader;a.onload=function(a){t.target.value="",Y.optimizeImage(a.target.result,e)},a.readAsDataURL(t.target.files[0])}}))}static optimizeImage(e,t){let a=function(e,a){$(".image-preview",t).attr("src",e),$(".image-preview",t).attr("data-optimized-url","")}.bind(this);n.optimizeImage(e,a)}static addClass(e){let t=V.globalIndex++;Y.openClass({id:t,title:"",subtitle:"",comment:"",prerequisites:[],days:[],description:"",tier:"beginner",tasks:[],exams:[],image:void 0,imageUrl:void 0,taskListSize:o.defaultTaskListSize,disableBonusAttendance:!1}),e&&($(`#classModal${t} .save-button, #classModal${t} .save-button-area a.customLink`).on("click",(function(a){$(e).append(`<option value="${t}" selected>Class ${t}: ${$(`#classModal${t} .classModal-title`).val()}</option>`),$(e).selectpicker("refresh").selectpicker("refresh")})),$("#classModal"+t).attr("data-ref",e))}static editClass(e){Y.openClass(V.classes.find((function(t){return t.id==e})))}static openClass(e){let t="#classModal"+e.id;$("#modalContainer").append(J.generateClassModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .classModal-title").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(t).modal("dispose").remove()})),Y.handleImageSelection(t)}static removeClass(e){V.classes=V.classes.filter((function(t){return t.id!=e})),$("#classModal"+e).is(":visible")?$("#classModal"+e).modal("hide"):Y.refreshView()}static saveClassAndNew(e){if(Y.saveClass(e)){const t=$("#classModal"+e).attr("data-ref");Y.addClass(t)}}static saveClass(e){let t="#classModal"+e,a={id:e,title:$(t+" .classModal-title").val(),subtitle:$(t+" .classModal-subtitle").val(),comment:$(t+" .classModal-comment").val(),prerequisites:$(t+" select.classModal-prerequisites").val().map(e=>Number.parseInt(e)),days:$(t+" select.classModal-days").val().map(e=>Number.parseInt(e)),description:$(t+" .classModal-description").val(),tier:$(`${t} input[name='classModal-tier${e}']:checked`).val(),tasks:$(t+" select.classModal-tasks").val().map(e=>Number.parseInt(e)),exams:$(t+" select.classModal-exams").val().map(e=>Number.parseInt(e)),image:$(t+" .image-preview").attr("src"),imageUrl:$(t+" .image-preview").attr("data-optimized-url"),taskListSize:Number.parseInt($(t+" .classModal-task-list-size").val()),disableBonusAttendance:$(`${t} #classModal-disable-bonus-attendance${e}`)[0].checked};return void 0===a.title||a.title.length<=0?(n.showToast("Invalid Title","Please provide a title.","warning"),!1):void 0===a.days||a.days.length<=0?(n.showToast("Invalid Selection","Please choose at least one day on which this class will be available.","warning"),!1):(Y.removeClass(e),V.classes.push(a),Y.autoSaveUpdate("class"),!0)}static addMajor(){Y.openMajor({id:V.globalIndex++,title:"",subtitle:"",description:"",prerequisites:[],exams:[],image:void 0,imageUrl:void 0})}static editMajor(e){Y.openMajor(V.majors.find((function(t){return t.id==e})))}static openMajor(e){let t="#majorModal"+e.id;$("#modalContainer").append(J.generateMajorModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .majorModal-title").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(t).modal("dispose").remove()})),Y.handleImageSelection(t)}static removeMajor(e){V.majors=V.majors.filter((function(t){return t.id!=e})),$("#majorModal"+e).is(":visible")?$("#majorModal"+e).modal("hide"):Y.refreshView()}static saveMajorAndNew(e){Y.saveMajor(e)&&Y.addMajor()}static saveMajor(e){let t="#majorModal"+e,a={id:e,title:$(t+" .majorModal-title").val(),subtitle:$(t+" .majorModal-subtitle").val(),description:$(t+" .majorModal-description").val(),prerequisites:$(t+" select.majorModal-prerequisites").val().map(e=>Number.parseInt(e)),exams:$(t+" select.majorModal-exams").val().map(e=>Number.parseInt(e)),image:$(t+" .image-preview").attr("src"),imageUrl:$(t+" .image-preview").attr("data-optimized-url")};return void 0===a.title||a.title.length<=0?(n.showToast("Invalid title","Please provide a title.","warning"),!1):(Y.removeMajor(e),V.majors.push(a),Y.autoSaveUpdate("major"),!0)}static addPunishment(){Y.openPunishment({id:V.globalIndex++,title:"",tier:"",punishment:"",image:void 0,imageUrl:void 0})}static editPunishment(e){Y.openPunishment(V.punishments.find((function(t){return t.id==e})))}static openPunishment(e){let t="#punishmentModal"+e.id;$("#modalContainer").append(J.generatePunishmentModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .punishmentModal-title").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(t).modal("dispose").remove()})),Y.handleImageSelection(t)}static removePunishment(e){V.punishments=V.punishments.filter((function(t){return t.id!=e})),$("#punishmentModal"+e).is(":visible")?$("#punishmentModal"+e).modal("hide"):Y.refreshView()}static savePunishmentAndNew(e){Y.savePunishment(e)&&Y.addPunishment()}static savePunishment(e){let t="#punishmentModal"+e,a={id:e,title:$(t+" .punishmentModal-title").val(),tier:$(`${t} input[name='punishmentModal-tier${e}']:checked`).val(),punishment:Number.parseInt($(t+" select.punishmentModal-punishment").val()),image:$(t+" .image-preview").attr("src"),imageUrl:$(t+" .image-preview").attr("data-optimized-url")};return void 0===a.title||a.title.length<=0?(n.showToast("Invalid title","Please provide a title.","warning"),!1):void 0===a.punishment||Number.isNaN(a.punishment)?(n.showToast("Invalid punishment","Please select a punishment!","warning"),!1):(Y.removePunishment(e),V.punishments.push(a),Y.autoSaveUpdate("punishment"),!0)}static addPartner(){Y.openPartner({id:V.globalIndex++,name:"",description:"",modifiers:[],image:void 0,imageUrl:void 0})}static editPartner(e){Y.openPartner(V.partners.find(t=>t.id==e))}static openPartner(e){let t="#partnerModal"+e.id;$("#modalContainer").append(J.generatePartnerModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .partnerModal-name").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(t).modal("dispose").remove()})),Y.handleImageSelection(t)}static removePartner(e){V.partners=V.partners.filter(t=>t.id!=e),$("#partnerModal"+e).is(":visible")?$("#partnerModal"+e).modal("hide"):Y.refreshView()}static savePartnerAndNew(e){Y.savePartner(e)&&Y.addPartner()}static savePartner(e){let t="#partnerModal"+e,a={id:e,name:$(t+" .partnerModal-name").val(),description:$(t+" .partnerModal-description").val(),modifiers:$(t+" select.partnerModal-modifiers").val().map(e=>Number.parseInt(e)),image:$(t+" .image-preview").attr("src"),imageUrl:$(t+" .image-preview").attr("data-optimized-url")};return void 0===a.name||a.name.length<=0?(n.showToast("Invalid name","Please provide a name for your partner.","warning"),!1):(Y.removePartner(e),V.partners.push(a),Y.autoSaveUpdate("partner"),!0)}static addClub(){Y.openClub({id:V.globalIndex++,name:"",description:"",tier:"normal",modifiers:[],comment:"",image:void 0,imageUrl:void 0})}static editClub(e){Y.openClub(V.clubs.find(t=>t.id==e))}static openClub(e){let t="#clubModal"+e.id;$("#modalContainer").append(J.generateClubModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .clubModal-name").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(t).modal("dispose").remove()})),Y.handleImageSelection(t)}static removeClub(e){V.clubs=V.clubs.filter(t=>t.id!=e),$("#clubModal"+e).is(":visible")?$("#clubModal"+e).modal("hide"):Y.refreshView()}static saveClubAndNew(e){Y.saveClub(e)&&Y.addClub()}static saveClub(e){let t="#clubModal"+e,a={id:e,name:$(t+" .clubModal-name").val(),comment:$(t+" .clubModal-comment").val(),description:$(t+" .clubModal-description").val(),tier:$(`${t} input[name='clubModal-tier${e}']:checked`).val(),modifiers:$(t+" select.clubModal-modifiers").val().map(e=>Number.parseInt(e)),image:$(t+" .image-preview").attr("src"),imageUrl:$(t+" .image-preview").attr("data-optimized-url")};return void 0===a.name||a.name.length<=0?(n.showToast("Invalid name","Please provide a name for your club.","warning"),!1):(Y.removeClub(e),V.clubs.push(a),Y.autoSaveUpdate("club"),!0)}static addModifier(e){let t=V.globalIndex++;Y.openModifier({id:t,modType:"",modVal:"",perkType:"",perkVal:"",tags:[]}),Y.enableModifierReference(t,e)}static enableModifierReference(e,t){if(!e||!t)return!1;$(`#modifierModal${e} .save-button, #modifierModal${e} .save-button-area a.customLink`).on("click",(function(a){$(t).append(`<option value="${e}" selected>Modifier ${e}</option>`),$(t).selectpicker("refresh").selectpicker("refresh")})),$("#modifierModal"+e).attr("data-ref",t)}static editModifier(e){Y.openModifier(V.modifiers.find(t=>t.id==e))}static openModifier(e){let t="#modifierModal"+e.id;$("#modalContainer").append(J.generateModifierModal(e)),$(t).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){$(t+" .modifierModal-modType").trigger("focus"),$(t+" select[multiple]").selectpicker()})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(t).modal("dispose").remove()})),$(t+" .modifierModal-modVal-text").on("change",(function(e){$(t+" .modifierModal-modifier-text").prop("checked",!0).trigger("change")})),$(t+" .modifierModal-modVal-difficulty").on("change",(function(e){$(t+" .modifierModal-modifier-difficulty").prop("checked",!0).trigger("change")})),$(t+" .modifierModal-perkVal-text").on("change",(function(e){$(t+" .modifierModal-perk-text").prop("checked",!0).trigger("change")})),$(t+" .modifierModal-perkVal-difficulty").on("change",(function(e){$(t+" .modifierModal-perk-difficulty").prop("checked",!0).trigger("change")}))}static removeModifier(e){V.modifiers=V.modifiers.filter(t=>t.id!=e),$("#modifierModal"+e).is(":visible")?$("#modifierModal"+e).modal("hide"):Y.refreshView()}static saveModifierAndNew(e){if(Y.saveModifier(e)){const t=$("#modifierModal"+e).attr("data-ref");Y.addModifier(t)}}static saveModifierAndDuplicate(e){if(Y.saveModifier(e)){const t=$("#modifierModal"+e).attr("data-ref");let a=JSON.parse(JSON.stringify(V.modifiers.find((function(t){return t.id==e}))));a.id=V.globalIndex++,Y.openModifier(a),Y.enableModifierReference(a.id,t)}}static saveModifier(e){let t="#modifierModal"+e,a={id:e,tags:$(t+" select.modifierModal-tags").val(),modType:$(`${t} input[name='modifierModal-modifier${e}']:checked`).val(),modVal:"",perkType:$(`${t} input[name='modifierModal-perk${e}']:checked`).val(),perkVal:""};if("difficulty"===a.modType){if(a.modVal=Number.parseInt($(t+" .modifierModal-modVal-difficulty").val()),Number.isNaN(a.modVal))return n.showToast("Invalid difficulty","Make sure to provide an integer value for the difficulty.","warning"),!1;if(a.modVal>100||a.modVal<1)return n.showToast("Invalid difficulty","Make sure to provide a value between 1 and 100 for the difficulty.","warning"),!1}else if("text"===a.modType&&(a.modVal=$(t+" .modifierModal-modVal-text").val(),void 0===a.modVal||a.modVal.length<=0))return n.showToast("Invalid text","Make sure to provide a value for the text.","warning"),!1;if("difficulty"===a.perkType){if(a.perkVal=Number.parseInt($(t+" .modifierModal-perkVal-difficulty").val()),Number.isNaN(a.perkVal))return n.showToast("Invalid difficulty","Make sure to provide an integer value for the difficulty.","warning"),!1;if(a.perkVal<-100||a.perkVal>-1)return n.showToast("Invalid difficulty","Make sure to provide a value between -1 and -100 for the difficulty.","warning"),!1}else if("text"===a.perkType&&(a.perkVal=$(t+" .modifierModal-perkVal-text").val(),void 0===a.perkVal||a.perkVal.length<=0))return n.showToast("Invalid text","Make sure to provide a value for the text.","warning"),!1;return Y.removeModifier(e),V.modifiers.push(a),Y.autoSaveUpdate("modifier"),!0}static getParams(e){let t="";for(const a of V.params.filter(t=>t.taskId==e).sort((e,t)=>e.id-t.id))t+=J.generateParamCard(a);return t.length<=0&&(t='<p class="pl-3">No params defined yet.</p>'),`<div class="row">${t}</div>`}static getTableColumns(e,t){let a="";for(const s of e)a+=`<th data-field="${s.id}" ${s.additional} `,Array.isArray(t)&&!t.includes(s.id)&&(a+='data-visible="false"'),a+=`>${s.text}</th>`;return a}static getTasksTable(){let e="",t=Y.restoredTableSettings("tasksTable");for(const t of V.tasks.sort((e,t)=>e.id-t.id))e+=J.generateTaskTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="tasksToolbar">\n \n </div>\n <table\n id="tasksTable"\n data-toolbar="#tasksToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:""},{id:"description",text:"Description",additional:""},{id:"tags",text:"Tags",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" data-width="25" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getTasks(){let e="";for(const t of V.tasks.sort((e,t)=>e.id-t.id))e+=J.generateTaskCard(t);return e.length<=0&&(e="No tasks defined yet."),`<div class="row">${e}</div>`}static getClassesTable(){let e="",t=Y.restoredTableSettings("classesTable");for(const t of V.classes.sort((e,t)=>e.id-t.id))e+=J.generateClassTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="classesToolbar">\n \n </div>\n <table\n id="classesTable"\n data-toolbar="#classesToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"tier",text:"Tier",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:'data-sortable="true"'},{id:"subtitle",text:"Subtitle",additional:'data-sortable="true"'},{id:"comment",text:"Comment",additional:""},{id:"description",text:"Description",additional:""},{id:"available",text:"Available On",additional:""},{id:"prerequisite",text:"Prerequisite classes",additional:""},{id:"tasks",text:"Tasks",additional:""},{id:"exams",text:"Exam tasks",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getClasses(){let e="";for(const t of V.classes.sort((e,t)=>e.id-t.id))e+=J.generateClassCard(t);return e.length<=0&&(e="No classes defined yet."),`<div class="row">${e}</div>`}static getRouletteTable(){let e="",t=Y.restoredTableSettings("rouletteTable");for(const t of V.rouletteOptions)e+=J.generateRouletteTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="rouletteToolbar">\n \n </div>\n <table\n id="rouletteTable"\n data-toolbar="#rouletteToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="1"\n data-show-pagination-switch="true"\n data-pagination="false"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:'data-sortable="true"'},{id:"description",text:"Description",additional:""},{id:"probability",text:"Probability",additional:""},{id:"rollPunishment",text:"Roll Punishment",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getHelpTable(){let e="",t=Y.restoredTableSettings("helpTable");for(const t of V.help)e+=J.generateHelpTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="helpToolbar">\n \n </div>\n <table\n id="helpTable"\n data-toolbar="#helpToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="1"\n data-show-pagination-switch="true"\n data-pagination="false"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:'data-sortable="true"'},{id:"text",text:"Content",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getTagsTable(){let e="",t=Y.restoredTableSettings("tagsTable");for(const t of V.tags)e+=J.generateTagTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="tagsToolbar">\n \n </div>\n <table\n id="tagsTable"\n data-toolbar="#tagsToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="false"\n data-pagination="false"\n data-id-field="tag"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"tag",text:"Tag",additional:'data-sortable="true" data-switchable="false"'},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getRoulette(){let e="";for(const t of V.rouletteOptions)e+=J.generateRouletteCard(t);return e.length<=0&&(e="No roulette options defined yet."),`<div class="row">${e}</div>`}static getHelp(){let e="";for(const t of V.help)e+=J.generateHelpCard(t);return e.length<=0&&(e="No help defined yet."),`<div class="row">${e}</div>`}static getTags(){let e="";for(const t of V.tags)e+=J.generateTagCard(t);return e.length<=0&&(e="No tags defined yet."),`<div class="row">${e}</div>`}static getMajorsTable(){let e="",t=Y.restoredTableSettings("majorsTable");for(const t of V.majors.sort((e,t)=>e.id-t.id))e+=J.generateMajorTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="majorsToolbar">\n \n </div>\n <table\n id="majorsTable"\n data-toolbar="#majorsToolbar"\n data-search="true" \n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true" \n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:'data-sortable="true"'},{id:"subtitle",text:"Subtitle",additional:'data-sortable="true"'},{id:"description",text:"Description",additional:""},{id:"prerequisite",text:"Prerequisite classes",additional:""},{id:"exams",text:"Exam tasks",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getMajors(){let e="";for(const t of V.majors.sort((e,t)=>e.id-t.id))e+=J.generateMajorCard(t);return e.length<=0&&(e="No majors defined yet."),`<div class="row">${e}</div>`}static getPunishmentsTable(){let e="",t=Y.restoredTableSettings("punishmentsTable");V.punishments=V.punishments.filter(e=>void 0!==V.tasks.find(t=>t.id===e.punishment));for(const t of V.punishments.sort((e,t)=>e.id-t.id))e+=J.generatePunishmentTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="punishmentsToolbar">\n \n </div>\n <table\n id="punishmentsTable"\n data-toolbar="#punishmentsToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"tier",text:"Tier",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:'data-sortable="true"'},{id:"task",text:"Punishment task",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getPunishments(){let e="";for(const t of V.punishments.sort((e,t)=>e.id-t.id))e+=J.generatePunishmentCard(t);return e.length<=0&&(e="No punishments defined yet."),`<div class="row">${e}</div>`}static getPartnersTable(){let e="",t=Y.restoredTableSettings("partnersTable");for(const t of V.partners.sort((e,t)=>e.id-t.id))e+=J.generatePartnerTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="partnersToolbar">\n \n </div>\n <table\n id="partnersTable"\n data-toolbar="#partnersToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"name",text:"Name",additional:'data-sortable="true"'},{id:"description",text:"Description",additional:""},{id:"modifiers",text:"Available modifiers",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getPartners(){let e="";for(const t of V.partners.sort((e,t)=>e.id-t.id))e+=J.generatePartnerCard(t);return e.length<=0&&(e="No partners defined yet."),`<div class="row">${e}</div>`}static getClubsTable(){let e="",t=Y.restoredTableSettings("clubsTable");for(const t of V.clubs.sort((e,t)=>e.id-t.id))e+=J.generateClubTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="clubsToolbar">\n \n </div>\n <table\n id="clubsTable"\n data-toolbar="#clubsToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"tier",text:"Tier",additional:'data-sortable="true"'},{id:"name",text:"Name",additional:'data-sortable="true"'},{id:"comment",text:"Comment",additional:""},{id:"description",text:"Description",additional:""},{id:"modifiers",text:"Available modifiers",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getClubs(){let e="";for(const t of V.clubs.sort((e,t)=>e.id-t.id))e+=J.generateClubCard(t);return e.length<=0&&(e="No clubs defined yet."),`<div class="row">${e}</div>`}static getModifiersTable(){let e="",t=Y.restoredTableSettings("modifiersTable");for(const t of V.modifiers.sort((e,t)=>e.id-t.id))e+=J.generateModifierTableEntry(t);return`\n <div class="row" style="overflow-x: auto">\n <div id="modifiersToolbar">\n \n </div>\n <table\n id="modifiersTable"\n data-toolbar="#modifiersToolbar"\n data-search="true"\n data-show-toggle="true"\n data-show-fullscreen="true"\n data-show-columns="true"\n data-show-columns-toggle-all="true" \n data-minimum-count-columns="2"\n data-show-pagination-switch="true"\n data-pagination="true"\n data-id-field="id"\n data-page-list="[10, 25, 50, 100, all]"\n data-sortable="true"\n\n data-search-text="${t.searchText}"\n data-sort-name="${t.sortName}"\n data-sort-order="${t.sortOrder}"\n data-page-number="${t.pageNumber}"\n data-page-size="${t.pageSize}"\n >\n <thead>\n <tr>\n ${Y.getTableColumns([{id:"id",text:"ID",additional:'data-sortable="true"'},{id:"title",text:"Title",additional:""},{id:"modifier",text:"Modifier",additional:""},{id:"perk",text:"Perk",additional:""},{id:"actions",text:"Actions",additional:'data-switchable="false" class="actionsTableField"'}],t.visibleColumns)}\n </tr>\n </thead>\n <tbody>\n ${e}\n </tbody>\n </table>\n </div>\n `}static getModifiers(){let e="";for(const t of V.modifiers.sort((e,t)=>e.id-t.id))e+=J.generateModifierCard(t);return e.length<=0&&(e="No modifiers defined yet."),`<div class="row">${e}</div>`}static refreshView(){let e=$(window).scrollTop();n.getSetting("useTableView")?($("#tagsCardContainer").html(Y.getTagsTable()),$("#tasksCardContainer").html(Y.getTasksTable()),$("#classesCardContainer").html(Y.getClassesTable()),$("#majorsCardContainer").html(Y.getMajorsTable()),$("#punishmentsCardContainer").html(Y.getPunishmentsTable()),$("#clubsCardContainer").html(Y.getClubsTable()),$("#partnersCardContainer").html(Y.getPartnersTable()),$("#modifiersCardContainer").html(Y.getModifiersTable()),$("#helpCardContainer").html(Y.getHelpTable()),$("#rouletteCardContainer").html(Y.getRouletteTable())):($("#tagsCardContainer").html(Y.getTags()),$("#tasksCardContainer").html(Y.getTasks()),$("#classesCardContainer").html(Y.getClasses()),$("#majorsCardContainer").html(Y.getMajors()),$("#punishmentsCardContainer").html(Y.getPunishments()),$("#clubsCardContainer").html(Y.getClubs()),$("#partnersCardContainer").html(Y.getPartners()),$("#modifiersCardContainer").html(Y.getModifiers()),$("#helpCardContainer").html(Y.getHelp()),$("#rouletteCardContainer").html(Y.getRoulette())),Y.resetGeneral(),window.scrollTo(0,e),document.querySelector("html").dispatchEvent(new CustomEvent("mappingRefreshed",{}))}static async import(e){const t=s.getInstance(),a={title:t.T("mapping-import-confirmation-header","Import an editable map"),text:t.T("mapping-import-confirmation-conent","Are you sure that you want to import this map? <b>Make sure to only load maps from trusted sources. Proceed at your own risk.</b>")},i={title:t.T("mapping-import-failed-header","Map import failed"),text:t.T("mapping-import-failed-content","During the import for the selected file an error occured. You can check the console for more information.")},n={title:t.T("mapping-file-import-compression-error-header","Compression unavailable"),text:t.T("mapping-file-import-compression-error-content","The file you tried to import is compressed. Sadly a library required to uncompress it is unavailable.")},o={title:t.T("mapping-file-import-aborted-header","Import aborted"),text:t.T("mapping-file-import-aborted-content","The file you tried to import included untrusted code. To protect you the import was aborted.")},r=await _.runImport(e,i,a,n,o);Y.autoSaveReset(),Y.importMapStorage(r)}static importMapStorage(e){Y.reset(),V=e,null==V.help&&(V.help=[]),null==V.rouletteOptions&&(V.rouletteOptions=[]),Y.refreshView()}static async export(){G=V.globalIndex,await _.runExport("pu_map",V)}static prepareModifierForExport(e){const t=Object.assign({},V.modifiers.find(t=>t.id==e));let a="",s={id:""+e,job:"",perk:"",tags:t.tags,modType:t.modType,modVal:t.modVal,perkType:t.perkType,perkVal:t.perkVal};return a=void 0!==t.tags&&t.tags.length>0?t.tags.join(", "):"all","difficulty"==t.modType?(t.modVal<0&&(t.modVal*=-1),s.job=`Increase difficulty for ${a} tasks by ${t.modVal}%.`):"punishment"==t.modType?(s.modVal=1,s.job="Roll a random punishment."):s.job+=""+t.modVal,"difficulty"==t.perkType?(t.perkVal<0&&(t.perkVal*=-1),s.perk=`Decrease difficulty for ${a} tasks by ${t.perkVal}%.`):"skip"==t.perkType?(s.perkVal=1,s.perk="Skip 1 class every day."):"attendance"==t.perkType?(s.perkVal=1,s.perk=`Add an additional attendance for ${a} classes.`):"resetRoulette"==t.perkType?(s.perkVal=1,s.perk="Reset roulette and roll again."):s.perk=""+t.perkVal,s}static prepareTaskForExport(e){const t=Object.assign({},V.tasks.find(t=>t.id==e)),a={id:""+e,task:t.description,tags:t.tags,parameters:{}};for(const t of V.params.filter(t=>t.taskId==e))a.parameters[t.id]={value:t.value,unit:t.valueType,timerInfo:t.description,applyMultiplier:t.applyMultiplier,spawnTimer:t.spawnTimer,startTimerAutomatically:t.startTimerAutomatically,provideCounter:t.provideCounter,hiddenTask:t.hiddenTask,punTier:t.punTier,punishTime:t.punishTime,punishTimeMinutes:t.punishTimeMinutes};return a}static async directExportTask(e){let t=Y.prepareTaskForExport(e);t.id=t.id+"_CUST",await _.runExport("pu_task",t)}static exportPlayableModule(e){let t={mapId:V.mapId||Y.uuidv4(),moduleId:$("#customExportModal .customExportModal-moduleId").val(),general:{title:$("#customExportModal .customExportModal-moduleTitle").val(),description:$("#customExportModal .customExportModal-moduleDescription").val()},majors:$("#customExportModal select.customExportModal-majors").val().map(e=>Number.parseInt(e)),classes:$("#customExportModal select.customExportModal-classes").val().map(e=>Number.parseInt(e)),clubs:$("#customExportModal select.customExportModal-clubs").val().map(e=>Number.parseInt(e)),partners:$("#customExportModal select.customExportModal-partners").val().map(e=>Number.parseInt(e)),punishments:$("#customExportModal select.customExportModal-punishments").val().map(e=>Number.parseInt(e)),rouletteOptions:$("#customExportModal select.customExportModal-rouletteOptions").val().map(e=>Number.parseInt(e))};if(void 0===t.moduleId||t.moduleId.length<=0)n.showToast("Invalid module ID","Please provide a module ID.","warning");else if(!t.general||void 0===t.general.title||t.general.title.length<=0)n.showToast("Invalid Title","Please provide a title.","warning");else{$("#customExportModal").is(":visible")&&$("#customExportModal").modal("hide");for(const e of V.majors.filter(e=>t.majors.includes(e.id)))t.classes=t.classes.concat(e.prerequisites);for(const e of t.classes)t.classes=t.classes.concat(Y.recursiveGetPrerequisites(e));t.classes=[...new Set(t.classes)],e(Y.getPlayable(t))}}static getPlayableImage(e){return void 0!==e.imageUrl&&e.imageUrl.length>0?e.imageUrl:void 0!==e.image&&e.image.length>0?e.image:n.extendDistPath()+"img/unknown.jpg"}static recursiveGetPrerequisites(e){let t=[];const a=V.classes.find(t=>t.id==e);if(!a)return t;t=t.concat(a.prerequisites);for(const e of t)t=t.concat(Y.recursiveGetPrerequisites(e));return t}static getPlayable(e){let t={mapId:V.mapId||Y.uuidv4(),general:V.general,classes:{},majors:{},partners:{},clubs:{},punishments:{},rouletteOptions:{}};void 0!==e&&(t.mapId=e.mapId,t.moduleId=e.moduleId,t.general=e.general),t.general.help=[],void 0!==V.help&&V.help.length>0&&(t.general.help=t.general.help.concat(V.help));for(const a of V.rouletteOptions.filter(t=>void 0===e||e.rouletteOptions.includes(t.id)))t.rouletteOptions[a.id]={id:""+a.id,type:"roulette_option",title:a.title,description:a.description,probability:a.probability,rollPunishment:a.rollPunishment};for(const a of V.classes.filter(t=>void 0===e||e.classes.includes(t.id))){t.classes[a.id]={id:""+a.id,type:"class",name:a.title,name2:a.subtitle,comment:a.comment,prerequisites:a.prerequisites,days:a.days,description:a.description,tier:a.tier,image:Y.getPlayableImage(a),tasks:{},taskListSize:a.taskListSize,disableBonusAttendance:a.disableBonusAttendance};for(const e of a.tasks)t.classes[a.id].tasks[e]=Y.prepareTaskForExport(e);for(const e of a.exams)t.classes[a.id].tasks[e]=Y.prepareTaskForExport(e),t.classes[a.id].tasks[e].isExam=!0}for(const a of V.majors.filter(t=>void 0===e||e.majors.includes(t.id))){t.majors[a.id]={id:""+a.id,type:"major",name:a.title,name2:a.subtitle,description:a.description,prerequisites:a.prerequisites,image:Y.getPlayableImage(a),tasks:{}};for(const e of a.exams)t.majors[a.id].tasks[e]=Y.prepareTaskForExport(e),t.majors[a.id].tasks[e].isExam=!0}for(const a of V.partners.filter(t=>void 0===e||e.partners.includes(t.id))){t.partners[a.id]={id:""+a.id,type:"partner",name:a.name,name2:"",tier:"1",description:a.description,image:Y.getPlayableImage(a),perks:{}};for(const e of a.modifiers)t.partners[a.id].perks[e]=Y.prepareModifierForExport(e)}for(const a of V.clubs.filter(t=>void 0===e||e.clubs.includes(t.id))){t.clubs[a.id]={id:""+a.id,type:"club",name:a.name,comment:a.comment,tier:a.tier,description:a.description,image:Y.getPlayableImage(a),perks:{}};for(const e of a.modifiers)t.clubs[a.id].perks[e]=Y.prepareModifierForExport(e)}for(const a of V.punishments.filter(t=>void 0===e||e.punishments.includes(t.id)))t.punishments[a.id]={id:""+a.id,type:"punishment",name:a.title,tier:a.tier,image:Y.getPlayableImage(a),tasks:{}},t.punishments[a.id].tasks[a.punishment]=Y.prepareTaskForExport(a.punishment);return t}static openImageSelection(e={id:"",image:"",imageUrl:""},t){let a="#imageModal"+e.id;$("#modalContainer").append(J.generateImageModal(e,t)),$(a).modal({keyboard:!0}).modal("show").on("keydown",Y.handleModalKeyDown).on("shown.bs.modal",(function(){})).on("hidden.bs.modal",(function(e){Y.refreshView(),$(a).modal("dispose").remove()})),Y.handleImageSelection(a)}static exportPlayableWithCustomImage(e){const t={id:Y.uuidv4(),image:"",imageUrl:""};Y.openImageSelection(t,t=>{let a="#imageModal"+t;const s=n.dataURLtoFile($(a+" .image-preview").attr("src"),"example.jpg");_.runExport("pu_game_map",e,s),$(""+a).is(":visible")?$(""+a).modal("hide"):Y.refreshView()})}static async exportPlayable(e){await _.runExport("pu_game_map",e)}static uuidv4(){return([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,e=>(e^crypto.getRandomValues(new Uint8Array(1))[0]&15>>e/4).toString(16))}static resetButton(){n.requestUserConfirmation("Reset current map","Are you sure that you want to reset the current map? <b>Make sure you exported your progress!</b>",()=>{Y.autoSaveReset(),Y.reset()})}static gotoFullPage(){n.requestUserConfirmation("Goto Mapping Tool","Do you want to open the Mapping Tool in a separat tab? It's not recommended that you bookmark the Mapping Tool by itself, since you will not receive any updates!",()=>{window.open(window.location.href,"_blank")})}static reset(){V={version:1,mapId:Y.uuidv4(),globalIndex:0,general:{},tags:[],majors:[],classes:[],tasks:[],params:[],punishments:[],clubs:[],partners:[],modifiers:[],help:[],rouletteOptions:[]},Y.refreshView()}static isChanged(){return V.globalIndex>0&&G<V.globalIndex}static autoSaveUpdate(e=""){try{localStorage.setItem("mappingAutoSave",n.compressJSObjectToString(V))}catch(e){console.error(e)}}static autoSaveReset(){localStorage.removeItem("mappingAutoSave")}static autoSaveRestore(){try{const e=localStorage.getItem("mappingAutoSave");if(!e)return;Y.importMapStorage(JSON.parse(n.decompressJsonFromString(e)))}catch(e){console.error(e)}}static registerWarning(){window.addEventListener("beforeunload",(function(e){Y.isChanged()&&Y.autoSaveUpdate("unload")}))}}class _{constructor(){this.inAppNavigation(`${window.location.search}${window.location.hash}`),window.onpopstate=function(e){e.state&&e.state.url?this.inAppNavigation(e.state.url):e.path&&Array.isArray(e.path)&&e.path.length>0&&this.inAppNavigation(`${e.path[0].location.search}${e.path[0].location.hash}`)}.bind(this)}static getInstance(){return void 0===this.instance&&(this.instance=new _),this.instance}static get navigationData(){const e=s.getInstance();return{home:{id:"home",text:e.T("navigation-home","Home"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:N,requirements:[]},majors:{id:"majors",text:e.T("navigation-majors","Majors"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:C,requirements:["map"]},major:{id:"major",text:e.T("navigation-major","Major"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:M,requirements:["map"]},classes:{id:"classes",text:e.T("navigation-classes","Classes"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:P,requirements:["map","major"]},class:{id:"class",text:e.T("navigation-class","Class"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:A,requirements:["map","major"]},clubs:{id:"clubs",text:e.T("navigation-clubs","Clubs"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:m,requirements:["map","major"]},club:{id:"club",text:e.T("navigation-club","Club"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:g,requirements:["map","major"]},partners:{id:"partners",text:e.T("navigation-partners","Partners"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:v,requirements:["map","major"]},partner:{id:"partner",text:e.T("navigation-partner","Partner"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:b,requirements:["map","major"]},punishments:{id:"punishments",text:e.T("navigation-punishments","Punishments"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:y,requirements:["map","major"]},punishment:{id:"punishment",text:e.T("navigation-punishment","Punishment"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:w,requirements:["map","major"]},schedule:{id:"schedule",text:e.T("navigation-schedule","Schedule"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:R,requirements:["map","major"]},progress:{id:"progress",text:e.T("navigation-progress","Progress"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:k,requirements:["map","major"]},roulette:{id:"roulette",text:e.T("navigation-roulette","Roulette"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:q,requirements:["map","major","rouletteEnabled"]},help:{id:"help",text:e.T("navigation-help","Help"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:j,requirements:["map"]},settings:{id:"settings",text:e.T("navigation-settings","Settings"),enabled:!0,isInNav:!0,autoHideAllowed:!0,handledBy:D,requirements:[]},mapping:{id:"mapping",text:e.T("navigation-mapping","Mapping"),enabled:!0,isInNav:!1,autoHideAllowed:!1,handledBy:J,requirements:[]},graduated:{id:"graduated",text:e.T("navigation-graduated","Graduated"),enabled:!0,isInNav:!1,autoHideAllowed:!0,handledBy:F,requirements:["map","majorFinished"]},disclaimer:{id:"disclaimer",text:e.T("navigation-disclaimer","Disclaimer"),enabled:!0,isInNav:!1,autoHideAllowed:!1,handledBy:U,requirements:[]}}}setParams(e){const t=new URLSearchParams(e.replace(/#.*?$/,""));this.allParams={};for(const[e,a]of t)this.allParams[e]=a}get params(){return this.allParams}hasParameter(e){return this.allParams.hasOwnProperty(e)}getParameter(e){return this.allParams[e]}navigationCheck(e){if(!this.params)return;const t=this.hasParameter("pageType")?this.getParameter("pageType"):"getView",a=e.expectedParameters[t];if(a){if(!(a.length>0))return $("main").html(e[t]()),void this.scrollToAnchor();{let s={};if(a.forEach(e=>{this.params.hasOwnProperty(e)&&(s[e]=this.params[e])}),a.length===Object.keys(s).length)return $("main").html(e[t](s)),void this.scrollToAnchor();this.navigateBack()}}else this.navigateBack()}navigatePage(e){if("back"===e)return void this.navigateBack();const t=_.navigationData[e];t&&t.enabled&&this.requirementsMet(t.requirements)?this.navigationCheck(t.handledBy):_.navigate("?page=home")}inAppNavigation(e){_.checkForUnsavedChanges(()=>{wi.reset(),u.getInstance().resetTaskIntervals(),this.setParams(e);const t=this.getParameter("page"),a=this.getParameter("pageType");if(!(n.getAcceptedDisclaimer()||this.hasParameter("page")&&"disclaimer"===t)){let e="";return window.location.search.length>0&&(e="&redirect="+encodeURIComponent(btoa(window.location.search))),void _.navigate("?page=disclaimer"+e)}$("#nav-auto").html(this.getNavigationBarHtml()),this.hasParameter("page")?($("#nav-"+t).addClass("active"),window.location.search.includes("page=back")||a&&"getView"!=a||(e!==`${window.location.search}${window.location.hash}`?window.history.pushState(_.getHistoryObj(e),t,e):e==`${window.location.search}${window.location.hash}`&&window.history.replaceState(_.getHistoryObj(e,!0),t,e)),this.navigatePage(t)):_.navigate("?page=home"),document.querySelector("html").dispatchEvent(new CustomEvent("navigationFinished",{})),n.getTutorial().includes(t.concat(";"))||H.startTour(t),$("#customCarousel").on("slid.bs.carousel",(function(e){let t=`${window.location.search}#slide${e.to}`;window.history.pushState(_.getHistoryObj(t),null,t)}))})}navigateBack(){_.navigateBack()}scrollToAnchor(){_.scrollToAnchor()}getNavigationBarHtml(){return _.getNavigationBarHtml()}requirementsMet(e){return _.requirementsMet(e)}getCreditsHtml(){return _.getCreditsHtml()}reload(){_.reload()}inAppReload(){_.inAppReload()}static getHistoryObj(e,t){if(t&&window.history.state&&window.history.state.url&&window.history.state.prevUrl)return{url:window.history.state.url,prevUrl:window.history.state.url.prevUrl,scrollHeight:$(window).scrollTop(),scrollHeightNew:$(window).scrollTop()};{let t=0;return window.history.state&&window.history.state.scrollHeightNew&&window.history.state.scrollHeightNew>0&&(t=window.history.state.scrollHeightNew),{url:e,prevUrl:`${window.location.search}${window.location.hash}`,scrollHeight:t,scrollHeightNew:$(window).scrollTop()}}}static getCreditsHtml(){const e=S.collectedCredits;return!e||e<=0?"":`<li class="nav-item">\n <span id="credits" class="text-warning nav-link">\n ${s.getInstance().T("credits","Credits")}: ${S.collectedCredits}/${p.requiredCreditsForGraduation}\n </span>\n </li>`}static getNavigationBarHtml(){let e=""+_.getCreditsHtml(),t=_.navigationData,a=0;for(let s in t){let i=t[s];i.enabled&&(i.isInNav&&_.requirementsMet(i.requirements)&&(e+=` <li id="nav-${i.id}" class="nav-item">\n <a class="nav-link" onClick="${wi.register(()=>{_.getInstance().inAppNavigation("?page="+i.id)})}">${i.text}</a>\n </li>`,a++))}return a<=0&&$("nav.navbar .navbar-toggler").addClass("collapse"),e}static requirementsMet(e){if(!e||null==e||e.length<=0)return!0;for(let t of e){if("major"===t&&!I.isAnyMajorActive())return!1;if("map"===t&&!u.getInstance().isMapLoaded)return!1;if("rouletteEnabled"===t&&!p.rouletteData)return!1;if("majorFinished"===t&&!u.getInstance().anyMajorFinished)return!1}return!0}static scrollToAnchor(){if(window.history.state&&window.history.state.scrollHeight&&window.history.state.scrollHeight>0)window.scrollTo(0,window.history.state.scrollHeight);else{{const t=window.location.hash;if(t.length>0){let a=t.match(/^#slide(\d)+$/);if(null===a){var e=$(""+t);return void $("html,body").animate({scrollTop:e.offset().top},"slow")}return void $("#customCarousel").carousel(Number.parseInt(a[1]))}}window.scrollTo(0,0)}}static checkForUnsavedChanges(e){const t=u.getInstance();t.wasSavegameChanged?t.setSavegame(e):e()}static reload(){_.checkForUnsavedChanges(()=>{location.reload()})}static inAppReload(){_.getInstance().inAppNavigation(window.location.search)}static replaceHistory(e){try{window.history.replaceState(_.getHistoryObj(e),null,e)}catch(e){console.error("failed to update history")}}static navigateBack(){_.checkForUnsavedChanges(()=>{null!==window.history.state&&void 0!==window.history.state.prevUrl?_.getInstance().inAppNavigation(window.history.state.prevUrl):history.back()})}static navigate(e){_.checkForUnsavedChanges(()=>{window.location.href=e})}static removeModuleFromMapId(e){return e.replace(/\(.*?\)/gm,"")}static async getSavegameData(e){if(n.shouldExportComplete()){const t=u.getInstance().mapId,a=u.getInstance().subMaps;let s={mapId:t,primaryMapData:{},subMapData:[],subMaps:a,saveGame:u.getInstance().getSavegame(),settings:n.getSettings()};const i=()=>new Promise((e,t)=>{u.getDBInstance().getMaps(e,t)});try{const e=await i();for(const i of e)i.mapData.mapId===t?s.primaryMapData=i.mapData:a.includes(i.mapData.mapId)&&s.subMapData.push(i.mapData);s.primaryMapData.mapId=_.removeModuleFromMapId(s.primaryMapData.mapId);for(const e of s.subMapData)e.mapId=_.removeModuleFromMapId(e.mapId);return s}catch(t){throw n.showToast(e.title,e.text,"danger"),"loading maps failed"}}return{mapId:u.getInstance().mapId,saveGame:u.getInstance().getSavegame()}}static async runClipboardExport(e,t={title:"",text:""},a={title:"",text:""},s={title:"",text:""}){const i=async()=>{try{const a=await _.getSavegameData(t);return`${e}:${n.compressJSObjectToString(a)}`}catch(e){throw e}};try{const e=new ClipboardItem({"text/plain":i().then(e=>new Blob([e],{type:"text/plain"}))});return await navigator.clipboard.write([e]),void n.showToast(a.title,a.text,"success")}catch(e){try{await navigator.clipboard.writeText(await i()),n.showToast(a.title,a.text,"success")}catch(t){n.showToast(s.title,`${s.text} [${e}][${t}]`,"danger")}}}static async runClipboardImport(e,t={title:"",text:""},a={title:"",text:""},s={title:"",text:""}){try{a&&await(i=a.title,o=a.text,new Promise(e=>{n.requestUserConfirmation(i,o,e)}));let t=await navigator.clipboard.readText();if(!t)throw"Empty file.";let r=t.split(":");if(2!==r.length)throw"Invalid clipboard";if(r[0]!==e)throw"Invalid import action";let l=n.decompressJsonFromString(r[1]);if(n.hasScript(l))throw n.showToast(s.title,s.text,"danger"),"Script found!";return JSON.parse(l)}catch(e){throw n.showToast(t.title,t.text),"Failed to import the clipboard."}var i,o}static async runExport(e,t,a){const s=n.isCompleteSavegame(t);"pu_save"==e&&s&&(e="pu_complete");const i=o.exportOptions[e];let r,l,d,c,p;if(n.getSetting("useImageExport")){r=i.imageExportExtension,a||(a=await n.loadImageFile(`/${n.extendDistPath()}img/${i.srcImage}.jpg`));let e=await n.encryptExportImage(t,a);l=e.ImageType,c=new Blob([e.imageData],{type:e.ImageType})}else t=n.shouldCompress()||s?n.compressJSObjectToString(t):JSON.stringify(t),r=i.textExportExtension,l=n.shouldCompress()||s?o.exportCompressedType:o.exportTextType,d=t,c=new Blob([t],{type:l});let m=n.getNow().format("YYYY-MM-DD_HH-mm"),g=`${i.exportFilenamePrefix}${m}.${r}`,h="";n.getSetting("useBase64Export")?(h=`download="${g}"`,p=`data:${l};base64,${btoa(d)}`):n.getSetting("useDynamicExport")?(p="/dynamic/"+g,u.getInstance().setExportStorageItem(g,c,l)):(h=`download="${g}"`,p=window.URL.createObjectURL(c));let v=$(`<a style="display: none" target="_blank" ${h} href="${p}"></a>`);$("body").append(v),v[0].click(),setTimeout(function(){window.URL.revokeObjectURL(p),v.remove()}.bind(this),5e3)}static async runImport(e,t={title:"",text:""},a={title:"",text:""},s={title:"",text:""},i={title:"",text:""}){let r=e.target.files[0];if(!r)return;e.target.value="";try{let e;if(a&&await(c=a.title,u=a.text,new Promise(e=>{n.requestUserConfirmation(c,u,e)})),o.isImage(r.type)){if(!n.canCompress())throw n.showToast(s.title,s.text,"warning"),"Compression not available!";if(e=await(l=r,d=!0,new Promise((e,t)=>{const a=new FileReader;a.onload=t=>e(d?n.decryptExportImage(t.target.result):new Uint8Array(t.target.result)),a.onerror=e=>t(e),a.readAsArrayBuffer(l)})),n.hasScript(e))throw n.showToast(i.title,i.text,"danger"),"Script found!";return JSON.parse(e)}if(e=await(e=>new Promise((t,a)=>{const s=new FileReader;s.onload=e=>t(e.target.result),s.onerror=e=>a(e),s.readAsText(e)}))(r),!e)throw"Empty file.";if(n.hasScript(e))throw n.showToast(i.title,i.text,"danger"),"Script found!";if("string"==typeof e&&e.startsWith("{"))return JSON.parse(e);if(!n.canCompress())throw n.showToast(s.title,s.text,"warning"),"Compression not available!";if(e=n.decompressJsonFromString(e),n.hasScript(e))throw n.showToast(i.title,i.text,"danger"),"Script found!";return JSON.parse(e)}catch(e){throw n.showToast(t.title,t.text),"Failed to import the selected file."}var l,d,c,u}}$(document).ready(()=>{!function(e){const t=document.querySelectorAll(`link[title="${e}"][rel="stylesheet"]`);(!e||e.length<=0||t.length<=0)&&(e="darkly");const a=document.querySelectorAll('link[title][rel="stylesheet"]');for(const t of a)t.title==e?t.disabled=!1:t.disabled=!0;const s=o.styles[e],i=document.querySelector("body nav.navbar");if(i)for(let e=0;e<i.classList.length;e++){const t=i.classList[e];"navbar-expand-lg"!==t&&t.startsWith("navbar-")?i.classList.replace(t,"navbar-"+s.navbar):t.startsWith("bg-")&&i.classList.replace(t,"bg-"+s.bg)}}(n.getSetting("style")),moment.relativeTimeThreshold("s",60),moment.relativeTimeThreshold("m",60),moment.relativeTimeThreshold("h",24),moment.relativeTimeThreshold("d",31),moment.relativeTimeThreshold("M",12),moment.relativeTimeThreshold("y",365),moment.relativeTimeRounding(Math.floor);const e=function(e){const t=new URLSearchParams(e.replace(/#.*?$/,""));let a=[];for(const[e,s]of t)a[e]=s;return a}(window.location.search);e.hasOwnProperty("lang")?(n.setSetting("language","en"),s.createInstance(e.lang)):n.getSetting("language")?s.createInstance(n.getSetting("language")):s.createInstance(n.getLanguage()),document.getElementById("newUpdateAvailable").innerHTML='<i class="fas fa-sync pr-2"></i> '+s.getInstance().T("update-notification","A new update is available, click to activate it now."),document.querySelector("html").addEventListener("navigationFinished",()=>{$('[data-toggle="popover"]').popover({html:!0,placement:"auto",delay:{show:0,hide:100}}).click((function(e){setTimeout((function(){let t=void 0;(t=e.currentTarget.attributes["aria-describedby"])&&$("#"+t.value).popover("hide")}),5e3)})),n.showScheduledToast(),x.checkForFinishedTasks(),function(){let e=function(){$("nav.navbar .navbar-toggler").is(":visible")&&$("nav.navbar .navbar-collapse").collapse("hide")};document.querySelectorAll("nav.navbar li a").forEach(t=>{t.addEventListener("click",e)})}()});let t=function(){_.getInstance(),x.checkForSkippedClasses(),$("#backToTop").on("click",()=>{n.scrollBackToTop()}),window.getFileData=async function(e){const t=u.getInstance().getExportStorageItem(e);return t||{error:"Unknown filename"}},window.addEventListener("beforeunload",(function(e){_.checkForUnsavedChanges(()=>{})})),n.getSetting("enableAutoHide")&&_.navigationData[_.getInstance().getParameter("page")].autoHideAllowed&&function(){var e;function t(){document.body.className="hidden"}function a(){document.body.className="",clearTimeout(e),e=setTimeout(t,60*o.autoHideTimer*1e3)}window.onload=a,window.onmousemove=a,window.onmousedown=a,window.ontouchstart=a,window.onclick=a,window.onkeypress=a,window.addEventListener("scroll",a,!0)}(),n.getSetting("enableBackToTop")&&window.addEventListener("scroll",(function(e){document.body.scrollTop>100||document.documentElement.scrollTop>100?$("#backToTop").fadeIn():$("#backToTop").fadeOut()})),n.getSetting("autoExtendCarousel")&&window.addEventListener("scroll",(function(e){$("#customCarousel").length>0&&document.documentElement.scrollTop==document.documentElement.scrollHeight-window.innerHeight&&($("#customCarousel").carousel("next"),window.scrollTo(0,0))}))};try{u.getInstance(t)}catch(e){console.error(e)}})}]); |