Loja e Palëve të Interesuara
by PixelPirate96701 lines17.1 KB
<!DOCTYPE html>
<html lang="sq">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loja e Palëve të Interesuara</title>
<style>
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
background: #f5f5f6;
color: #303039;
font-family: Arial, Helvetica, sans-serif;
font-size: 13px;
}
.app {
width: min(1000px, 96vw);
margin: 16px auto;
}
.game-card {
background: #fff;
border-radius: 18px;
box-shadow: 0 7px 24px rgba(50, 35, 70, .08);
padding: 16px;
}
.top {
display: grid;
grid-template-columns: 205px 1fr;
gap: 14px;
align-items: stretch;
}
.stakeholders {
background: #fff;
border-radius: 16px;
padding: 12px 14px;
}
.heading {
color: #7d3fa0;
font-size: 10px;
font-weight: 800;
letter-spacing: 1.3px;
margin-bottom: 9px;
padding-bottom: 8px;
border-bottom: 1px solid #eadcf0;
}
.stakeholder-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.stakeholder {
min-height: 46px;
border-radius: 10px;
background: #f7f7f8;
display: flex;
align-items: center;
gap: 9px;
padding: 7px 10px;
cursor: grab;
user-select: none;
border: 1px solid transparent;
transition: .15s ease;
}
.stakeholder:hover { transform: translateY(-1px); }
.stakeholder.selected {
border-color: #9a66ae;
box-shadow: 0 0 0 2px rgba(154,102,174,.12);
}
.stakeholder.placed { opacity: .56; cursor: default; }
.icon {
width: 22px;
text-align: center;
font-size: 17px;
flex: 0 0 22px;
}
.stakeholder-name {
flex: 1;
color: #77777d;
font-size: 12px;
line-height: 1.15;
}
.tick {
font-size: 17px;
color: #999;
}
.placed-count {
margin-top: 9px;
color: #aaa;
font-size: 11px;
display: flex;
align-items: center;
gap: 3px;
}
.dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #82429f;
display: inline-block;
}
.matrix-wrap {
position: relative;
padding: 15px 0 22px 35px;
}
.axis-top {
position: absolute;
left: 35px;
right: 0;
top: 0;
display: flex;
justify-content: space-between;
color: #999;
font-size: 9px;
letter-spacing: .6px;
}
.axis-top .arrow {
flex: 1;
margin: 0 9px;
height: 1px;
margin-top: 6px;
background: linear-gradient(to right, #c5a6dc 0 48%, transparent 48% 52%, #c5a6dc 52%);
position: relative;
}
.axis-top .arrow:after {
content: "→";
position: absolute;
left: 50%;
top: -8px;
color: #b99acb;
}
.axis-left {
position: absolute;
left: 7px;
top: 47px;
bottom: 28px;
width: 12px;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
color: #999;
font-size: 9px;
writing-mode: vertical-rl;
transform: rotate(180deg);
letter-spacing: .5px;
}
.matrix {
height: 396px;
display: grid;
grid-template-columns: 1fr 1fr;
grid-template-rows: 1fr 1fr;
}
.quadrant {
position: relative;
padding: 12px 10px;
border: 2px dashed;
overflow: hidden;
}
.quadrant:nth-child(1) {
border-color: #94bfff;
border-radius: 14px 0 0 0;
}
.quadrant:nth-child(2) {
border-color: #91d89a;
border-radius: 0 14px 0 0;
}
.quadrant:nth-child(3) {
border-color: #d0d0d0;
border-radius: 0 0 0 14px;
}
.quadrant:nth-child(4) {
border-color: #f1ae53;
border-radius: 0 0 14px 0;
}
.q-title {
font-weight: 800;
font-size: 13px;
margin-bottom: 2px;
}
.q-subtitle {
font-size: 9px;
color: #aaa;
margin-bottom: 14px;
}
.blue { color: #3e83e9; }
.green { color: #4d9b55; }
.gray { color: #777; }
.orange { color: #ed971d; }
.placed-items {
display: flex;
flex-wrap: wrap;
gap: 7px;
align-content: flex-start;
}
.pill {
display: inline-flex;
align-items: center;
gap: 6px;
border: 1px solid #ff3c39;
color: #29292f;
background: #fff;
border-radius: 18px;
padding: 7px 10px;
font-size: 10px;
white-space: nowrap;
}
.pill .score {
color: #e42b2b;
font-weight: 700;
}
.recommend {
border: 1px dashed #48a85a;
color: #48a85a;
background: #f9fff9;
border-radius: 18px;
padding: 7px 10px;
font-size: 9px;
font-weight: 700;
cursor: default;
}
.legend {
position: absolute;
left: 35px;
bottom: 0;
color: #777;
font-size: 9px;
display: flex;
gap: 10px;
}
.legend span { display: inline-flex; align-items: center; gap: 4px; }
.legend i {
width: 9px; height: 9px; border-radius: 50%; display: inline-block;
}
.buttons {
display: flex;
gap: 10px;
margin-top: 14px;
margin-left: 219px;
}
button {
border: 0;
font: inherit;
cursor: pointer;
}
#revealBtn {
flex: 1;
height: 44px;
border-radius: 11px;
background: #d7c2eb;
color: #fff;
font-weight: 800;
font-size: 15px;
}
#revealBtn:not(:disabled):hover { background: #c8ace1; }
#revealBtn:disabled { cursor: default; opacity: .8; }
#resetBtn {
width: 92px;
height: 44px;
border-radius: 11px;
background: #fff;
color: #7d3fa0;
border: 1px solid #7d3fa0;
font-weight: 700;
}
.results {
margin-top: 18px;
background: #fff;
border-radius: 18px;
box-shadow: 0 7px 24px rgba(50, 35, 70, .08);
padding: 16px 18px 18px;
display: none;
}
.results.show { display: block; }
.results-title {
color: #7d3fa0;
font-size: 10px;
font-weight: 800;
letter-spacing: 1.2px;
margin-bottom: 12px;
}
.reason-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 10px;
}
.reason {
border-radius: 11px;
padding: 12px 13px;
min-height: 105px;
}
.reason:nth-child(1),
.reason:nth-child(2),
.reason:nth-child(3),
.reason:nth-child(7),
.reason:nth-child(8) { background: #fff9f0; }
.reason:nth-child(4),
.reason:nth-child(5),
.reason:nth-child(6) { background: #f1faf2; }
.reason h3 {
margin: 0 0 4px;
font-size: 12px;
color: #2d2b30;
}
.placement {
font-weight: 800;
font-size: 10px;
margin-bottom: 7px;
}
.reason p {
margin: 0;
color: #666;
font-size: 10px;
line-height: 1.42;
}
.toast {
position: fixed;
left: 50%;
bottom: 18px;
transform: translateX(-50%) translateY(20px);
background: #35263d;
color: #fff;
padding: 9px 14px;
border-radius: 20px;
opacity: 0;
pointer-events: none;
transition: .2s;
font-size: 11px;
}
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
@media (max-width: 800px) {
.top { grid-template-columns: 1fr; }
.buttons { margin-left: 0; }
.matrix-wrap { padding-left: 28px; }
.axis-top { left: 28px; }
.legend { left: 28px; }
.matrix { height: 460px; }
.reason-grid { grid-template-columns: 1fr; }
}
@media (max-width: 520px) {
.matrix { height: 540px; grid-template-columns: 1fr; grid-template-rows: repeat(4, 1fr); }
.quadrant { border-radius: 12px !important; }
.buttons { flex-direction: column; }
#resetBtn { width: 100%; }
.axis-top { display: none; }
}
</style>
</head>
<body>
<div class="app">
<div class="game-card">
<div class="top">
<aside class="stakeholders">
<div class="heading">PALËT E INTERESUARA PËR T'U VENDOSUR</div>
<div id="stakeholderList" class="stakeholder-list"></div>
<div class="placed-count">Të vendosura:
<span id="dots"></span>
</div>
</aside>
<main class="matrix-wrap">
<div class="axis-top">
<span>INTERES I ULËT</span>
<span class="arrow"></span>
<span>INTERES I LARTË</span>
</div>
<div class="axis-left">
<span>FUQI E ULËT</span>
<span>FUQI E LARTË</span>
</div>
<div class="matrix">
<section class="quadrant" data-zone="keepSatisfied">
<div class="q-title blue">Mbaji të kënaqur</div>
<div class="q-subtitle">Fuqi e lartë · Interes i ulët</div>
<div class="placed-items" id="keepSatisfied"></div>
</section>
<section class="quadrant" data-zone="manageClosely">
<div class="q-title green">Menaxho nga afër</div>
<div class="q-subtitle">Fuqi e lartë · Interes i lartë</div>
<div class="placed-items" id="manageClosely"></div>
</section>
<section class="quadrant" data-zone="monitor">
<div class="q-title gray">Monitoro</div>
<div class="q-subtitle">Fuqi e ulët · Interes i ulët</div>
<div class="placed-items" id="monitor"></div>
</section>
<section class="quadrant" data-zone="keepInformed">
<div class="q-title orange">Mbaji të informuar</div>
<div class="q-subtitle">Fuqi e ulët · Interes i lartë</div>
<div class="placed-items" id="keepInformed"></div>
</section>
</div>
<div class="legend">
<span><i style="background:#4384e6"></i> Mbaji të kënaqur</span>
<span><i style="background:#51a25b"></i> Menaxho nga afër</span>
<span><i style="background:#999"></i> Monitoro</span>
<span><i style="background:#ef9b27"></i> Mbaji të informuar</span>
</div>
</main>
</div>
<div class="buttons">
<button id="revealBtn">✓ Shfaq arsyetimin</button>
<button id="resetBtn">↻ Rivendos</button>
</div>
</div>
<section id="results" class="results">
<div class="results-title">VENDOSJET E REKOMANDUARA DHE ARSYETIMI</div>
<div class="reason-grid" id="reasonGrid"></div>
</section>
</div>
<div id="toast" class="toast"></div>
<script>
const stakeholders = [
{
id: "principal",
icon: "🏫",
name: "Drejtori i shkollës",
zone: "manageClosely",
placement: "→ Menaxho nga afër",
reason: "Autoriteti më i lartë në terren — mund ta miratojë ose ta ndalojë projektin. Ndikon drejtpërdrejt në reputacionin e shkollës dhe mirëqenien e nxënësve."
},
{
id: "funder",
icon: "💶",
name: "Financuesi / Sponsori i projektit",
zone: "manageClosely",
placement: "→ Menaxho nga afër",
reason: "Kontrollon buxhetin. Pa mbështetje të vazhdueshme financiare, projekti nuk mund të vazhdojë — duhet menaxhuar nga afër në çdo fazë."
},
{
id: "permit",
icon: "🏛️",
name: "Autoriteti bashkiak i lejeve",
zone: "keepSatisfied",
placement: "→ Mbaji të kënaqur",
reason: "Ka fuqinë ligjore për të bllokuar ose miratuar ndërtimin, por nuk ka interes të përditshëm në çështjet e shkollës. Mbajini të kënaqur përmes pajtueshmërisë."
},
{
id: "maintenance",
icon: "🔧",
name: "Departamenti i mirëmbajtjes",
zone: "keepInformed",
placement: "✓ Mbaji të informuar",
reason: "Do të jetë përgjegjës për mirëmbajtjen afatgjatë të fushës dhe ka interes të lartë, por fuqi të kufizuar mbi vendimin strategjik për ndërtimin."
},
{
id: "students",
icon: "🧑🎓",
name: "Nxënësit",
zone: "keepInformed",
placement: "→ Mbaji të informuar",
reason: "Përdoruesit kryesorë dhe pala më e interesuar, por kanë pak fuqi formale institucionale mbi vendimet për infrastrukturën."
},
{
id: "parents",
icon: "👨👩👧",
name: "Prindërit",
zone: "keepInformed",
placement: "✓ Mbaji të informuar",
reason: "Kanë interes të lartë si kujdestarë të përdoruesve kryesorë; megjithatë, kanë fuqi formale të kufizuar mbi vendimet për infrastrukturën e shkollës."
},
{
id: "public",
icon: "🤝",
name: "Publiku i gjerë (larg)",
zone: "monitor",
placement: "→ Monitoro",
reason: "Nuk ka gjasa të ndikohet drejtpërdrejt nga një fushë sportive e shkollës në anën tjetër të qytetit. Interes i ulët dhe pa fuqi domethënëse — vetëm mbajeni nën vëzhgim."
},
{
id: "business",
icon: "🏪",
name: "Biznes lokal i palidhur",
zone: "monitor",
placement: "→ Monitoro",
reason: "Nuk ka interes të drejtpërdrejtë në projekt. Vetëm mbajeni nën vëzhgim në rast se më vonë shfaqen ndikime të tërthorta, si zhurma ose trafiku."
}
];
const state = {
placements: {},
selected: null
};
const list = document.getElementById("stakeholderList");
const dots = document.getElementById("dots");
const revealBtn = document.getElementById("revealBtn");
const resetBtn = document.getElementById("resetBtn");
const results = document.getElementById("results");
const reasonGrid = document.getElementById("reasonGrid");
const toast = document.getElementById("toast");
function renderList() {
list.innerHTML = "";
stakeholders.forEach(s => {
const el = document.createElement("div");
el.className = "stakeholder" +
(state.placements[s.id] ? " placed" : "") +
(state.selected === s.id ? " selected" : "");
el.dataset.id = s.id;
el.draggable = !state.placements[s.id];
el.innerHTML = `
<span class="icon">${s.icon}</span>
<span class="stakeholder-name">${s.name}</span>
<span class="tick">${state.placements[s.id] ? "✓" : "✓"}</span>
`;
el.addEventListener("click", () => {
if (state.placements[s.id]) return;
state.selected = state.selected === s.id ? null : s.id;
renderList();
showToast(state.selected ? "Tani zgjidh kuadrantin ku dëshiron ta vendosësh." : "");
});
el.addEventListener("dragstart", e => {
if (state.placements[s.id]) return;
state.selected = s.id;
e.dataTransfer.setData("text/plain", s.id);
});
list.appendChild(el);
});
renderDots();
}
function renderDots() {
dots.innerHTML = "";
const count = Object.keys(state.placements).length;
for (let i = 0; i < count; i++) {
const d = document.createElement("i");
d.className = "dot";
dots.appendChild(d);
}
for (let i = count; i < stakeholders.length; i++) {
const d = document.createElement("i");
d.className = "dot";
d.style.opacity = ".18";
dots.appendChild(d);
}
}
function addPill(s, zone) {
const container = document.getElementById(zone);
const pill = document.createElement("div");
pill.className = "pill";
pill.dataset.id = s.id;
pill.innerHTML = `${s.icon} ${s.name} <span class="score">${zone === s.zone ? "✓" : "✕"}</span>`;
container.appendChild(pill);
if (zone === s.zone) {
const rec = document.createElement("div");
rec.className = "recommend";
rec.textContent = "✓ Rekomandohet";
container.appendChild(rec);
}
}
function place(id, zone) {
const s = stakeholders.find(x => x.id === id);
if (!s || state.placements[id]) return;
state.placements[id] = zone;
state.selected = null;
addPill(s, zone);
renderList();
if (zone === s.zone) {
showToast("Vendosje e saktë!");
} else {
showToast("Vendosje e gabuar — provo një kuadrant tjetër.");
}
if (Object.keys(state.placements).length === stakeholders.length) {
revealBtn.disabled = false;
}
}
document.querySelectorAll(".quadrant").forEach(q => {
q.addEventListener("dragover", e => {
e.preventDefault();
q.style.background = "rgba(125,63,160,.04)";
});
q.addEventListener("dragleave", () => q.style.background = "");
q.addEventListener("drop", e => {
e.preventDefault();
q.style.background = "";
const id = e.dataTransfer.getData("text/plain");
place(id, q.dataset.zone);
});
q.addEventListener("click", () => {
if (state.selected) place(state.selected, q.dataset.zone);
});
});
function reveal() {
if (Object.keys(state.placements).length < stakeholders.length) {
showToast("Vendos të gjitha palët e interesuara fillimisht.");
return;
}
reasonGrid.innerHTML = "";
stakeholders.forEach(s => {
const correct = state.placements[s.id] === s.zone;
const card = document.createElement("article");
card.className = "reason";
card.innerHTML = `
<h3>${s.icon} ${s.name}</h3>
<div class="placement" style="color:${correct ? "#4d9b55" : "#e89a24"}">
${correct ? "✓ " : "✕ "}${s.placement}
</div>
<p>${s.reason}</p>
`;
reasonGrid.appendChild(card);
});
results.classList.add("show");
results.scrollIntoView({ behavior: "smooth", block: "start" });
}
function resetGame() {
state.placements = {};
state.selected = null;
results.classList.remove("show");
revealBtn.disabled = false;
["keepSatisfied","manageClosely","monitor","keepInformed"].forEach(id => {
document.getElementById(id).innerHTML = "";
});
renderList();
window.scrollTo({top: 0, behavior: "smooth"});
}
function showToast(text) {
if (!text) {
toast.classList.remove("show");
return;
}
toast.textContent = text;
toast.classList.add("show");
clearTimeout(showToast.timer);
showToast.timer = setTimeout(() => toast.classList.remove("show"), 1700);
}
revealBtn.addEventListener("click", reveal);
resetBtn.addEventListener("click", resetGame);
renderList();
</script>
</body>
</html>
Game Source: Loja e Palëve të Interesuara
Creator: PixelPirate96
Libraries: none
Complexity: complex (701 lines, 17.1 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: loja-e-pal-ve-t-interesuara-pixelpirate96" to link back to the original. Then publish at arcadelab.ai/publish.