UX overhaul: card layout, admin overlay, status bar, and phase-safe inputs
This commit is contained in:
@@ -49,11 +49,13 @@ async function loadState() {
|
|||||||
renderPhasePill();
|
renderPhasePill();
|
||||||
renderCounts();
|
renderCounts();
|
||||||
const nameInput = $("name-input");
|
const nameInput = $("name-input");
|
||||||
if (!nameInput.dataset.userEditing) {
|
if (nameInput && !nameInput.dataset.userEditing) {
|
||||||
nameInput.value = me.displayName || "";
|
nameInput.value = me.displayName || "";
|
||||||
}
|
}
|
||||||
|
if ($("player-id")) {
|
||||||
$("player-id").textContent = `Player ID: ${me.id}`;
|
$("player-id").textContent = `Player ID: ${me.id}`;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function loadSuggestData() {
|
async function loadSuggestData() {
|
||||||
if (state.phase !== "Suggest") return;
|
if (state.phase !== "Suggest") return;
|
||||||
@@ -92,35 +94,28 @@ function renderPhasePill() {
|
|||||||
const id = viewMap[state.phase];
|
const id = viewMap[state.phase];
|
||||||
if (id) $(id).classList.remove("hidden");
|
if (id) $(id).classList.remove("hidden");
|
||||||
const phaseSelect = $("phase-select");
|
const phaseSelect = $("phase-select");
|
||||||
if (!phaseSelect.dataset.userEditing) {
|
if (phaseSelect && !phaseSelect.dataset.userEditing) {
|
||||||
phaseSelect.value = state.phase || "Suggest";
|
phaseSelect.value = state.phase || "Suggest";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderCounts() {
|
function renderCounts() {
|
||||||
if (!state.counts) return;
|
if (!state.counts) return;
|
||||||
$("phase-description").textContent = `Phase: ${state.phase}`;
|
|
||||||
$("counts").textContent = `Players: ${state.counts.players} • Suggestions: ${state.counts.suggestions} • Votes: ${state.counts.votes}`;
|
$("counts").textContent = `Players: ${state.counts.players} • Suggestions: ${state.counts.suggestions} • Votes: ${state.counts.votes}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMySuggestions() {
|
function renderMySuggestions() {
|
||||||
const list = $("my-suggestions");
|
const wrap = $("my-suggestions");
|
||||||
list.innerHTML = "";
|
if (!wrap) return;
|
||||||
state.mySuggestions.forEach((s) => {
|
wrap.innerHTML = "";
|
||||||
const li = document.createElement("li");
|
state.mySuggestions.forEach((s) => wrap.appendChild(buildCard(s, { showAuthor: false })));
|
||||||
li.innerHTML = `<strong>${s.name}</strong>${s.genre ? ` · ${s.genre}` : ""}<br>${s.description || ""}`;
|
|
||||||
list.appendChild(li);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderAllSuggestions() {
|
function renderAllSuggestions() {
|
||||||
const list = $("all-suggestions");
|
const list = $("all-suggestions");
|
||||||
|
if (!list) return;
|
||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
state.allSuggestions.forEach((s) => {
|
state.allSuggestions.forEach((s) => list.appendChild(buildCard(s, { showAuthor: true })));
|
||||||
const li = document.createElement("li");
|
|
||||||
li.innerHTML = `<strong>${s.name}</strong> by ${s.author || "Anonymous"}${s.genre ? ` · ${s.genre}` : ""}<br>${s.description || ""}`;
|
|
||||||
list.appendChild(li);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderVotes() {
|
function renderVotes() {
|
||||||
@@ -128,18 +123,14 @@ function renderVotes() {
|
|||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
const votesMap = Object.fromEntries(state.myVotes.map((v) => [v.suggestionId, v.score]));
|
const votesMap = Object.fromEntries(state.myVotes.map((v) => [v.suggestionId, v.score]));
|
||||||
state.allSuggestions.forEach((s) => {
|
state.allSuggestions.forEach((s) => {
|
||||||
const li = document.createElement("li");
|
const li = buildCard(s, { showAuthor: true });
|
||||||
const current = votesMap[s.id] ?? 0;
|
const current = votesMap[s.id] ?? 0;
|
||||||
li.innerHTML = `
|
const footer = document.createElement("div");
|
||||||
<div class="vote-row">
|
footer.className = "vote-controls";
|
||||||
<div>
|
footer.innerHTML = `
|
||||||
<strong>${s.name}</strong> by ${s.author || "Anonymous"}
|
|
||||||
</div>
|
|
||||||
<div class="vote-controls">
|
|
||||||
<input type="range" min="0" max="10" value="${current}" data-id="${s.id}">
|
<input type="range" min="0" max="10" value="${current}" data-id="${s.id}">
|
||||||
<span class="score" id="score-${s.id}">${current}</span>
|
<span class="score" id="score-${s.id}">${current}</span>`;
|
||||||
</div>
|
li.querySelector(".card-body").appendChild(footer);
|
||||||
</div>`;
|
|
||||||
list.appendChild(li);
|
list.appendChild(li);
|
||||||
});
|
});
|
||||||
list.querySelectorAll("input[type=range]").forEach((input) => {
|
list.querySelectorAll("input[type=range]").forEach((input) => {
|
||||||
@@ -165,18 +156,26 @@ function renderResults() {
|
|||||||
const list = $("results-list");
|
const list = $("results-list");
|
||||||
list.innerHTML = "";
|
list.innerHTML = "";
|
||||||
state.results.forEach((r) => {
|
state.results.forEach((r) => {
|
||||||
const li = document.createElement("li");
|
const card = buildCard({
|
||||||
li.innerHTML = `<strong>${r.name}</strong> — ${r.total} pts (${r.count} votes, avg ${r.average.toFixed(1)})${r.author ? ` · ${r.author}` : ""}`;
|
id: r.id,
|
||||||
list.appendChild(li);
|
name: r.name,
|
||||||
|
genre: `${r.total} pts • ${r.count} votes • avg ${r.average.toFixed(1)}`,
|
||||||
|
description: r.author ? `By ${r.author}` : "",
|
||||||
|
screenshotUrl: r.screenshotUrl,
|
||||||
|
youtubeUrl: r.youtubeUrl
|
||||||
|
}, { showAuthor: false });
|
||||||
|
list.appendChild(card);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function setupHandlers() {
|
function setupHandlers() {
|
||||||
const nameInput = $("name-input");
|
const nameInput = $("name-input");
|
||||||
|
if (nameInput) {
|
||||||
["focus", "input"].forEach(evt => {
|
["focus", "input"].forEach(evt => {
|
||||||
nameInput.addEventListener(evt, () => { nameInput.dataset.userEditing = "1"; });
|
nameInput.addEventListener(evt, () => { nameInput.dataset.userEditing = "1"; });
|
||||||
});
|
});
|
||||||
nameInput.addEventListener("blur", () => { nameInput.dataset.userEditing = ""; });
|
nameInput.addEventListener("blur", () => { nameInput.dataset.userEditing = ""; });
|
||||||
|
}
|
||||||
|
|
||||||
$("save-name").addEventListener("click", async () => {
|
$("save-name").addEventListener("click", async () => {
|
||||||
const name = nameInput.value.trim();
|
const name = nameInput.value.trim();
|
||||||
@@ -232,6 +231,13 @@ function setupHandlers() {
|
|||||||
|
|
||||||
$("reset").addEventListener("click", () => adminAction("/api/admin/reset", "Reset complete"));
|
$("reset").addEventListener("click", () => adminAction("/api/admin/reset", "Reset complete"));
|
||||||
$("factory-reset").addEventListener("click", () => adminAction("/api/admin/factory-reset", "Factory reset complete"));
|
$("factory-reset").addEventListener("click", () => adminAction("/api/admin/factory-reset", "Factory reset complete"));
|
||||||
|
|
||||||
|
const adminToggle = $("admin-toggle");
|
||||||
|
const adminCard = $("admin-card");
|
||||||
|
const adminClose = $("admin-close");
|
||||||
|
const togglePanel = (show) => adminCard.classList.toggle("hidden", !show);
|
||||||
|
adminToggle.addEventListener("click", () => togglePanel(!adminCard.classList.contains("hidden")));
|
||||||
|
adminClose.addEventListener("click", () => togglePanel(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function adminAction(path, successMessage) {
|
async function adminAction(path, successMessage) {
|
||||||
@@ -250,6 +256,25 @@ async function refreshPhaseData() {
|
|||||||
await Promise.all([loadSuggestData(), loadRevealData(), loadVoteData(), loadResults()]);
|
await Promise.all([loadSuggestData(), loadRevealData(), loadVoteData(), loadResults()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildCard(s, { showAuthor }) {
|
||||||
|
const card = document.createElement("article");
|
||||||
|
card.className = "game-card";
|
||||||
|
const hasImage = !!s.screenshotUrl;
|
||||||
|
card.innerHTML = `
|
||||||
|
<div class="card-visual" style="${hasImage ? `background-image:url('${s.screenshotUrl}')` : ''}"></div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="card-title-row">
|
||||||
|
<h3>${s.name}</h3>
|
||||||
|
${showAuthor && s.author ? `<span class="chip">${s.author}</span>` : ""}
|
||||||
|
</div>
|
||||||
|
${s.genre ? `<p class="muted">${s.genre}</p>` : ""}
|
||||||
|
${s.description ? `<p>${s.description}</p>` : ""}
|
||||||
|
${s.youtubeUrl ? `<a class="link" href="${s.youtubeUrl}" target="_blank" rel="noopener">YouTube ↗</a>` : ""}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
return card;
|
||||||
|
}
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
setupHandlers();
|
setupHandlers();
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -7,34 +7,29 @@
|
|||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<header class="hero">
|
<div class="status-bar">
|
||||||
<div>
|
<span class="status-dot"></span>
|
||||||
<h1>CoopGameChooser</h1>
|
<span id="phase-pill">Loading…</span>
|
||||||
<p class="subtitle">Blind suggestions, blind votes, quick decision.</p>
|
<span class="counts" id="counts">—</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="phase-pill" id="phase-pill">Loading…</div>
|
|
||||||
</header>
|
|
||||||
|
|
||||||
<main class="grid">
|
<main class="grid">
|
||||||
<section class="card" id="identity-card">
|
|
||||||
<h2>Your Name</h2>
|
|
||||||
<label class="stack">
|
|
||||||
<span class="label">Display name</span>
|
|
||||||
<input id="name-input" maxlength="64" placeholder="Pick a name" />
|
|
||||||
</label>
|
|
||||||
<button id="save-name">Save</button>
|
|
||||||
<p class="hint" id="player-id"></p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card" id="phase-card">
|
|
||||||
<h2>Current Phase</h2>
|
|
||||||
<p id="phase-description">Loading…</p>
|
|
||||||
<p class="hint" id="counts"></p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section class="card" id="actions-card">
|
<section class="card" id="actions-card">
|
||||||
<div id="suggest-view" class="phase-view hidden">
|
<div id="suggest-view" class="phase-view hidden">
|
||||||
|
<div class="split">
|
||||||
|
<div>
|
||||||
<h2>Suggest (up to 3)</h2>
|
<h2>Suggest (up to 3)</h2>
|
||||||
|
<p class="hint">Only you can see your suggestions until Reveal.</p>
|
||||||
|
</div>
|
||||||
|
<div class="name-box">
|
||||||
|
<label class="stack">
|
||||||
|
<span class="label">Your name</span>
|
||||||
|
<input id="name-input" maxlength="64" placeholder="Pick a name" />
|
||||||
|
</label>
|
||||||
|
<button id="save-name" class="ghost">Save</button>
|
||||||
|
<p class="hint" id="player-id"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<form id="suggest-form" class="stack">
|
<form id="suggest-form" class="stack">
|
||||||
<input name="name" required maxlength="100" placeholder="Game name *" />
|
<input name="name" required maxlength="100" placeholder="Game name *" />
|
||||||
<input name="genre" maxlength="50" placeholder="Genre" />
|
<input name="genre" maxlength="50" placeholder="Genre" />
|
||||||
@@ -45,27 +40,32 @@
|
|||||||
</div>
|
</div>
|
||||||
<button type="submit">Submit</button>
|
<button type="submit">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
<ul id="my-suggestions" class="list"></ul>
|
<div id="my-suggestions" class="card-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="reveal-view" class="phase-view hidden">
|
<div id="reveal-view" class="phase-view hidden">
|
||||||
<h2>All Suggestions</h2>
|
<h2>All Suggestions</h2>
|
||||||
<ul id="all-suggestions" class="list"></ul>
|
<div id="all-suggestions" class="card-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="vote-view" class="phase-view hidden">
|
<div id="vote-view" class="phase-view hidden">
|
||||||
<h2>Vote 0–10</h2>
|
<h2>Vote 0–10</h2>
|
||||||
<ul id="vote-list" class="list"></ul>
|
<div id="vote-list" class="card-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="results-view" class="phase-view hidden">
|
<div id="results-view" class="phase-view hidden">
|
||||||
<h2>Results</h2>
|
<h2>Results</h2>
|
||||||
<ol id="results-list" class="list"></ol>
|
<div id="results-list" class="card-grid results-grid"></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
</main>
|
||||||
|
|
||||||
<section class="card" id="admin-card">
|
<button id="admin-toggle" class="admin-toggle" title="Admin tools">•••</button>
|
||||||
<h2>Admin</h2>
|
<section class="card admin-panel hidden" id="admin-card">
|
||||||
|
<div class="panel-header">
|
||||||
|
<h3>Admin</h3>
|
||||||
|
<button id="admin-close" class="ghost">✕</button>
|
||||||
|
</div>
|
||||||
<label class="stack">
|
<label class="stack">
|
||||||
<span class="label">Admin key</span>
|
<span class="label">Admin key</span>
|
||||||
<input id="admin-key" type="password" placeholder="X-Admin-Key" />
|
<input id="admin-key" type="password" placeholder="X-Admin-Key" />
|
||||||
@@ -84,7 +84,6 @@
|
|||||||
<button id="factory-reset" class="danger">Factory reset</button>
|
<button id="factory-reset" class="danger">Factory reset</button>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
|
||||||
|
|
||||||
<div id="toast" class="toast hidden"></div>
|
<div id="toast" class="toast hidden"></div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,43 +1,42 @@
|
|||||||
:root {
|
:root {
|
||||||
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
|
||||||
background: radial-gradient(circle at 20% 20%, #1f2937, #0b1224);
|
background: radial-gradient(circle at 20% 20%, #0f172a, #050816);
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 24px;
|
padding: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hero {
|
.status-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
background: linear-gradient(135deg, #111827, #0f172a);
|
gap: 10px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
background: rgba(15, 23, 42, 0.8);
|
||||||
border: 1px solid #1f2937;
|
border: 1px solid #1f2937;
|
||||||
border-radius: 12px;
|
border-radius: 10px;
|
||||||
padding: 16px 20px;
|
box-shadow: 0 10px 24px rgba(0,0,0,0.25);
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
|
max-width: 540px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.subtitle {
|
.status-dot {
|
||||||
margin: 4px 0 0;
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #22c55e;
|
||||||
|
box-shadow: 0 0 10px #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.counts {
|
||||||
color: #9ca3af;
|
color: #9ca3af;
|
||||||
}
|
font-size: 13px;
|
||||||
|
|
||||||
.phase-pill {
|
|
||||||
padding: 8px 12px;
|
|
||||||
background: #1d4ed8;
|
|
||||||
color: white;
|
|
||||||
border-radius: 999px;
|
|
||||||
font-weight: 600;
|
|
||||||
min-width: 96px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.grid {
|
.grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
grid-template-columns: 1fr;
|
||||||
gap: 16px;
|
gap: 16px;
|
||||||
margin-top: 16px;
|
margin-top: 16px;
|
||||||
}
|
}
|
||||||
@@ -55,6 +54,23 @@ body {
|
|||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.split {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 16px;
|
||||||
|
align-items: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name-box {
|
||||||
|
min-width: 220px;
|
||||||
|
max-width: 260px;
|
||||||
|
background: #0b1224;
|
||||||
|
border: 1px solid #1f2937;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.stack {
|
.stack {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -92,16 +108,70 @@ button.danger {
|
|||||||
border-color: #b91c1c;
|
border-color: #b91c1c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button.ghost {
|
||||||
|
background: transparent;
|
||||||
|
border-color: #374151;
|
||||||
|
}
|
||||||
|
|
||||||
.label { color: #9ca3af; font-size: 12px; }
|
.label { color: #9ca3af; font-size: 12px; }
|
||||||
.hint { color: #9ca3af; font-size: 12px; margin: 8px 0 0; }
|
.hint { color: #9ca3af; font-size: 12px; margin: 8px 0 0; }
|
||||||
|
|
||||||
.list { list-style: none; padding: 0; margin: 8px 0 0; display: flex; flex-direction: column; gap: 10px; }
|
.card-grid {
|
||||||
.list li { padding: 12px; border: 1px solid #1f2937; border-radius: 8px; background: #0b1224; }
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
.vote-row { display: flex; justify-content: space-between; gap: 12px; align-items: center; }
|
.game-card {
|
||||||
.vote-controls { display: flex; gap: 10px; align-items: center; min-width: 180px; }
|
background: #0b1224;
|
||||||
|
border: 1px solid #1f2937;
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-visual {
|
||||||
|
height: 140px;
|
||||||
|
background: linear-gradient(135deg, #1d4ed8, #22c55e);
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-title-row h3 { margin: 0; font-size: 18px; }
|
||||||
|
.muted { color: #9ca3af; margin: 0; }
|
||||||
|
.link { color: #93c5fd; text-decoration: none; font-weight: 600; }
|
||||||
|
.link:hover { text-decoration: underline; }
|
||||||
|
.chip {
|
||||||
|
background: #1f2937;
|
||||||
|
color: #e5e7eb;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vote-controls { display: flex; gap: 10px; align-items: center; margin-top: 6px; }
|
||||||
.score { font-weight: 700; }
|
.score { font-weight: 700; }
|
||||||
|
|
||||||
|
.results-grid .game-card { border-color: #2563eb44; }
|
||||||
|
|
||||||
.hidden { display: none !important; }
|
.hidden { display: none !important; }
|
||||||
|
|
||||||
.toast {
|
.toast {
|
||||||
@@ -116,3 +186,31 @@ button.danger {
|
|||||||
max-width: 320px;
|
max-width: 320px;
|
||||||
}
|
}
|
||||||
.toast.error { background: #dc2626; }
|
.toast.error { background: #dc2626; }
|
||||||
|
|
||||||
|
.admin-toggle {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 18px;
|
||||||
|
right: 18px;
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid #1f2937;
|
||||||
|
background: #0f172a;
|
||||||
|
color: #9ca3af;
|
||||||
|
font-weight: 700;
|
||||||
|
box-shadow: 0 8px 20px rgba(0,0,0,0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.admin-panel {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 70px;
|
||||||
|
right: 18px;
|
||||||
|
width: 320px;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
.panel-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user