Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="utf-8" />
- <meta name="viewport" content="width=device-width, initial-scale=1" />
- <title>SAT Math — 75th Percentile Range (Mock)</title>
- <style>
- :root{
- --bg:#0b0d10; --panel:#12151a; --ink:#e8eef6; --muted:#a9b4c0;
- --line:#232a33; --good:#1f9d6e; --near:#c59f14; --stretch:#6d8aff; --reach:#b84a4a;
- --chip-bg:#0f1318;
- }
- *{box-sizing:border-box}
- html,body{height:100%}
- body{
- margin:0; background:var(--bg); color:var(--ink);
- font:14px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"Noto Sans";
- }
- .wrap{max-width:1100px; margin:24px auto; padding:0 16px;}
- header{display:flex; flex-wrap:wrap; gap:14px; align-items:center; justify-content:space-between; margin-bottom:12px}
- header h1{font-size:20px; margin:0; letter-spacing:.2px}
- .panel{
- background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:14px;
- box-shadow:0 4px 16px rgba(0,0,0,.25);
- }
- .controls{display:grid; grid-template-columns: 1fr 1fr 1fr; gap:12px}
- .control{
- background:var(--chip-bg); border:1px solid var(--line); border-radius:10px; padding:12px;
- }
- .control label{display:block; font-size:12px; color:var(--muted); margin-bottom:6px}
- .scoreRow{display:flex; align-items:center; gap:10px}
- input[type="range"]{width:100%}
- input[type="number"]{
- width:92px; background:#0c1015; border:1px solid var(--line); border-radius:8px; color:var(--ink);
- padding:6px 8px; font:inherit;
- }
- select{
- width:100%; background:#0c1015; border:1px solid var(--line); border-radius:8px; color:var(--ink);
- padding:8px; font:inherit;
- }
- .legend{display:flex; gap:8px; flex-wrap:wrap}
- .chip{
- display:inline-flex; align-items:center; gap:6px; padding:6px 10px; border-radius:999px; border:1px solid var(--line);
- background:var(--chip-bg); color:var(--ink); font-weight:600; font-size:12px;
- }
- .dot{width:9px; height:9px; border-radius:50%}
- .at {border-color:#2aa879}
- .close{border-color:#c9a424}
- .stretch{border-color:#6d8aff}
- .reach{border-color:#b84a4a}
- .at .dot{background:var(--good)}
- .close .dot{background:var(--near)}
- .stretch .dot{background:var(--stretch)}
- .reach .dot{background:var(--reach)}
- .est-wrap{margin:12px 0}
- .est{display:flex; gap:10px; align-items:center; flex-wrap:wrap; padding:12px; border:1px dashed #34404e; border-radius:12px; background:#0e1319}
- .est h2{margin:0 8px 0 0; font-size:14px}
- .est .pill{display:inline-flex; gap:6px; align-items:center; padding:6px 10px; border:1px solid var(--line); border-radius:999px; background:#0f1318; font-weight:700}
- .est .time{font-weight:800; letter-spacing:.2px}
- table{width:100%; border-collapse:separate; border-spacing:0; margin-top:12px}
- thead th{
- position:sticky; top:0; background:linear-gradient(#12151a,#12151a); z-index:1;
- text-align:left; font-size:12px; color:var(--muted); letter-spacing:.2px;
- border-bottom:1px solid var(--line); padding:10px 12px;
- }
- tbody td{padding:12px; border-bottom:1px solid var(--line)}
- tbody tr:hover{background:rgba(255,255,255,.02)}
- .rank{width:52px; text-align:right; color:var(--muted)}
- .score, .gap{text-align:right; font-variant-numeric:tabular-nums}
- .school{font-weight:600}
- .statusCell{min-width:280px}
- .statusPill{
- display:inline-flex; gap:6px; align-items:center; padding:5px 9px; border-radius:999px;
- border:1px solid var(--line); margin-right:6px; font-size:12px; font-weight:700; background:#0f1318;
- }
- .statusPill.at{border-color:var(--good); color:var(--good)}
- .statusPill.close{border-color:var(--near); color:var(--near)}
- .statusPill.stretch{border-color:var(--stretch); color:var(--stretch)}
- .statusPill.reach{border-color:var(--reach); color:var(--reach)}
- .nowRow{outline:1px solid rgba(31,157,110,.4); background:linear-gradient(180deg, rgba(31,157,110,.08), transparent)}
- .maxRow{outline:1px solid rgba(197,159,20,.35)}
- /* Student row — visually distinct */
- .studentRow{
- background:linear-gradient(90deg, rgba(109,138,255,.12), rgba(31,157,110,.08));
- position:sticky; top:32px; z-index:2; /* pinned just under the header */
- }
- .studentBadge{
- display:inline-flex; gap:6px; align-items:center; padding:4px 8px; border-radius:999px;
- font-size:11px; font-weight:800; letter-spacing:.3px; text-transform:uppercase;
- border:1px solid #3e4a5a; color:#9fb6ff; background:#121933;
- margin-left:8px;
- }
- .studentRank{color:#9fb6ff; font-weight:800}
- .muted{color:var(--muted)}
- footer{margin-top:10px; color:var(--muted); font-size:12px}
- .small{font-size:12px}
- @media (max-width:820px){
- .controls{grid-template-columns:1fr}
- .statusCell{min-width:auto}
- .rank{display:none}
- }
- </style>
- </head>
- <body>
- <div class="wrap">
- <header>
- <h1>SAT Math — 75th Percentile Range</h1>
- <div class="legend">
- <span class="chip at"><span class="dot"></span> At/Above 75th</span>
- <span class="chip close"><span class="dot"></span> Close (≤ 20)</span>
- <span class="chip stretch"><span class="dot"></span> Stretch (≤ 40)</span>
- <span class="chip reach"><span class="dot"></span> Reach (> 40)</span>
- </div>
- </header>
- <!-- Estimator (hardcoded logic per request) -->
- <section class="panel est-wrap" aria-live="polite">
- <div class="est">
- <h2>Estimate to hit target</h2>
- <div class="pill">
- Target school:
- <select id="estSchool"></select>
- </div>
- <div class="pill">
- or target score:
- <input id="estScore" type="number" min="200" max="800" step="10" value="800" />
- </div>
- <div class="pill">Your last: <strong id="estLast">740</strong></div>
- <div class="pill">Your max: <strong id="estMax">780</strong></div>
- <div class="pill">Gap to target: <strong id="estGap">20</strong></div>
- <div class="pill time">Time estimate to reach theoretical 800: <strong>≈ 2 hours</strong></div>
- </div>
- <p class="small muted" style="margin:8px 0 0">
- For now, the time is hardcoded to <strong>≈ 2 hours</strong> for this student (780 → 800 theoretical). Replace with your real estimator later.
- </p>
- </section>
- <section class="panel">
- <div class="controls">
- <div class="control">
- <label for="lastInput">Last Practice Test</label>
- <div class="scoreRow">
- <input id="lastRange" type="range" min="200" max="800" step="10" value="740" aria-label="Last practice slider" />
- <input id="lastInput" type="number" min="200" max="800" step="10" value="740" />
- </div>
- </div>
- <div class="control">
- <label for="maxInput">Theoretical Max (Topic Mastery)</label>
- <div class="scoreRow">
- <input id="maxRange" type="range" min="200" max="800" step="10" value="780" aria-label="Theoretical max slider" />
- <input id="maxInput" type="number" min="200" max="800" step="10" value="780" />
- </div>
- </div>
- <div class="control">
- <label for="filterSelect">Filter</label>
- <select id="filterSelect">
- <option value="all">Show all schools</option>
- <option value="now">In range now (Last ≤ 20 below)</option>
- <option value="max">Reachable with Max (Max ≤ 20 below)</option>
- <option value="at">At/Above 75th (either metric)</option>
- </select>
- </div>
- </div>
- <div class="summary" id="summary" aria-live="polite" style="margin:8px 0 10px"></div>
- <div class="tableWrap">
- <table id="tbl">
- <thead>
- <tr>
- <th class="rank">#</th>
- <th>School</th>
- <th class="score">SAT Math 75th</th>
- <th class="statusCell">Your Status</th>
- <th class="gap">Gap (Last)</th>
- <th class="gap">Gap (Max)</th>
- </tr>
- </thead>
- <tbody id="tbody"></tbody>
- </table>
- </div>
- <footer>
- <p class="small">Values are <strong>mock data</strong> for design/dev only. Replace with live SAT Math 75th data and your real time-estimator.</p>
- </footer>
- </section>
- </div>
- <script>
- /** Mock school data — replace with your live feed */
- const SCHOOLS = [
- { school: "MIT", math75: 800 },
- { school: "Caltech", math75: 800 },
- { school: "Stanford", math75: 800 },
- { school: "Harvard", math75: 800 },
- { school: "Princeton", math75: 800 },
- { school: "Yale", math75: 800 },
- { school: "Columbia", math75: 800 },
- { school: "University of Chicago", math75: 800 },
- { school: "UPenn", math75: 800 },
- { school: "Duke", math75: 800 },
- { school: "Carnegie Mellon", math75: 800 },
- { school: "Johns Hopkins", math75: 800 },
- { school: "Northwestern", math75: 800 },
- { school: "Brown", math75: 800 },
- { school: "Dartmouth", math75: 800 },
- { school: "Cornell", math75: 800 },
- { school: "Rice", math75: 800 },
- { school: "UC Berkeley", math75: 790 },
- { school: "NYU", math75: 790 },
- { school: "UCLA", math75: 780 },
- { school: "Georgia Tech", math75: 780 },
- { school: "University of Michigan", math75: 780 },
- { school: "UIUC", math75: 780 },
- { school: "Boston University", math75: 770 },
- { school: "Northeastern", math75: 770 },
- { school: "UT Austin", math75: 770 },
- { school: "Purdue", math75: 760 },
- { school: "University of Florida", math75: 760 },
- { school: "Virginia Tech", math75: 760 },
- { school: "UW–Madison", math75: 760 }
- ].sort((a,b)=> b.math75 - a.math75);
- const els = {
- tbody: document.getElementById('tbody'),
- lastRange: document.getElementById('lastRange'),
- lastInput: document.getElementById('lastInput'),
- maxRange: document.getElementById('maxRange'),
- maxInput: document.getElementById('maxInput'),
- filter: document.getElementById('filterSelect'),
- summary: document.getElementById('summary'),
- estSchool: document.getElementById('estSchool'),
- estScore: document.getElementById('estScore'),
- estLast: document.getElementById('estLast'),
- estMax: document.getElementById('estMax'),
- estGap: document.getElementById('estGap'),
- };
- function clamp(v, lo, hi){ return Math.max(lo, Math.min(hi, v)); }
- function syncPair(rangeEl, inputEl, onChange){
- const sync = (from, to) => () => { to.value = from.value; onChange(); };
- rangeEl.addEventListener('input', sync(rangeEl, inputEl));
- inputEl.addEventListener('input', ()=> {
- inputEl.value = clamp(parseInt(inputEl.value || 0, 10), parseInt(inputEl.min,10), parseInt(inputEl.max,10));
- rangeEl.value = inputEl.value;
- onChange();
- });
- }
- function statusFor(score, threshold){
- const gap = Math.max(0, threshold - score); // 0 if at/above
- if (gap === 0) return { tag: "At/Above", class: "at", gap };
- if (gap <= 20) return { tag: "Close", class: "close", gap };
- if (gap <= 40) return { tag: "Stretch", class: "stretch", gap };
- return { tag: "Reach", class: "reach", gap };
- }
- function fmtGap(gap){ return gap === 0 ? "—" : `${gap}`; }
- function applyFilter(rows, filter, lastScore, maxScore){
- return rows.filter(r=>{
- const last = statusFor(lastScore, r.math75);
- const max = statusFor(maxScore, r.math75);
- if (filter === 'all') return true;
- if (filter === 'now') return (last.gap <= 20); // in range now
- if (filter === 'max') return (max.gap <= 20); // reachable by max
- if (filter === 'at') return (last.gap === 0 || max.gap === 0);
- return true;
- });
- }
- function renderEstimator(targetScore){
- const last = parseInt(els.lastInput.value,10);
- const max = parseInt(els.maxInput.value,10);
- els.estLast.textContent = last;
- els.estMax.textContent = max;
- els.estGap.textContent = Math.max(0, targetScore - Math.max(last, max));
- // Per request: keep time hardcoded to "~2 hours" for this student (780 -> 800 theoretical).
- // If you later wire a real model, replace this with your function.
- }
- function update(){
- const last = parseInt(els.lastInput.value, 10);
- const max = parseInt(els.maxInput.value, 10);
- const filter = els.filter.value;
- // Fill school selector once
- if (els.estSchool.options.length === 0){
- SCHOOLS.forEach(s=>{
- const opt = document.createElement('option');
- opt.value = s.math75; opt.textContent = `${s.school} (${s.math75})`;
- els.estSchool.appendChild(opt);
- });
- els.estSchool.value = "800"; // default to a top-75th = 800
- }
- // Summary counts
- const nowCount = SCHOOLS.filter(s => statusFor(last, s.math75).gap <= 20).length;
- const maxCount = SCHOOLS.filter(s => statusFor(max, s.math75).gap <= 20).length;
- const atEither = SCHOOLS.filter(s => statusFor(last, s.math75).gap === 0 || statusFor(max, s.math75).gap === 0).length;
- els.summary.innerHTML = `
- <span class="chip at"><span class="dot"></span> At/Above (either): <strong>${atEither}</strong></span>
- <span class="chip close"><span class="dot"></span> In Range Now (Last ≤ 20): <strong>${nowCount}</strong></span>
- <span class="chip close"><span class="dot"></span> Reachable w/ Max (≤ 20): <strong>${maxCount}</strong></span>
- <span class="chip"><span class="dot" style="background:#6b7280"></span> Total schools: <strong>${SCHOOLS.length}</strong></span>
- `;
- // Render rows
- const filtered = applyFilter(SCHOOLS, filter, last, max);
- els.tbody.innerHTML = '';
- // --- Student row (distinct, pinned) ---
- const studentTr = document.createElement('tr');
- studentTr.classList.add('studentRow');
- studentTr.innerHTML = `
- <td class="rank studentRank">★</td>
- <td class="school">You <span class="studentBadge">Student</span></td>
- <td class="score muted">—</td>
- <td class="statusCell">
- <span class="statusPill ${statusFor(last, 800).class}" title="Last Practice">${statusFor(last, 800).tag} · Last vs 800</span>
- <span class="statusPill ${statusFor(max, 800).class}" title="Theoretical Max">${statusFor(max, 800).tag} · Max vs 800</span>
- </td>
- <td class="gap muted">—</td>
- <td class="gap muted">—</td>
- `;
- els.tbody.appendChild(studentTr);
- // --- School rows ---
- filtered.forEach((row, idx)=>{
- const lastStatus = statusFor(last, row.math75);
- const maxStatus = statusFor(max, row.math75);
- const tr = document.createElement('tr');
- const highlightNow = (lastStatus.gap <= 20);
- const highlightMax = (!highlightNow && maxStatus.gap <= 20);
- if (highlightNow) tr.classList.add('nowRow');
- else if (highlightMax) tr.classList.add('maxRow');
- tr.innerHTML = `
- <td class="rank">${idx+1}</td>
- <td class="school">${row.school}</td>
- <td class="score">${row.math75}</td>
- <td class="statusCell">
- <span class="statusPill ${lastStatus.class}" title="Last Practice">${lastStatus.tag} · Last</span>
- <span class="statusPill ${maxStatus.class}" title="Theoretical Max">${maxStatus.tag} · Max</span>
- </td>
- <td class="gap">${fmtGap(lastStatus.gap)}</td>
- <td class="gap">${fmtGap(maxStatus.gap)}</td>
- `;
- // Clicking a row selects it for the estimator
- tr.addEventListener('click', ()=>{
- els.estSchool.value = String(row.math75);
- els.estScore.value = row.math75;
- renderEstimator(row.math75);
- tr.scrollIntoView({block:'center', behavior:'smooth'});
- });
- els.tbody.appendChild(tr);
- });
- // Initialize estimator view
- const initTarget = parseInt(els.estScore.value || els.estSchool.value, 10) || 800;
- renderEstimator(initTarget);
- }
- // wire up controls
- syncPair(els.lastRange, els.lastInput, update);
- syncPair(els.maxRange, els.maxInput, update);
- els.filter.addEventListener('change', update);
- // estimator interactions
- els.estSchool.addEventListener('change', ()=>{
- els.estScore.value = els.estSchool.value;
- renderEstimator(parseInt(els.estSchool.value,10));
- });
- els.estScore.addEventListener('input', ()=>{
- const v = clamp(parseInt(els.estScore.value||0,10), 200, 800);
- els.estScore.value = isNaN(v) ? 800 : v;
- renderEstimator(parseInt(els.estScore.value,10));
- });
- // initialize from defaults (Last=740, Max=780; hardcoded time ≈2h to hit 800 theoretical)
- update();
- </script>
- </body>
- </html>
Advertisement
Add Comment
Please, Sign In to add comment