diff --git a/swac/components/QuestGame/QuestGame.js b/swac/components/QuestGame/QuestGame.js new file mode 100644 index 0000000..5ebe555 --- /dev/null +++ b/swac/components/QuestGame/QuestGame.js @@ -0,0 +1,323 @@ +import SWAC from '../../swac.js'; +import View from '../../View.js'; +import Msg from '../../Msg.js'; + +/** + * Sample component for development of own components + */ +export default class QuestGame extends View { + + /* + * Constructs a new component object and transfers the config to the + * object + */ + constructor(options = {}) { + super(options); + this.name = 'QuestGame'; + this.desc.text = 'Component for creating quest games.'; + this.desc.developers = 'Florian Fehring'; + this.desc.license = 'Closed License'; + + // Include an external library that does not use export + // Include files that use export by import statement at start of the file + +// this.desc.depends[1] = { +// name: 'NameOfTheAlgorithmComponent', +// algorithm: 'NameOfTheAlgorithmComponent', +// desc: 'Description why this algorithm is needed.' +// }; + this.desc.templates[0] = { + name: 'default', + style: 'urban-fantasy', + desc: 'Default template.' + }; +// this.desc.reqPerTpl[0] = { +// selc: 'cssSelectorForRequiredElement', +// desc: 'Description why the element is expected in the template' +// }; + this.desc.optPerTpl[0] = { + selc: 'cssSelectorForOptionalElement', + desc: 'Description what is the expected effect, when this element is in the template.' + }; + this.desc.reqPerSet[0] = { + name: 'id', + desc: 'The attribute id is required for the component to work properly.' + }; + this.desc.optPerSet[0] = { + name: 'nameOfTheAttributeOptionalInEachSet', + desc: 'Description what is the expected effect, when this attribute is in the set.' + }; + // opts ids over 1000 are reserved for Component independend options + this.desc.opts[0] = { + name: "OptionsName", + desc: "This is the description of an option", + example: { + some1: "This is an example config for configuration", + some2: "It can be any object / string / value", + func1: function (t) { + t.do(); + } + } + }; + // Setting a default value, only applying when the options parameter does not contain this option + if (!options.OptionsName) + this.options.OptionsName = 'defaultvalue'; + // Sample for useing the general option showWhenNoData + if (!options.showWhenNoData) + this.options.showWhenNoData = true; + + // Internal attrtibutes + this.currentStation = 1; + } + + /* + * This method will be called when the component is complete loaded + * At this thime the template code is loaded, the data inserted into the + * template and even plugins are ready to use. + */ + async init() { + // here we can do what we want with the data and template. + } + + /** + * Method thats called before adding a dataset + * This overrides the method from View.js + * + * @param {Object} set Object with attributes to add + * @returns {Object} (modified) set + */ + beforeAddSet(set) { + // You can check or transform the dataset here + return set; + } + + /** + * Method thats called after a dataset was added. + * This overrides the method from View.js + * + * @param {Object} set Object with attributes to add + * @param {DOMElement[]} repeateds Elements that where created as representation for the set + * @returns {undefined} + */ + afterAddSet(set, repeateds) { + // Call Components afterAddSet and plugins afterAddSet + super.afterAddSet(set, repeateds); + + // There should be only one repeated + let repeatedElem = repeateds[0]; + + if (set.id === 1) { + repeatedElem.classList.remove('swac_dontdisplay'); + } + + // Get elements that change by type + let nextBtn = repeatedElem.querySelector('.qg-next-btn'); + // Jump over button + let jumpOverBtn = repeatedElem.querySelector('.qg-overjump-btn'); + jumpOverBtn.classList.add('swac_dontdisplay'); + + // Change things depending on type + if (set.type == 'intro') { + nextBtn.innerHTML = 'Starten'; + nextBtn.setAttribute('swac_lang', 'start'); + nextBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + if (set.type === 'way') { + + + nextBtn.innerHTML = 'Wir sind angekommen'; + nextBtn.setAttribute('swac_lang', 'way_arrived'); + nextBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + if (set.type === 'story') { + + + nextBtn.innerHTML = 'Weiter'; + nextBtn.setAttribute('swac_lang', 'story_gofurther'); + nextBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + if (set.type === 'npc-virtual') { + + + nextBtn.innerHTML = 'Weiter'; + nextBtn.setAttribute('swac_lang', 'story_gofurther'); + nextBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + if (set.type === 'npc-real') { + + + nextBtn.innerHTML = 'Weiter'; + nextBtn.setAttribute('swac_lang', 'story_gofurther'); + nextBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + + + if (set.type === 'question') { + // Hints + if (set.hints && Array.isArray(set.hints) && set.hints.length > 0) { + let hintBlock = repeatedElem.querySelector('.qg-hints'); + let hintBtn = repeatedElem.querySelector('.qg-hint-btn'); + let hintList = repeatedElem.querySelector('.qg-hint-list'); + + hintBlock.classList.remove('swac_dontdisplay'); + + let hintIndex = 0; + + hintBtn.addEventListener('click', () => { + + // nächsten Hinweis holen + let nextHint = set.hints[hintIndex]; + + // neuen Listeneintrag erzeugen + let li = document.createElement('li'); + li.innerHTML = nextHint; + hintList.appendChild(li); + + // Index erhöhen + hintIndex++; + + // Button-Text anpassen + if (hintIndex < set.hints.length) { + hintBtn.innerText = "Weiterer Hinweis"; + } else { + hintBtn.innerText = "Alle Hinweise angezeigt"; + hintBtn.disabled = true; + } + }); + } + + // Create selection + if (set.answertype === 'choice') { + let answerTpl = repeatedElem.querySelector('.qg-repeat_for_answer-option'); + let answertype; + if (Array.isArray(set.correctanswers) && set.correctanswers.length === 1) { + answertype = "radio"; + } else { + answertype = "checkbox"; + } + // Go trough possible answers + for (let curPosAnswer of set.possibleanswers) { + let curAnswerElem = answerTpl.cloneNode(true); + curAnswerElem.classList.remove('qg-repeat_for_answer-option'); + // Set input attributes + let curAnswerInputElem = curAnswerElem.querySelector('input'); + curAnswerInputElem.setAttribute('type', answertype); + curAnswerInputElem.setAttribute('value', curPosAnswer); + // Set option text + let curAnswerTxtElem = curAnswerElem.querySelector('.qg-choiseanswer-option'); + curAnswerTxtElem.innerHTML = curPosAnswer; + // Add answer option + answerTpl.parentElement.appendChild(curAnswerElem); + + // If single choice add direct check + if (answertype === 'radio') { + curAnswerElem.addEventListener('click', () => { + const isCorrect = set.correctanswers.includes(curPosAnswer); + + if (!isCorrect) { + // Hinweis anzeigen + let feedbackElem = repeatedElem.querySelector('.qg-feedback'); + if (feedbackElem) + feedbackElem.innerText = 'Leider falsch!'; + return; + } + + // richtige Antwort → weiter zur nächsten Station + this.showStation(set.nextstation); + }); + } + } + + } else if (set.answertype === 'text') { + let answerInputElem = repeatedElem.querySelector('.qg-textanswer'); + answerInputElem.classList.remove('swac_dontdisplay'); + + } + + // Configure 'check' button + nextBtn.innerHTML = 'Antwort prüfen'; + nextBtn.setAttribute('swac_lang', 'question_check'); + + if (set.answertype === 'text') { + nextBtn.addEventListener('click', () => { + let inputElem = repeatedElem.querySelector('.qg-textanswer input'); + let userAnswer = inputElem.value.trim().toLowerCase(); + + // mehrere korrekte Antworten möglich + let correctAnswers = set.correctanswers.map(a => a.trim().toLowerCase()); + + let isCorrect = correctAnswers.includes(userAnswer); + + if (!isCorrect) { + let feedbackElem = repeatedElem.querySelector('.qg-feedback'); + if (feedbackElem) + feedbackElem.innerText = 'Leider falsch!'; + return; // nicht weitergehen + } + + // Wenn keine Textfrage oder Antwort korrekt → weiter + this.showStation(set.nextstation); + }); + } + + // Configure jump over button + jumpOverBtn.classList.remove('swac_dontdisplay'); + jumpOverBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + + if (set.type === 'afterinfos') { + + + nextBtn.innerHTML = 'Weiter'; + nextBtn.setAttribute('swac_lang', 'afterinfo_gofurther'); + nextBtn.addEventListener('click', () => { + this.scrollUp(); + this.showStation(set.nextstation); + }); + } + + return; + } + + showStation(newStationId) { + if (newStationId == 0) { + UIkit.modal('#spende-modal').show(); + return; + } + + let oldElems = this.requestor.querySelectorAll('.swac_repeatedForSet'); + for (let curElem of oldElems) + curElem.classList.add('swac_dontdisplay'); + let newElem = this.requestor.querySelector('[swac_id="' + newStationId + '"]'); + newElem.classList.remove('swac_dontdisplay'); + this.currentStation = newStationId; + } + + scrollUp() { + window.scrollTo({ + top: 0, + behavior: "smooth" + }); + } + +} + + diff --git a/swac/components/QuestGame/default.css b/swac/components/QuestGame/default.css new file mode 100644 index 0000000..706e664 --- /dev/null +++ b/swac/components/QuestGame/default.css @@ -0,0 +1,186 @@ +.questgame { + max-width: 600px; + margin: auto; + padding: 1rem; + font-family: sans-serif; +} + +.qg-header img { + width: 100%; + max-height: 180px; + object-fit: contain; +} + +.qg-title { + text-align: center; + margin-top: 0.5rem; +} + +.qg-content { + margin-top: 1rem; + line-height: 1.5; +} + +.qg-next-btn { + width: 100%; + padding: 1rem; + font-size: 1.4rem; + background: #333; + color: white; + border: none; + border-radius: 8px; + margin-top: 1rem; +} + +.qg-overjump-btn { + font-size: 0.7rem; +} + +.qg-feedback { + margin-top: 1rem; + color: red; + font-weight: bold; +} + +.qg-repeat_for_answer-option { + display: none; +} + +.qg-answer-option { + padding: 0.8rem; + background: #eee; + margin: 0.5rem 0; + border-radius: 6px; +} + +.qg-choise ul { + list-style: none; + padding: 0; + margin: 0; +} + +.qg-choise li { + margin-bottom: 0.8rem; +} + +.qg-choise label { + display: flex; + align-items: center; + gap: 0.6rem; + + padding: 0.8rem 1rem; + border-radius: 10px; + + background: #f7f7f7; + border: 1px solid #ddd; + cursor: pointer; +} + +.qg-choise input[type="radio"], +.qg-choise input[type="checkbox"] { + width: 1.2rem; + height: 1.2rem; +} + +.qg-choiseanswer-option { + flex: 1; +} + +.qg-textanswer { + margin-top: 1rem; + width: 100%; +} + +.qg-textanswer input[type="text"] { + width: 100%; + padding: 0.9rem 1rem; + + font-size: 1.2rem; + border-radius: 10px; + + border: 1px solid #ccc; + background: #f7f7f7; + + box-sizing: border-box; + + transition: border-color 0.2s ease, background 0.2s ease; +} + +/* Fokus-Effekt */ +.qg-textanswer input[type="text"]:focus { + outline: none; + border-color: #333; + background: #fff; +} + +.qg-hints { + margin-top: 1rem; +} + +.qg-hint-btn { + width: 100%; + padding: 0.8rem 1rem; + font-size: 1.1rem; + background: #444; + color: #fff; + border: none; + border-radius: 8px; + cursor: pointer; +} + +.qg-hint-btn:active { + background: #333; +} + +.qg-hint-list { + list-style: none; + padding: 0; + margin-top: 0.8rem; +} + +.qg-hint-list li { + background: #f7f7f7; + border: 1px solid #ddd; + border-radius: 8px; + padding: 0.8rem 1rem; + margin-bottom: 0.6rem; + font-size: 1rem; + line-height: 1.4; +} + + +.qg-says { + background: #fff; + border: 2px solid #333; + border-radius: 20px; + padding: 1rem 1.2rem; + margin: 1rem 0; + position: relative; + font-size: 1.2rem; + line-height: 1.5; +} + +/* Comic-Pfeil */ +.qg-says::after { + content: ""; + position: absolute; + bottom: -14px; + left: 30px; + width: 0; + height: 0; + border-top: 14px solid #333; + border-left: 14px solid transparent; + border-right: 14px solid transparent; +} + +.qg-says::before { + content: ""; + position: absolute; + bottom: -12px; + left: 30px; + width: 0; + height: 0; + border-top: 12px solid #fff; + border-left: 12px solid transparent; + border-right: 12px solid transparent; +} diff --git a/swac/components/QuestGame/default.html b/swac/components/QuestGame/default.html new file mode 100644 index 0000000..73cd2d3 --- /dev/null +++ b/swac/components/QuestGame/default.html @@ -0,0 +1,48 @@ +
+ +
+ +
+ + + + +

{title}

+
+ +
{text}
+ +
{says}
+ + + + + +
+ +
+
    +
  • + +
  • +
+
+ +
+ +
+ +
+ +

+
+ +
    +
    + + Überspringen +




    +
    +
    diff --git a/swac/components/QuestGame/langs/de.js b/swac/components/QuestGame/langs/de.js new file mode 100644 index 0000000..3bf5806 --- /dev/null +++ b/swac/components/QuestGame/langs/de.js @@ -0,0 +1,4 @@ +var QuestGame_de = { + languagemark: 'Sprachmarke' +}; +export default QuestGame_de; \ No newline at end of file diff --git a/swac/components/QuestGame/langs/en.js b/swac/components/QuestGame/langs/en.js new file mode 100644 index 0000000..b2e6083 --- /dev/null +++ b/swac/components/QuestGame/langs/en.js @@ -0,0 +1,4 @@ +var QuestGame_de = { + languagemark: 'Languagemark' +}; +export default QuestGame_de; \ No newline at end of file diff --git a/swac/components/QuestGame/urban-fantasy.css b/swac/components/QuestGame/urban-fantasy.css new file mode 100644 index 0000000..932f0f6 --- /dev/null +++ b/swac/components/QuestGame/urban-fantasy.css @@ -0,0 +1,163 @@ +/* Grundlayout */ +.questgame { + max-width: 600px; + margin: auto; + padding: 1.2rem; + font-family: "Merriweather", serif; /* wirkt erzählerischer */ + background: #1b1b1d; + color: #e6e6e6; + border-radius: 12px; + box-shadow: 0 0 25px rgba(0,0,0,0.4); +} + +/* Header-Bild */ +.qg-header img { + width: 100%; + max-height: 200px; + object-fit: cover; + border-radius: 10px; + box-shadow: 0 0 12px rgba(0,0,0,0.5); +} + +/* Titel */ +.qg-title { + text-align: center; + margin-top: 0.8rem; + font-size: 1.6rem; + letter-spacing: 1px; + color: #f0e6d2; /* leicht warmes Fantasy-Gold */ +} + +/* Text */ +.qg-content { + margin-top: 1rem; + line-height: 1.6; + font-size: 1.1rem; +} + +/* Button */ +.qg-next-btn { + width: 100%; + padding: 1rem; + font-size: 1.3rem; + background: linear-gradient(135deg, #3a2f4a, #5a3d6d); + color: #f7f7f7; + border: none; + border-radius: 10px; + margin-top: 1.2rem; + box-shadow: 0 0 12px rgba(90, 61, 109, 0.6); + transition: transform 0.1s ease; +} + +.qg-next-btn:active { + transform: scale(0.97); +} + +/* Antwortoptionen */ +.qg-choise label { + background: rgba(255,255,255,0.05); + border: 1px solid rgba(255,255,255,0.15); + color: #e6e6e6; + backdrop-filter: blur(4px); +} + +/* Textantwort */ +.qg-textanswer input[type="text"] { + width: 90%; + padding: 1rem; + font-size: 1.3rem; + background: rgba(255,255,255,0.08); + border: 1px solid rgba(255,255,255,0.2); + color: #f0e6d2; +} + +/* Hinweise */ +.qg-hint-btn { + width: 100%; + padding: 0.5rem; + font-size: 1.0rem; + color: #e6e6e6; + background: #5a3d6d; + border-radius: 10px; +} + +.qg-hint-list li { + background: #262629; + border: 2px solid #444; + border-radius: 25px; + padding: 1rem 1.2rem; + margin-bottom: 1rem; + color: #e6e6e6; + position: relative; + box-shadow: 0 0 12px rgba(255,255,255,0.08); +} + +/* Gedankenblasen-Kreise */ +.qg-hint-list li::before, +.qg-hint-list li::after { + content: ""; + position: absolute; + background: #262629; + border: 2px solid #444; + border-radius: 50%; +} + +.qg-hint-list li::before { + width: 18px; + height: 18px; + bottom: -12px; + left: 30px; +} + +.qg-hint-list li::after { + width: 12px; + height: 12px; + bottom: -24px; + left: 20px; +} + +/* Sprechblase */ +.qg-says { + background: #262629; + border: 2px solid #5a3d6d; + border-radius: 20px; + padding: 1rem 1.2rem; + margin: 1rem 0; + position: relative; + font-size: 1.2rem; + line-height: 1.5; + color: #f0e6d2; + box-shadow: 0 0 15px rgba(90, 61, 109, 0.4); +} + +/* Pfeil */ +.qg-says::after { + content: ""; + position: absolute; + bottom: -18px; + left: 40px; + border-width: 18px 18px 0 18px; + border-style: solid; + border-color: #5a3d6d transparent transparent transparent; +} + +.qg-says::before { + content: ""; + position: absolute; + bottom: -16px; + left: 40px; + border-width: 16px 16px 0 16px; + border-style: solid; + border-color: #262629 transparent transparent transparent; +} + + +.qg-repeat_for_answer-option { + display: none; +} + +.qg-choise ul { + list-style: none; + padding: 0; + margin: 0; +} \ No newline at end of file diff --git a/swac/{img} b/swac/{img} deleted file mode 100644 index edf8575..0000000 Binary files a/swac/{img} and /dev/null differ