giantpiston

Untitled

Aug 31st, 2025
250
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
HTML 16.25 KB | None | 0 0
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="utf-8" />
  5. <meta name="viewport" content="width=device-width, initial-scale=1" />
  6. <title>SAT Math — 75th Percentile Range (Mock)</title>
  7. <style>
  8.   :root{
  9.     --bg:#0b0d10; --panel:#12151a; --ink:#e8eef6; --muted:#a9b4c0;
  10.     --line:#232a33; --good:#1f9d6e; --near:#c59f14; --stretch:#6d8aff; --reach:#b84a4a;
  11.     --chip-bg:#0f1318;
  12.   }
  13.   *{box-sizing:border-box}
  14.   html,body{height:100%}
  15.   body{
  16.     margin:0; background:var(--bg); color:var(--ink);
  17.     font:14px/1.45 system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,"Noto Sans";
  18.   }
  19.   .wrap{max-width:1100px; margin:24px auto; padding:0 16px;}
  20.   header{display:flex; flex-wrap:wrap; gap:14px; align-items:center; justify-content:space-between; margin-bottom:12px}
  21.   header h1{font-size:20px; margin:0; letter-spacing:.2px}
  22.   .panel{
  23.     background:var(--panel); border:1px solid var(--line); border-radius:12px; padding:14px;
  24.     box-shadow:0 4px 16px rgba(0,0,0,.25);
  25.   }
  26.   .controls{display:grid; grid-template-columns: 1fr 1fr 1fr; gap:12px}
  27.   .control{
  28.     background:var(--chip-bg); border:1px solid var(--line); border-radius:10px; padding:12px;
  29.   }
  30.   .control label{display:block; font-size:12px; color:var(--muted); margin-bottom:6px}
  31.   .scoreRow{display:flex; align-items:center; gap:10px}
  32.   input[type="range"]{width:100%}
  33.   input[type="number"]{
  34.     width:92px; background:#0c1015; border:1px solid var(--line); border-radius:8px; color:var(--ink);
  35.     padding:6px 8px; font:inherit;
  36.   }
  37.   select{
  38.     width:100%; background:#0c1015; border:1px solid var(--line); border-radius:8px; color:var(--ink);
  39.     padding:8px; font:inherit;
  40.   }
  41.   .legend{display:flex; gap:8px; flex-wrap:wrap}
  42.   .chip{
  43.     display:inline-flex; align-items:center; gap:6px; padding:6px 10px; border-radius:999px; border:1px solid var(--line);
  44.     background:var(--chip-bg); color:var(--ink); font-weight:600; font-size:12px;
  45.   }
  46.   .dot{width:9px; height:9px; border-radius:50%}
  47.   .at   {border-color:#2aa879}
  48.   .close{border-color:#c9a424}
  49.   .stretch{border-color:#6d8aff}
  50.   .reach{border-color:#b84a4a}
  51.   .at .dot{background:var(--good)}
  52.   .close .dot{background:var(--near)}
  53.   .stretch .dot{background:var(--stretch)}
  54.   .reach .dot{background:var(--reach)}
  55.  
  56.   .est-wrap{margin:12px 0}
  57.   .est{display:flex; gap:10px; align-items:center; flex-wrap:wrap; padding:12px; border:1px dashed #34404e; border-radius:12px; background:#0e1319}
  58.   .est h2{margin:0 8px 0 0; font-size:14px}
  59.   .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}
  60.   .est .time{font-weight:800; letter-spacing:.2px}
  61.  
  62.   table{width:100%; border-collapse:separate; border-spacing:0; margin-top:12px}
  63.   thead th{
  64.     position:sticky; top:0; background:linear-gradient(#12151a,#12151a); z-index:1;
  65.     text-align:left; font-size:12px; color:var(--muted); letter-spacing:.2px;
  66.     border-bottom:1px solid var(--line); padding:10px 12px;
  67.   }
  68.   tbody td{padding:12px; border-bottom:1px solid var(--line)}
  69.   tbody tr:hover{background:rgba(255,255,255,.02)}
  70.   .rank{width:52px; text-align:right; color:var(--muted)}
  71.   .score, .gap{text-align:right; font-variant-numeric:tabular-nums}
  72.   .school{font-weight:600}
  73.   .statusCell{min-width:280px}
  74.   .statusPill{
  75.     display:inline-flex; gap:6px; align-items:center; padding:5px 9px; border-radius:999px;
  76.     border:1px solid var(--line); margin-right:6px; font-size:12px; font-weight:700; background:#0f1318;
  77.   }
  78.   .statusPill.at{border-color:var(--good); color:var(--good)}
  79.   .statusPill.close{border-color:var(--near); color:var(--near)}
  80.   .statusPill.stretch{border-color:var(--stretch); color:var(--stretch)}
  81.   .statusPill.reach{border-color:var(--reach); color:var(--reach)}
  82.  
  83.   .nowRow{outline:1px solid rgba(31,157,110,.4); background:linear-gradient(180deg, rgba(31,157,110,.08), transparent)}
  84.   .maxRow{outline:1px solid rgba(197,159,20,.35)}
  85.  
  86.   /* Student row — visually distinct */
  87.   .studentRow{
  88.     background:linear-gradient(90deg, rgba(109,138,255,.12), rgba(31,157,110,.08));
  89.     position:sticky; top:32px; z-index:2; /* pinned just under the header */
  90.   }
  91.   .studentBadge{
  92.     display:inline-flex; gap:6px; align-items:center; padding:4px 8px; border-radius:999px;
  93.     font-size:11px; font-weight:800; letter-spacing:.3px; text-transform:uppercase;
  94.     border:1px solid #3e4a5a; color:#9fb6ff; background:#121933;
  95.     margin-left:8px;
  96.   }
  97.   .studentRank{color:#9fb6ff; font-weight:800}
  98.   .muted{color:var(--muted)}
  99.   footer{margin-top:10px; color:var(--muted); font-size:12px}
  100.   .small{font-size:12px}
  101.  
  102.   @media (max-width:820px){
  103.     .controls{grid-template-columns:1fr}
  104.     .statusCell{min-width:auto}
  105.     .rank{display:none}
  106.   }
  107. </style>
  108. </head>
  109. <body>
  110.   <div class="wrap">
  111.     <header>
  112.       <h1>SAT Math — 75th Percentile Range</h1>
  113.       <div class="legend">
  114.         <span class="chip at"><span class="dot"></span> At/Above 75th</span>
  115.         <span class="chip close"><span class="dot"></span> Close (≤ 20)</span>
  116.         <span class="chip stretch"><span class="dot"></span> Stretch (≤ 40)</span>
  117.         <span class="chip reach"><span class="dot"></span> Reach (&gt; 40)</span>
  118.       </div>
  119.     </header>
  120.  
  121.     <!-- Estimator (hardcoded logic per request) -->
  122.     <section class="panel est-wrap" aria-live="polite">
  123.       <div class="est">
  124.         <h2>Estimate to hit target</h2>
  125.         <div class="pill">
  126.           Target school:
  127.           <select id="estSchool"></select>
  128.         </div>
  129.         <div class="pill">
  130.           or target score:
  131.           <input id="estScore" type="number" min="200" max="800" step="10" value="800" />
  132.         </div>
  133.         <div class="pill">Your last: <strong id="estLast">740</strong></div>
  134.         <div class="pill">Your max: <strong id="estMax">780</strong></div>
  135.         <div class="pill">Gap to target: <strong id="estGap">20</strong></div>
  136.         <div class="pill time">Time estimate to reach theoretical 800: <strong>≈ 2 hours</strong></div>
  137.       </div>
  138.       <p class="small muted" style="margin:8px 0 0">
  139.         For now, the time is hardcoded to <strong>≈ 2 hours</strong> for this student (780 → 800 theoretical). Replace with your real estimator later.
  140.       </p>
  141.     </section>
  142.  
  143.     <section class="panel">
  144.       <div class="controls">
  145.         <div class="control">
  146.           <label for="lastInput">Last Practice Test</label>
  147.           <div class="scoreRow">
  148.             <input id="lastRange" type="range" min="200" max="800" step="10" value="740" aria-label="Last practice slider" />
  149.             <input id="lastInput" type="number" min="200" max="800" step="10" value="740" />
  150.           </div>
  151.         </div>
  152.         <div class="control">
  153.           <label for="maxInput">Theoretical Max (Topic Mastery)</label>
  154.           <div class="scoreRow">
  155.             <input id="maxRange" type="range" min="200" max="800" step="10" value="780" aria-label="Theoretical max slider" />
  156.             <input id="maxInput" type="number" min="200" max="800" step="10" value="780" />
  157.           </div>
  158.         </div>
  159.         <div class="control">
  160.           <label for="filterSelect">Filter</label>
  161.           <select id="filterSelect">
  162.             <option value="all">Show all schools</option>
  163.             <option value="now">In range now (Last ≤ 20 below)</option>
  164.             <option value="max">Reachable with Max (Max ≤ 20 below)</option>
  165.             <option value="at">At/Above 75th (either metric)</option>
  166.           </select>
  167.         </div>
  168.       </div>
  169.  
  170.       <div class="summary" id="summary" aria-live="polite" style="margin:8px 0 10px"></div>
  171.  
  172.       <div class="tableWrap">
  173.         <table id="tbl">
  174.           <thead>
  175.             <tr>
  176.               <th class="rank">#</th>
  177.               <th>School</th>
  178.               <th class="score">SAT Math 75th</th>
  179.               <th class="statusCell">Your Status</th>
  180.               <th class="gap">Gap (Last)</th>
  181.               <th class="gap">Gap (Max)</th>
  182.             </tr>
  183.           </thead>
  184.           <tbody id="tbody"></tbody>
  185.         </table>
  186.       </div>
  187.  
  188.       <footer>
  189.         <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>
  190.       </footer>
  191.     </section>
  192.   </div>
  193.  
  194. <script>
  195. /** Mock school data — replace with your live feed */
  196. const SCHOOLS = [
  197.   { school: "MIT", math75: 800 },
  198.   { school: "Caltech", math75: 800 },
  199.   { school: "Stanford", math75: 800 },
  200.   { school: "Harvard", math75: 800 },
  201.   { school: "Princeton", math75: 800 },
  202.   { school: "Yale", math75: 800 },
  203.   { school: "Columbia", math75: 800 },
  204.   { school: "University of Chicago", math75: 800 },
  205.   { school: "UPenn", math75: 800 },
  206.   { school: "Duke", math75: 800 },
  207.   { school: "Carnegie Mellon", math75: 800 },
  208.   { school: "Johns Hopkins", math75: 800 },
  209.   { school: "Northwestern", math75: 800 },
  210.   { school: "Brown", math75: 800 },
  211.   { school: "Dartmouth", math75: 800 },
  212.   { school: "Cornell", math75: 800 },
  213.   { school: "Rice", math75: 800 },
  214.   { school: "UC Berkeley", math75: 790 },
  215.   { school: "NYU", math75: 790 },
  216.   { school: "UCLA", math75: 780 },
  217.   { school: "Georgia Tech", math75: 780 },
  218.   { school: "University of Michigan", math75: 780 },
  219.   { school: "UIUC", math75: 780 },
  220.   { school: "Boston University", math75: 770 },
  221.   { school: "Northeastern", math75: 770 },
  222.   { school: "UT Austin", math75: 770 },
  223.   { school: "Purdue", math75: 760 },
  224.   { school: "University of Florida", math75: 760 },
  225.   { school: "Virginia Tech", math75: 760 },
  226.   { school: "UW–Madison", math75: 760 }
  227. ].sort((a,b)=> b.math75 - a.math75);
  228.  
  229. const els = {
  230.   tbody: document.getElementById('tbody'),
  231.   lastRange: document.getElementById('lastRange'),
  232.   lastInput: document.getElementById('lastInput'),
  233.   maxRange: document.getElementById('maxRange'),
  234.   maxInput: document.getElementById('maxInput'),
  235.   filter: document.getElementById('filterSelect'),
  236.   summary: document.getElementById('summary'),
  237.   estSchool: document.getElementById('estSchool'),
  238.   estScore: document.getElementById('estScore'),
  239.   estLast: document.getElementById('estLast'),
  240.   estMax: document.getElementById('estMax'),
  241.   estGap: document.getElementById('estGap'),
  242. };
  243.  
  244. function clamp(v, lo, hi){ return Math.max(lo, Math.min(hi, v)); }
  245.  
  246. function syncPair(rangeEl, inputEl, onChange){
  247.   const sync = (from, to) => () => { to.value = from.value; onChange(); };
  248.   rangeEl.addEventListener('input', sync(rangeEl, inputEl));
  249.   inputEl.addEventListener('input', ()=> {
  250.     inputEl.value = clamp(parseInt(inputEl.value || 0, 10), parseInt(inputEl.min,10), parseInt(inputEl.max,10));
  251.     rangeEl.value = inputEl.value;
  252.     onChange();
  253.   });
  254. }
  255.  
  256. function statusFor(score, threshold){
  257.   const gap = Math.max(0, threshold - score); // 0 if at/above
  258.   if (gap === 0) return { tag: "At/Above", class: "at", gap };
  259.   if (gap <= 20) return { tag: "Close", class: "close", gap };
  260.  if (gap <= 40) return { tag: "Stretch", class: "stretch", gap };
  261.  return { tag: "Reach", class: "reach", gap };
  262. }
  263.  
  264. function fmtGap(gap){ return gap === 0 ? "—" : `${gap}`; }
  265.  
  266. function applyFilter(rows, filter, lastScore, maxScore){
  267.  return rows.filter(r=>{
  268.     const last = statusFor(lastScore, r.math75);
  269.     const max  = statusFor(maxScore,  r.math75);
  270.     if (filter === 'all') return true;
  271.     if (filter === 'now') return (last.gap <= 20); // in range now
  272.    if (filter === 'max') return (max.gap <= 20);  // reachable by max
  273.    if (filter === 'at')  return (last.gap === 0 || max.gap === 0);
  274.    return true;
  275.  });
  276. }
  277.  
  278. function renderEstimator(targetScore){
  279.  const last = parseInt(els.lastInput.value,10);
  280.  const max = parseInt(els.maxInput.value,10);
  281.  els.estLast.textContent = last;
  282.  els.estMax.textContent = max;
  283.  els.estGap.textContent = Math.max(0, targetScore - Math.max(last, max));
  284.  // Per request: keep time hardcoded to "~2 hours" for this student (780 -> 800 theoretical).
  285.   // If you later wire a real model, replace this with your function.
  286. }
  287.  
  288. function update(){
  289.   const last = parseInt(els.lastInput.value, 10);
  290.   const max  = parseInt(els.maxInput.value,  10);
  291.   const filter = els.filter.value;
  292.  
  293.   // Fill school selector once
  294.   if (els.estSchool.options.length === 0){
  295.     SCHOOLS.forEach(s=>{
  296.       const opt = document.createElement('option');
  297.       opt.value = s.math75; opt.textContent = `${s.school} (${s.math75})`;
  298.       els.estSchool.appendChild(opt);
  299.     });
  300.     els.estSchool.value = "800"; // default to a top-75th = 800
  301.   }
  302.  
  303.   // Summary counts
  304.   const nowCount = SCHOOLS.filter(s => statusFor(last, s.math75).gap <= 20).length;
  305.  const maxCount = SCHOOLS.filter(s => statusFor(max,  s.math75).gap <= 20).length;
  306.  const atEither = SCHOOLS.filter(s => statusFor(last, s.math75).gap === 0 || statusFor(max, s.math75).gap === 0).length;
  307.  
  308.   els.summary.innerHTML = `
  309.     <span class="chip at"><span class="dot"></span> At/Above (either): <strong>${atEither}</strong></span>
  310.     <span class="chip close"><span class="dot"></span> In Range Now (Last ≤ 20): <strong>${nowCount}</strong></span>
  311.     <span class="chip close"><span class="dot"></span> Reachable w/ Max (≤ 20): <strong>${maxCount}</strong></span>
  312.     <span class="chip"><span class="dot" style="background:#6b7280"></span> Total schools: <strong>${SCHOOLS.length}</strong></span>
  313.   `;
  314.  
  315.   // Render rows
  316.   const filtered = applyFilter(SCHOOLS, filter, last, max);
  317.   els.tbody.innerHTML = '';
  318.  
  319.   // --- Student row (distinct, pinned) ---
  320.   const studentTr = document.createElement('tr');
  321.   studentTr.classList.add('studentRow');
  322.   studentTr.innerHTML = `
  323.     <td class="rank studentRank"></td>
  324.     <td class="school">You <span class="studentBadge">Student</span></td>
  325.     <td class="score muted"></td>
  326.     <td class="statusCell">
  327.       <span class="statusPill ${statusFor(last, 800).class}" title="Last Practice">${statusFor(last, 800).tag} · Last vs 800</span>
  328.       <span class="statusPill ${statusFor(max, 800).class}" title="Theoretical Max">${statusFor(max, 800).tag} · Max vs 800</span>
  329.     </td>
  330.     <td class="gap muted"></td>
  331.     <td class="gap muted"></td>
  332.   `;
  333.   els.tbody.appendChild(studentTr);
  334.  
  335.   // --- School rows ---
  336.   filtered.forEach((row, idx)=>{
  337.     const lastStatus = statusFor(last, row.math75);
  338.     const maxStatus  = statusFor(max,  row.math75);
  339.  
  340.     const tr = document.createElement('tr');
  341.     const highlightNow = (lastStatus.gap <= 20);
  342.    const highlightMax = (!highlightNow && maxStatus.gap <= 20);
  343.    if (highlightNow) tr.classList.add('nowRow');
  344.    else if (highlightMax) tr.classList.add('maxRow');
  345.  
  346.    tr.innerHTML = `
  347.      <td class="rank">${idx+1}</td>
  348.       <td class="school">${row.school}</td>
  349.       <td class="score">${row.math75}</td>
  350.       <td class="statusCell">
  351.         <span class="statusPill ${lastStatus.class}" title="Last Practice">${lastStatus.tag} · Last</span>
  352.         <span class="statusPill ${maxStatus.class}" title="Theoretical Max">${maxStatus.tag} · Max</span>
  353.       </td>
  354.       <td class="gap">${fmtGap(lastStatus.gap)}</td>
  355.       <td class="gap">${fmtGap(maxStatus.gap)}</td>
  356.     `;
  357.     // Clicking a row selects it for the estimator
  358.     tr.addEventListener('click', ()=>{
  359.       els.estSchool.value = String(row.math75);
  360.       els.estScore.value = row.math75;
  361.       renderEstimator(row.math75);
  362.       tr.scrollIntoView({block:'center', behavior:'smooth'});
  363.     });
  364.     els.tbody.appendChild(tr);
  365.   });
  366.  
  367.   // Initialize estimator view
  368.   const initTarget = parseInt(els.estScore.value || els.estSchool.value, 10) || 800;
  369.   renderEstimator(initTarget);
  370. }
  371.  
  372. // wire up controls
  373. syncPair(els.lastRange, els.lastInput, update);
  374. syncPair(els.maxRange, els.maxInput, update);
  375. els.filter.addEventListener('change', update);
  376.  
  377. // estimator interactions
  378. els.estSchool.addEventListener('change', ()=>{
  379.   els.estScore.value = els.estSchool.value;
  380.   renderEstimator(parseInt(els.estSchool.value,10));
  381. });
  382. els.estScore.addEventListener('input', ()=>{
  383.   const v = clamp(parseInt(els.estScore.value||0,10), 200, 800);
  384.   els.estScore.value = isNaN(v) ? 800 : v;
  385.   renderEstimator(parseInt(els.estScore.value,10));
  386. });
  387.  
  388. // initialize from defaults (Last=740, Max=780; hardcoded time ≈2h to hit 800 theoretical)
  389. update();
  390. </script>
  391. </body>
  392. </html>
Advertisement
Add Comment
Please, Sign In to add comment