fix(textbooks): Алгебра 8 — два бага

1. Hover-preview карточек §§ перекрывал соседнюю карточку
   - Было: position absolute, left:105% — превью §4 ложилось на §5
   - Стало: top:calc(100% + 8px), left:50% center, max-width:90vw
   - Добавлена 'стрелка-носик' указывающая на карточку
   - z-index 100 → 200 (поверх соседей)

2. 'Существует или нет?' в §1 — добавлен click-fallback
   - HTML5 drag&drop сохранён для desktop
   - Альтернатива: клик на корне → клик на зоне (yes/no/обратно в pool)
   - Жёлтый outline для выбранного элемента
   - Hover-эффект на .drag-item (translateY -1px + shadow)
   - Подсказка в widget-описании обновлена
This commit is contained in:
Maxim Dolgolyov
2026-05-27 12:40:54 +03:00
parent 0248e3db61
commit ed93b696bd
+33 -7
View File
@@ -184,9 +184,11 @@ input,select,textarea{font-family:inherit}
.dz{min-height:60px;padding:12px;border:2px dashed var(--border);border-radius:10px;background:var(--card);display:flex;flex-wrap:wrap;gap:6px;align-content:flex-start;transition:border-color .15s,background .15s}
.dz.over{border-color:var(--pri);background:var(--pri-soft)}
.dz-label{font-size:.78rem;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.06em}
.drag-item{padding:6px 12px;background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff;border-radius:8px;font-weight:600;font-size:.88rem;cursor:grab;user-select:none;font-family:'JetBrains Mono',monospace;box-shadow:var(--sh)}
.drag-item{padding:6px 12px;background:linear-gradient(135deg,var(--acc),var(--acc2));color:#fff;border-radius:8px;font-weight:600;font-size:.88rem;cursor:grab;user-select:none;font-family:'JetBrains Mono',monospace;box-shadow:var(--sh);transition:transform .12s,box-shadow .12s}
.drag-item:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(3,169,244,.35)}
.drag-item:active{cursor:grabbing}
.drag-item.dragging{opacity:.5}
.drag-item.selected{outline:3px solid #FFD166;outline-offset:2px;box-shadow:0 0 0 6px rgba(255,209,102,.22),var(--sh)}
/* NUMBER LINE */
.num-line{position:relative;height:60px;background:var(--card);border-radius:8px;margin:14px 0;border:1px solid var(--border)}
@@ -452,9 +454,11 @@ input,select,textarea{font-family:inherit}
/* Task 7: psel-card hover preview */
.psel-card{overflow:visible}
/* restore border-radius clip for the active stripe via pseudo-element only on non-preview */
.psel-card-preview{display:none;position:absolute;left:105%;top:0;width:200px;background:var(--card);border:1.5px solid var(--pri);border-radius:12px;padding:12px 14px;z-index:100;box-shadow:var(--sh2);pointer-events:none;animation:previewIn .18s ease}
@keyframes previewIn{from{opacity:0;transform:translateX(-8px)}to{opacity:1;transform:none}}
.psel-card-preview{display:none;position:absolute;left:50%;top:calc(100% + 8px);transform:translateX(-50%);width:220px;max-width:90vw;background:var(--card);border:1.5px solid var(--pri);border-radius:12px;padding:12px 14px;z-index:200;box-shadow:var(--sh2);pointer-events:none;animation:previewIn .18s ease}
@keyframes previewIn{from{opacity:0;transform:translateX(-50%) translateY(-6px)}to{opacity:1;transform:translateX(-50%) translateY(0)}}
.psel-card:hover .psel-card-preview{display:block}
/* стрелка-носик у превью, указывает на карточку */
.psel-card-preview::before{content:'';position:absolute;top:-6px;left:50%;transform:translateX(-50%) rotate(45deg);width:10px;height:10px;background:var(--card);border-left:1.5px solid var(--pri);border-top:1.5px solid var(--pri)}
.psel-preview-title{font-size:.72rem;font-weight:800;color:var(--pri2);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}
.psel-preview-topic{font-size:.78rem;color:var(--text);margin-bottom:2px;padding-left:8px;border-left:2px solid var(--sec-acc,var(--pri))}
.psel-preview-bar{height:5px;background:rgba(233,30,99,.12);border-radius:3px;overflow:hidden;margin-top:8px}
@@ -1674,7 +1678,7 @@ function buildP1(){
</ol>
`)}
${widget('«Существует или нет?»', 'DRAG', 'Перетащите выражения в правильный столбик. Подсказка: подкоренное число должно быть неотрицательным.', `
${widget('«Существует или нет?»', 'DRAG', 'Перетащите выражение в нужный столбик или кликните, чтобы выбрать → кликните по зоне. Подкоренное число должно быть неотрицательным.', `
<div style="display:grid;grid-template-columns:1fr 1fr;gap:14px">
<div>
<div class="dz-label" style="color:var(--ok)">Существует</div>
@@ -1992,21 +1996,30 @@ const EXISTS_ITEMS = [
{expr:'√0.49', val:'yes'}, {expr:'√(-100)', val:'no'}, {expr:'√(-0.1)', val:'no'},
{expr:'√169', val:'yes'}, {expr:'√(-9)', val:'no'},
];
let _existsSelected = null;
function initExists(){
const pool = document.getElementById('exists-pool');
if(!pool) return;
existsReset();
// setup dz drop handlers
// HTML5 drag-and-drop
['exists-yes','exists-no','exists-pool'].forEach(id=>{
const dz = document.getElementById(id);
dz.addEventListener('dragover', e=>{ e.preventDefault(); dz.classList.add('over'); });
dz.addEventListener('dragleave', ()=>dz.classList.remove('over'));
dz.addEventListener('drop', e=>{
e.preventDefault(); dz.classList.remove('over');
const id = e.dataTransfer.getData('text/plain');
const item = document.getElementById(id);
const itemId = e.dataTransfer.getData('text/plain');
const item = document.getElementById(itemId);
if(item) dz.appendChild(item);
});
// Click-режим: если выбран элемент — клик по зоне переносит его
dz.addEventListener('click', e=>{
if(!_existsSelected) return;
if(e.target.closest('.drag-item') && e.target.closest('.drag-item') !== _existsSelected) return;
dz.appendChild(_existsSelected);
_existsSelected.classList.remove('selected');
_existsSelected = null;
});
});
}
function existsReset(){
@@ -2014,6 +2027,7 @@ function existsReset(){
pool.innerHTML = '';
document.getElementById('exists-yes').innerHTML='';
document.getElementById('exists-no').innerHTML='';
_existsSelected = null;
EXISTS_ITEMS.forEach((it,i)=>{
const d = el('div', {class:'drag-item', id:'exi-'+i, draggable:'true'}, it.expr);
d.dataset.val = it.val;
@@ -2022,6 +2036,18 @@ function existsReset(){
d.classList.add('dragging');
});
d.addEventListener('dragend', ()=>d.classList.remove('dragging'));
// Click — выбор / снятие выбора
d.addEventListener('click', ev=>{
ev.stopPropagation();
if(_existsSelected === d){
d.classList.remove('selected');
_existsSelected = null;
return;
}
document.querySelectorAll('.drag-item.selected').forEach(x=>x.classList.remove('selected'));
d.classList.add('selected');
_existsSelected = d;
});
pool.appendChild(d);
});
document.getElementById('exists-fb').className='feedback';