revert(textbooks ui): откат компактной сетки — возврат к крупным карточкам с обложками
This commit is contained in:
+115
-184
@@ -22,121 +22,88 @@
|
||||
.tb-title { font-family:'Unbounded',sans-serif; font-size:1.35rem; font-weight:800; letter-spacing:-.02em; }
|
||||
.tb-sub { font-size:.82rem; color:var(--text-2); margin-top:2px; }
|
||||
|
||||
/* ── Filter chips ── */
|
||||
.tb-filters {
|
||||
display:flex; gap:6px; flex-wrap:wrap; margin-bottom:16px; align-items:center;
|
||||
}
|
||||
.tb-filter-label {
|
||||
font-size:.7rem; font-weight:700; color:var(--text-3);
|
||||
text-transform:uppercase; letter-spacing:.06em; margin-right:6px;
|
||||
}
|
||||
.tb-chip {
|
||||
padding:5px 11px; border-radius:99px;
|
||||
background:var(--surface); border:1.5px solid var(--border);
|
||||
color:var(--text-2); font-family:'Manrope',sans-serif;
|
||||
font-size:.78rem; font-weight:700; cursor:pointer;
|
||||
transition:all .14s; display:inline-flex; align-items:center; gap:5px;
|
||||
}
|
||||
.tb-chip:hover { color:var(--text); border-color:var(--text-3); }
|
||||
.tb-chip.active {
|
||||
background:var(--violet); color:#fff; border-color:var(--violet);
|
||||
}
|
||||
.tb-chip-count {
|
||||
font-size:.68rem; opacity:.7; font-weight:600;
|
||||
}
|
||||
|
||||
/* ── Compact grid ── */
|
||||
.tb-grid {
|
||||
display:grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
|
||||
gap:10px;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap:22px;
|
||||
}
|
||||
|
||||
.tb-card {
|
||||
position:relative;
|
||||
background:var(--surface);
|
||||
border:1.5px solid var(--border);
|
||||
border-radius:12px; overflow:hidden;
|
||||
display:flex; align-items:stretch;
|
||||
text-decoration:none; color:inherit;
|
||||
transition: border-color .14s, transform .14s, box-shadow .14s;
|
||||
min-height:74px;
|
||||
border-radius:18px; overflow:hidden;
|
||||
transition: border-color .18s, box-shadow .18s, transform .18s;
|
||||
display:flex; flex-direction:column;
|
||||
}
|
||||
.tb-card:hover {
|
||||
transform: translateY(-1px);
|
||||
border-color: var(--text-3);
|
||||
box-shadow: 0 4px 14px rgba(0,0,0,.08);
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 12px 36px rgba(0,0,0,.18);
|
||||
}
|
||||
|
||||
/* Left color strip (cover-marker) */
|
||||
.tb-mark {
|
||||
flex:0 0 46px;
|
||||
display:flex; flex-direction:column;
|
||||
align-items:center; justify-content:center;
|
||||
color:#fff; position:relative; overflow:hidden;
|
||||
.tb-cover {
|
||||
height:140px; position:relative; overflow:hidden;
|
||||
display:flex; align-items:flex-end; padding:18px 22px 14px;
|
||||
}
|
||||
.tb-mark::before {
|
||||
.tb-cover.amber { background:linear-gradient(135deg, #b45309 0%, #d97706 60%, #f59e0b 100%); }
|
||||
.tb-cover.blue { background:linear-gradient(135deg, #1e40af 0%, #2563eb 60%, #3b82f6 100%); }
|
||||
.tb-cover.green { background:linear-gradient(135deg, #047857 0%, #059669 60%, #10b981 100%); }
|
||||
.tb-cover.violet { background:linear-gradient(135deg, #6d28d9 0%, #7c3aed 60%, #9333ea 100%); }
|
||||
.tb-cover.pink { background:linear-gradient(135deg, #be185d 0%, #db2777 60%, #ec4899 100%); }
|
||||
.tb-cover.indigo { background:linear-gradient(135deg, #3730a3 0%, #4f46e5 60%, #818cf8 100%); }
|
||||
.tb-cover.rose { background:linear-gradient(135deg, #9f1239 0%, #e11d48 60%, #fb7185 100%); }
|
||||
.tb-cover.teal { background:linear-gradient(135deg, #134e4a 0%, #0d9488 60%, #14b8a6 100%); }
|
||||
.tb-cover.cyan { background:linear-gradient(135deg, #164e63 0%, #0891b2 60%, #22d3ee 100%); }
|
||||
.tb-cover.emerald{ background:linear-gradient(135deg, #064e3b 0%, #059669 60%, #34d399 100%); }
|
||||
.tb-cover.amber-light{ background:linear-gradient(135deg, #92400e 0%, #d97706 60%, #fbbf24 100%); }
|
||||
|
||||
.tb-cover::before {
|
||||
content: attr(data-watermark);
|
||||
position:absolute; left:50%; top:50%; transform:translate(-50%,-50%);
|
||||
position:absolute; right:-10px; top:-15%;
|
||||
font-family:'Unbounded',sans-serif; font-weight:900;
|
||||
font-size:2.4rem; letter-spacing:-.04em; line-height:1;
|
||||
color:rgba(255,255,255,.16);
|
||||
pointer-events:none; user-select:none; z-index:0;
|
||||
font-size:clamp(3rem, 9vw, 7rem); letter-spacing:-.04em; line-height:1;
|
||||
color:transparent; -webkit-text-stroke:1.5px rgba(255,255,255,.18);
|
||||
pointer-events:none; user-select:none;
|
||||
}
|
||||
.tb-mark-grade {
|
||||
position:relative; z-index:1;
|
||||
.tb-cover-info {
|
||||
position:relative; z-index:1; color:#fff;
|
||||
}
|
||||
.tb-cover-grade {
|
||||
display:inline-flex; align-items:center; gap:4px;
|
||||
padding:3px 10px; border-radius:99px;
|
||||
background:rgba(255,255,255,.18); backdrop-filter:blur(4px);
|
||||
font-size:.7rem; font-weight:800; text-transform:uppercase; letter-spacing:.08em;
|
||||
margin-bottom:6px;
|
||||
}
|
||||
.tb-cover-title {
|
||||
font-family:'Unbounded',sans-serif; font-weight:800;
|
||||
font-size:1.1rem; letter-spacing:-.02em; line-height:1;
|
||||
font-size:1.15rem; letter-spacing:-.01em;
|
||||
}
|
||||
.tb-mark-sub {
|
||||
position:relative; z-index:1;
|
||||
font-size:.56rem; font-weight:800; text-transform:uppercase;
|
||||
letter-spacing:.08em; margin-top:3px; opacity:.85;
|
||||
}
|
||||
.tb-mark.amber { background:linear-gradient(160deg, #b45309 0%, #f59e0b 100%); }
|
||||
.tb-mark.blue { background:linear-gradient(160deg, #1e40af 0%, #3b82f6 100%); }
|
||||
.tb-mark.green { background:linear-gradient(160deg, #047857 0%, #10b981 100%); }
|
||||
.tb-mark.violet { background:linear-gradient(160deg, #6d28d9 0%, #9333ea 100%); }
|
||||
.tb-mark.pink { background:linear-gradient(160deg, #be185d 0%, #ec4899 100%); }
|
||||
.tb-mark.indigo { background:linear-gradient(160deg, #3730a3 0%, #818cf8 100%); }
|
||||
.tb-mark.rose { background:linear-gradient(160deg, #9f1239 0%, #fb7185 100%); }
|
||||
.tb-mark.teal { background:linear-gradient(160deg, #134e4a 0%, #14b8a6 100%); }
|
||||
.tb-mark.cyan { background:linear-gradient(160deg, #164e63 0%, #22d3ee 100%); }
|
||||
.tb-mark.emerald{ background:linear-gradient(160deg, #064e3b 0%, #34d399 100%); }
|
||||
.tb-mark.amber-light{ background:linear-gradient(160deg, #92400e 0%, #fbbf24 100%); }
|
||||
|
||||
/* Body */
|
||||
.tb-body {
|
||||
flex:1; min-width:0;
|
||||
padding:9px 12px 8px 12px;
|
||||
display:flex; flex-direction:column; gap:5px;
|
||||
justify-content:center;
|
||||
padding:16px 20px 18px; flex:1;
|
||||
display:flex; flex-direction:column; gap:10px;
|
||||
}
|
||||
.tb-title-row {
|
||||
display:flex; align-items:flex-start; gap:6px;
|
||||
.tb-author {
|
||||
font-size:.78rem; color:var(--text-2); font-weight:600;
|
||||
display:inline-flex; align-items:center; gap:6px;
|
||||
}
|
||||
.tb-name {
|
||||
font-family:'Manrope',sans-serif; font-weight:700;
|
||||
font-size:.86rem; line-height:1.25; color:var(--text);
|
||||
overflow:hidden; display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical;
|
||||
.tb-author svg { width:13px; height:13px; opacity:.7; }
|
||||
.tb-desc {
|
||||
font-size:.85rem; line-height:1.55; color:var(--text-2);
|
||||
flex:1;
|
||||
}
|
||||
.tb-meta {
|
||||
font-size:.68rem; color:var(--text-3); font-weight:600;
|
||||
display:flex; align-items:center; gap:6px;
|
||||
}
|
||||
.tb-meta b { color:var(--text-2); font-weight:700; }
|
||||
|
||||
/* Progress strip */
|
||||
.tb-progress {
|
||||
display:flex; align-items:center; gap:6px;
|
||||
margin-top:auto;
|
||||
margin-top:6px;
|
||||
padding-top:12px; border-top:1px solid var(--border);
|
||||
}
|
||||
.tb-progress-bar {
|
||||
flex:1; height:3px; border-radius:99px; background:var(--border); overflow:hidden;
|
||||
height:6px; border-radius:99px; background:var(--border); overflow:hidden;
|
||||
margin-bottom:7px;
|
||||
}
|
||||
.tb-progress-fill {
|
||||
height:100%; border-radius:99px; transition: width .3s ease;
|
||||
height:100%; border-radius:99px;
|
||||
transition: width .3s ease;
|
||||
}
|
||||
.tb-progress.amber .tb-progress-fill { background:#d97706; }
|
||||
.tb-progress.blue .tb-progress-fill { background:#2563eb; }
|
||||
@@ -149,34 +116,42 @@
|
||||
.tb-progress.cyan .tb-progress-fill { background:#0891b2; }
|
||||
.tb-progress.emerald .tb-progress-fill { background:#059669; }
|
||||
.tb-progress-text {
|
||||
font-size:.66rem; color:var(--text-3); font-weight:700;
|
||||
font-variant-numeric: tabular-nums;
|
||||
min-width:28px; text-align:right;
|
||||
display:flex; justify-content:space-between; align-items:center;
|
||||
font-size:.74rem; color:var(--text-3);
|
||||
}
|
||||
.tb-progress-text b { color:var(--text); font-weight:700; }
|
||||
|
||||
.tb-actions {
|
||||
display:flex; gap:8px; margin-top:12px;
|
||||
}
|
||||
.tb-btn {
|
||||
flex:1; padding:9px 14px; border-radius:10px;
|
||||
border:1.5px solid var(--border-h); background:transparent; color:var(--text);
|
||||
font-family:'Manrope',sans-serif; font-size:.85rem; font-weight:700;
|
||||
cursor:pointer; transition:all .15s; text-decoration:none;
|
||||
display:inline-flex; align-items:center; justify-content:center; gap:6px;
|
||||
}
|
||||
.tb-btn:hover { border-color:var(--text-2); }
|
||||
.tb-btn.primary {
|
||||
border-color:transparent; color:#fff;
|
||||
}
|
||||
.tb-btn.primary.amber { background:#d97706; }
|
||||
.tb-btn.primary.blue { background:#2563eb; }
|
||||
.tb-btn.primary.green { background:#059669; }
|
||||
.tb-btn.primary.violet { background:#7c3aed; }
|
||||
.tb-btn.primary.pink { background:#db2777; }
|
||||
.tb-btn.primary.indigo { background:#4f46e5; }
|
||||
.tb-btn.primary.rose { background:#e11d48; }
|
||||
.tb-btn.primary.teal { background:#0d9488; }
|
||||
.tb-btn.primary.cyan { background:#0891b2; }
|
||||
.tb-btn.primary.emerald { background:#059669; }
|
||||
.tb-btn.primary:hover { filter:brightness(1.1); }
|
||||
.tb-btn svg { width:14px; height:14px; }
|
||||
|
||||
/* Teacher assign button — overlay on hover */
|
||||
.tb-assign-btn {
|
||||
position:absolute; top:5px; right:5px;
|
||||
width:24px; height:24px; padding:0; border-radius:6px;
|
||||
background:rgba(255,255,255,.92); border:1px solid var(--border);
|
||||
color:var(--text-2); cursor:pointer;
|
||||
display:flex; align-items:center; justify-content:center;
|
||||
opacity:0; transition:opacity .14s, color .14s, transform .14s;
|
||||
z-index:2;
|
||||
width:auto; min-width:42px; padding:9px 12px;
|
||||
flex:0 0 auto;
|
||||
}
|
||||
html.dark .tb-assign-btn { background:rgba(20,20,25,.92); }
|
||||
.tb-card:hover .tb-assign-btn { opacity:1; }
|
||||
.tb-assign-btn:hover { color:var(--violet); transform:scale(1.05); }
|
||||
.tb-assign-btn svg { width:13px; height:13px; }
|
||||
|
||||
/* Continue badge (small "play" overlay on cards in progress) */
|
||||
.tb-resume {
|
||||
position:absolute; bottom:5px; right:5px;
|
||||
font-size:.6rem; font-weight:800; color:var(--text-3);
|
||||
text-transform:uppercase; letter-spacing:.04em;
|
||||
opacity:0; transition:opacity .14s;
|
||||
}
|
||||
.tb-card:hover .tb-resume { opacity:1; }
|
||||
|
||||
.tb-empty {
|
||||
grid-column: 1 / -1;
|
||||
@@ -184,12 +159,6 @@
|
||||
}
|
||||
.tb-empty svg { width:48px; height:48px; opacity:.5; margin-bottom:14px; stroke:var(--text-3); }
|
||||
|
||||
@media (max-width: 600px) {
|
||||
.tb-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap:8px; }
|
||||
.tb-name { font-size:.8rem; }
|
||||
.tb-mark { flex-basis:40px; }
|
||||
}
|
||||
|
||||
/* ── Tabs ── */
|
||||
.tb-tabs {
|
||||
display:flex; gap:4px; margin-bottom:24px;
|
||||
@@ -394,11 +363,6 @@
|
||||
|
||||
<!-- TAB: catalog -->
|
||||
<div class="tb-panel active" id="tb-panel-catalog">
|
||||
<div class="tb-filters" id="tb-filters" style="display:none">
|
||||
<span class="tb-filter-label">Предмет</span>
|
||||
<button class="tb-chip active" data-subject="all" onclick="setSubjectFilter('all')">Все <span class="tb-chip-count" id="cnt-all"></span></button>
|
||||
<span id="tb-subject-chips"></span>
|
||||
</div>
|
||||
<div class="tb-grid" id="tb-grid">
|
||||
<div class="tb-empty">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>
|
||||
@@ -488,87 +452,54 @@
|
||||
}
|
||||
}
|
||||
|
||||
let subjectFilter = 'all';
|
||||
const SUBJECT_LABELS = {
|
||||
physics:'Физика', math:'Математика', chemistry:'Химия', biology:'Биология',
|
||||
russian:'Русский', literature:'Литература', english:'Английский',
|
||||
history:'История', geography:'География', informatics:'Информатика'
|
||||
};
|
||||
const SUBJECT_WATERMARK = {
|
||||
physics:'Φ', math:'Σ', chemistry:'Х', biology:'Β',
|
||||
russian:'Р', literature:'Л', english:'E',
|
||||
history:'H', geography:'G', informatics:'I'
|
||||
};
|
||||
|
||||
window.setSubjectFilter = function(s) {
|
||||
subjectFilter = s;
|
||||
document.querySelectorAll('.tb-chip').forEach(c => c.classList.toggle('active', c.dataset.subject === s));
|
||||
render();
|
||||
};
|
||||
|
||||
function buildSubjectChips() {
|
||||
const counts = {};
|
||||
textbooks.forEach(t => { counts[t.subject] = (counts[t.subject] || 0) + 1; });
|
||||
const subjects = Object.keys(counts).sort();
|
||||
if (!subjects.length || subjects.length < 2) {
|
||||
document.getElementById('tb-filters').style.display = 'none';
|
||||
return;
|
||||
}
|
||||
document.getElementById('tb-filters').style.display = 'flex';
|
||||
document.getElementById('cnt-all').textContent = textbooks.length;
|
||||
const cont = document.getElementById('tb-subject-chips');
|
||||
cont.innerHTML = subjects.map(s => {
|
||||
const label = SUBJECT_LABELS[s] || s;
|
||||
return `<button class="tb-chip" data-subject="${esc(s)}" onclick="setSubjectFilter('${esc(s)}')">${esc(label)} <span class="tb-chip-count">${counts[s]}</span></button>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
function render() {
|
||||
const grid = document.getElementById('tb-grid');
|
||||
if (!textbooks.length) {
|
||||
grid.innerHTML = '<div class="tb-empty">Учебники не добавлены</div>';
|
||||
return;
|
||||
}
|
||||
buildSubjectChips();
|
||||
const filtered = subjectFilter === 'all' ? textbooks : textbooks.filter(t => t.subject === subjectFilter);
|
||||
if (!filtered.length) {
|
||||
grid.innerHTML = '<div class="tb-empty">По этому фильтру ничего нет</div>';
|
||||
return;
|
||||
}
|
||||
grid.innerHTML = filtered.map(t => {
|
||||
grid.innerHTML = textbooks.map(t => {
|
||||
const readCount = (t.progress?.read || []).length;
|
||||
const pct = t.para_count ? Math.round(100 * readCount / t.para_count) : 0;
|
||||
const watermark = SUBJECT_WATERMARK[t.subject] || '§';
|
||||
const watermark = t.subject === 'chemistry' ? 'Х' : t.subject === 'physics' ? 'Φ' : t.subject === 'math' ? 'Σ' : t.subject === 'biology' ? 'Β' : '§';
|
||||
const continueHref = t.progress?.last_para
|
||||
? `/textbook/${t.slug}#${t.progress.last_para}`
|
||||
: `/textbook/${t.slug}`;
|
||||
const resumeLabel = t.progress?.last_para ? 'Продолжить →' : 'Открыть →';
|
||||
const subjLabel = SUBJECT_LABELS[t.subject] || t.subject || '';
|
||||
|
||||
return `
|
||||
<a class="tb-card" href="${continueHref}" title="${esc(t.title)}\n${esc(t.description || '')}">
|
||||
<div class="tb-mark ${t.color}" data-watermark="${watermark}">
|
||||
<div class="tb-mark-grade">${t.grade}</div>
|
||||
<div class="tb-mark-sub">${esc(subjLabel.slice(0,4)).toLowerCase()}</div>
|
||||
<article class="tb-card">
|
||||
<div class="tb-cover ${t.color}" data-watermark="${watermark}">
|
||||
<div class="tb-cover-info">
|
||||
<div class="tb-cover-grade">${t.grade} класс</div>
|
||||
<div class="tb-cover-title">${esc(t.title)}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-body">
|
||||
<div class="tb-title-row">
|
||||
<div class="tb-name">${esc(t.title)}</div>
|
||||
</div>
|
||||
<div class="tb-meta">
|
||||
<span><b>${readCount}</b>/${t.para_count || '?'} §</span>
|
||||
${t.author ? `<span style="opacity:.6">·</span><span style="overflow:hidden;text-overflow:ellipsis;white-space:nowrap;min-width:0">${esc(t.author.split(' ').pop())}</span>` : ''}
|
||||
</div>
|
||||
${t.author ? `<div class="tb-author">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
|
||||
${esc(t.author)}
|
||||
</div>` : ''}
|
||||
<div class="tb-desc">${esc(t.description)}</div>
|
||||
<div class="tb-progress ${t.color}">
|
||||
<div class="tb-progress-bar"><div class="tb-progress-fill" style="width:${pct}%"></div></div>
|
||||
<div class="tb-progress-text">${pct}%</div>
|
||||
<div class="tb-progress-bar">
|
||||
<div class="tb-progress-fill" style="width:${pct}%"></div>
|
||||
</div>
|
||||
<div class="tb-progress-text">
|
||||
<span><b>${readCount}</b> из ${t.para_count} прочитано</span>
|
||||
<span>${pct}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tb-actions">
|
||||
<a href="${continueHref}" class="tb-btn primary ${t.color}">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polygon points="5 3 19 12 5 21 5 3"/></svg>
|
||||
${t.progress?.last_para ? 'Продолжить' : 'Открыть'}
|
||||
</a>
|
||||
${isTeacher ? `<button class="tb-btn tb-assign-btn" onclick="openAssignModal('${t.slug}', '${esc(t.title)}')" title="Назначить чтение как ДЗ">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M2 12h20"/></svg>
|
||||
</button>` : ''}
|
||||
</div>
|
||||
</div>
|
||||
${isTeacher ? `<button class="tb-assign-btn" onclick="event.preventDefault();event.stopPropagation();openAssignModal('${t.slug}', '${esc(t.title)}')" title="Назначить как ДЗ">
|
||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 2v20M2 12h20"/></svg>
|
||||
</button>` : ''}
|
||||
<div class="tb-resume">${resumeLabel}</div>
|
||||
</a>`;
|
||||
</article>`;
|
||||
}).join('');
|
||||
if (window.lucide) lucide.createIcons();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user