Full spec for ordered, sequentially unlocked, bundle-priced learning paths, plus 16 clickable prototypes (public org site, learner LMS, and teacher workspace) built on the packages/ui tokens and the minimal org-landing theme. The prototypes are the UX source of truth; start at prototypes/learning-paths/index.html. Also adds the write-prd skill codifying the clarify -> write -> prototype workflow.
217 lines
20 KiB
HTML
217 lines
20 KiB
HTML
<!doctype html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>Path Courses · ClassroomIO</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Geist:wght@300..800&display=swap" rel="stylesheet" />
|
|
<link rel="stylesheet" href="app-theme.css" />
|
|
<script>
|
|
if (localStorage.cioTheme === 'dark' || (!localStorage.cioTheme && matchMedia('(prefers-color-scheme: dark)').matches))
|
|
document.documentElement.classList.add('dark');
|
|
</script>
|
|
<style>
|
|
.rule { display: flex; align-items: center; gap: 13px; padding: 14px 16px; margin: 18px 0; }
|
|
.rule .ico { width: 34px; height: 34px; border-radius: var(--radius-md); background: color-mix(in oklab, var(--primary) 10%, transparent); display: grid; place-items: center; color: var(--primary); flex-shrink: 0; }
|
|
.rule .ico svg { width: 16px; height: 16px; stroke-width: 2.2; }
|
|
.rule .txt { flex: 1; }
|
|
.rule .txt b { font-size: 14px; font-weight: 600; }
|
|
.rule .txt p { margin: 1px 0 0; font-size: 13px; color: var(--muted-foreground); }
|
|
.list-head { display: flex; align-items: center; margin: 20px 2px 12px; }
|
|
.list-head h2 { font-size: 14px; font-weight: 600; margin: 0; }
|
|
.list-head .hint { margin-left: auto; font-size: 12.5px; color: var(--muted-foreground); display: flex; align-items: center; gap: 6px; }
|
|
.list-head .hint svg { width: 14px; height: 14px; stroke-width: 2; }
|
|
.courses { display: flex; flex-direction: column; gap: 8px; }
|
|
.row { display: grid; grid-template-columns: 24px 32px 60px 1fr auto; align-items: center; gap: 13px; background: var(--card); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 12px 14px; box-shadow: var(--shadow-2xs); transition: opacity 0.12s, border-color 0.12s, box-shadow 0.12s; }
|
|
.row.dragging { opacity: 0.4; }
|
|
.row.dragover { border-color: var(--primary); box-shadow: 0 0 0 3px color-mix(in oklab, var(--primary) 15%, transparent); }
|
|
.grip { color: var(--muted-foreground); cursor: grab; display: grid; place-items: center; }
|
|
.grip:active { cursor: grabbing; }
|
|
.grip svg { width: 16px; height: 16px; stroke-width: 2; }
|
|
.ord { width: 28px; height: 28px; border-radius: var(--radius-md); background: color-mix(in oklab, var(--primary) 10%, transparent); color: var(--primary); display: grid; place-items: center; font-weight: 600; font-size: 13px; }
|
|
.thumb { width: 60px; height: 42px; border-radius: var(--radius-md); }
|
|
.r-title { font-size: 14px; font-weight: 500; margin: 0; }
|
|
.r-meta { font-size: 12px; color: var(--muted-foreground); margin: 2px 0 0; display: flex; align-items: center; gap: 12px; }
|
|
.r-meta .m { display: flex; align-items: center; gap: 4px; }
|
|
.r-meta svg { width: 12px; height: 12px; stroke-width: 2; }
|
|
.r-actions { display: flex; align-items: center; gap: 2px; }
|
|
.mini { width: 30px; height: 30px; border-radius: var(--radius-md); border: none; background: transparent; display: grid; place-items: center; color: var(--muted-foreground); cursor: pointer; }
|
|
.mini:hover { background: var(--accent); color: var(--foreground); }
|
|
.mini.danger:hover { color: var(--destructive); background: color-mix(in oklab, var(--destructive) 10%, transparent); }
|
|
.mini svg { width: 15px; height: 15px; stroke-width: 2; }
|
|
.add-course { display: flex; align-items: center; justify-content: center; gap: 8px; border: 1px dashed var(--border); border-radius: var(--radius-md); padding: 13px; color: var(--muted-foreground); font-weight: 500; font-size: 13.5px; cursor: pointer; margin-top: 8px; background: transparent; width: 100%; }
|
|
.add-course:hover { border-color: var(--primary); color: var(--primary); background: color-mix(in oklab, var(--primary) 5%, transparent); }
|
|
.add-course svg { width: 16px; height: 16px; stroke-width: 2.2; }
|
|
.cert-row { display: grid; grid-template-columns: 24px 32px 60px 1fr auto; align-items: center; gap: 13px; padding: 12px 14px; border: 1px dashed var(--border); border-radius: var(--radius-md); margin-top: 8px; color: var(--muted-foreground); }
|
|
.cert-row .ord { background: var(--muted); color: var(--muted-foreground); border-radius: 999px; }
|
|
.cert-row .ord svg { width: 14px; height: 14px; stroke-width: 2; }
|
|
@media (max-width: 720px) { .row, .cert-row { grid-template-columns: 22px 28px 1fr auto; } .thumb { display: none; } }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="app">
|
|
<aside class="sidebar">
|
|
<a class="back-link" href="teacher-paths.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M19 12H5M12 19l-7-7 7-7"/></svg>All Learning Paths</a>
|
|
<div class="path-id">
|
|
<div class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M6 3v12"/><circle cx="6" cy="18" r="3"/><circle cx="18" cy="6" r="3"/><path d="M18 9a9 9 0 0 1-9 9"/></svg></div>
|
|
<div><div class="nm">Frontend Engineering Bootcamp</div><div class="st st-active"><span class="dot"></span>Active</div></div>
|
|
</div>
|
|
<a class="nav-item active" href="teacher-path-builder.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M4 19.5A2.5 2.5 0 0 1 6.5 17H20"/><path d="M6.5 2H20v20H6.5A2.5 2.5 0 0 1 4 19.5v-15A2.5 2.5 0 0 1 6.5 2z"/></svg>Courses<span class="nav-tail tnum">5</span></a>
|
|
<a class="nav-item" href="teacher-path-people.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/></svg>People<span class="nav-tail tnum">48</span></a>
|
|
<a class="nav-item" href="teacher-path-analytics.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 3v18h18"/><path d="m19 9-5 5-4-4-3 3"/></svg>Analytics</a>
|
|
<a class="nav-item" href="teacher-path-landing-editor.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="10"/><path d="M2 12h20M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/></svg>Landing page</a>
|
|
<a class="nav-item" href="teacher-path-certificate.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg>Certificate</a>
|
|
<a class="nav-item" href="teacher-path-settings.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="3"/><path d="M12 3v2M12 19v2M3 12h2M19 12h2M5.6 5.6l1.4 1.4M17 17l1.4 1.4M5.6 18.4 7 17M17 7l1.4-1.4"/></svg>Settings</a>
|
|
<div class="sidebar-foot"><div class="avatar">JD</div><div class="who"><div class="nm">Jordan Diallo</div><div class="rl">Admin</div></div></div>
|
|
</aside>
|
|
|
|
<div class="main">
|
|
<header class="topbar">
|
|
<nav class="crumbs">
|
|
<a href="teacher-paths.html">Learning Paths</a><span class="sep">/</span>
|
|
<span class="here">Frontend Engineering Bootcamp</span>
|
|
</nav>
|
|
<div class="top-actions">
|
|
<button class="icon-btn" id="theme" title="Toggle theme" aria-label="Toggle theme"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></button>
|
|
<a class="btn btn-outline btn-sm" href="teacher-path-setup.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>Finish setup · 33%</a>
|
|
<a class="btn btn-outline btn-sm" href="path-detail.html"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/></svg>Preview as student</a>
|
|
</div>
|
|
</header>
|
|
|
|
<main class="content">
|
|
<div class="wrap-narrow" style="max-width: 820px">
|
|
<div class="page-head" style="margin-bottom: 4px">
|
|
<div>
|
|
<h1 class="page-title">Courses</h1>
|
|
<p class="page-sub">Add courses and drag to set the order students move through them.</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card rule">
|
|
<div class="ico"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><rect x="3" y="11" width="18" height="11" rx="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></div>
|
|
<div class="txt">
|
|
<b>Unlock courses in order</b>
|
|
<p>Each course stays locked until a student completes the previous one's lessons and exercises. Turn off to allow any order.</p>
|
|
</div>
|
|
<label class="switch"><input type="checkbox" checked aria-label="Unlock courses in order" /><span class="track"></span><span class="knob"></span></label>
|
|
</div>
|
|
|
|
<div class="list-head">
|
|
<h2>5 courses in this path</h2>
|
|
<span class="hint"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg>Drag to reorder</span>
|
|
</div>
|
|
|
|
<div class="courses" id="courses">
|
|
<div class="row" draggable="true">
|
|
<span class="grip" aria-label="Drag to reorder"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg></span>
|
|
<span class="ord tnum">1</span>
|
|
<span class="thumb" style="background: linear-gradient(135deg, oklch(0.705 0.213 47.604), oklch(0.75 0.183 55.934))"></span>
|
|
<div>
|
|
<h3 class="r-title">HTML & CSS Foundations</h3>
|
|
<p class="r-meta"><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>12 lessons</span><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>2 exercises</span><span class="m">$49</span></p>
|
|
</div>
|
|
<div class="r-actions">
|
|
<button class="mini" title="Open course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></button>
|
|
<button class="mini danger" title="Remove from path"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" draggable="true">
|
|
<span class="grip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg></span>
|
|
<span class="ord tnum">2</span>
|
|
<span class="thumb" style="background: linear-gradient(135deg, oklch(0.795 0.184 86.047), oklch(0.769 0.188 70.08))"></span>
|
|
<div>
|
|
<h3 class="r-title">JavaScript Essentials</h3>
|
|
<p class="r-meta"><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>10 lessons</span><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>3 exercises</span><span class="m">$59</span></p>
|
|
</div>
|
|
<div class="r-actions">
|
|
<button class="mini" title="Open course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></button>
|
|
<button class="mini danger" title="Remove from path"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" draggable="true">
|
|
<span class="grip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg></span>
|
|
<span class="ord tnum">3</span>
|
|
<span class="thumb" style="background: linear-gradient(135deg, oklch(0.685 0.169 237.323), oklch(0.488 0.243 264.376))"></span>
|
|
<div>
|
|
<h3 class="r-title">Modern Web Development with React</h3>
|
|
<p class="r-meta"><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>12 lessons</span><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>4 exercises</span><span class="m">$79</span></p>
|
|
</div>
|
|
<div class="r-actions">
|
|
<button class="mini" title="Open course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></button>
|
|
<button class="mini danger" title="Remove from path"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" draggable="true">
|
|
<span class="grip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg></span>
|
|
<span class="ord tnum">4</span>
|
|
<span class="thumb" style="background: linear-gradient(135deg, oklch(0.585 0.233 277.117), oklch(0.606 0.25 292.717))"></span>
|
|
<div>
|
|
<h3 class="r-title">State Management & Data Fetching</h3>
|
|
<p class="r-meta"><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>10 lessons</span><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>3 exercises</span><span class="m">$59</span></p>
|
|
</div>
|
|
<div class="r-actions">
|
|
<button class="mini" title="Open course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></button>
|
|
<button class="mini danger" title="Remove from path"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="row" draggable="true">
|
|
<span class="grip"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="6" r="1"/><circle cx="15" cy="6" r="1"/><circle cx="9" cy="12" r="1"/><circle cx="15" cy="12" r="1"/><circle cx="9" cy="18" r="1"/><circle cx="15" cy="18" r="1"/></svg></span>
|
|
<span class="ord tnum">5</span>
|
|
<span class="thumb" style="background: linear-gradient(135deg, oklch(0.645 0.246 16.439), oklch(0.586 0.253 17.585))"></span>
|
|
<div>
|
|
<h3 class="r-title">Building & Shipping a Capstone</h3>
|
|
<p class="r-meta"><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/></svg>7 lessons</span><span class="m"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 11l3 3L22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/></svg>1 capstone</span><span class="m">$34</span></p>
|
|
</div>
|
|
<div class="r-actions">
|
|
<button class="mini" title="Open course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><path d="M15 3h6v6M10 14 21 3"/></svg></button>
|
|
<button class="mini danger" title="Remove from path"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2M19 6l-1 14a2 2 0 0 1-2 2H8a2 2 0 0 1-2-2L5 6"/></svg></button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<button class="add-course"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M12 5v14M5 12h14"/></svg>Add course to path</button>
|
|
|
|
<div class="cert-row">
|
|
<span></span>
|
|
<span class="ord"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="6"/><path d="M15.5 13.5 17 22l-5-3-5 3 1.5-8.5"/></svg></span>
|
|
<span></span>
|
|
<div><h3 class="r-title" style="color: var(--foreground)">Path certificate</h3><p class="r-meta">Awarded automatically when a student completes all 5 courses.</p></div>
|
|
<div class="r-actions"><a class="mini" href="teacher-path-certificate.html" title="Configure certificate"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="12" r="3"/><path d="M12 3v2M12 19v2M3 12h2M19 12h2"/></svg></a></div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
document.getElementById('theme').onclick = () => {
|
|
const dark = document.documentElement.classList.toggle('dark');
|
|
localStorage.cioTheme = dark ? 'dark' : 'light';
|
|
};
|
|
const listEl = document.getElementById('courses');
|
|
let dragEl = null;
|
|
listEl.querySelectorAll('.row').forEach((row) => {
|
|
row.addEventListener('dragstart', () => { dragEl = row; row.classList.add('dragging'); });
|
|
row.addEventListener('dragend', () => {
|
|
row.classList.remove('dragging');
|
|
listEl.querySelectorAll('.row').forEach((r) => r.classList.remove('dragover'));
|
|
listEl.querySelectorAll('.row .ord').forEach((el, i) => { el.textContent = i + 1; });
|
|
});
|
|
row.addEventListener('dragover', (e) => {
|
|
e.preventDefault();
|
|
if (row === dragEl) return;
|
|
const rect = row.getBoundingClientRect();
|
|
const after = e.clientY > rect.top + rect.height / 2;
|
|
listEl.querySelectorAll('.row').forEach((r) => r.classList.remove('dragover'));
|
|
row.classList.add('dragover');
|
|
listEl.insertBefore(dragEl, after ? row.nextSibling : row);
|
|
});
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|