db updates
This commit is contained in:
@@ -1 +1,4 @@
|
|||||||
RANKED EQAO
|
Prisma DB for debugging: npx prisma studio
|
||||||
|
Express server: node src/index.js
|
||||||
|
Run: cd frontend && npm run dev
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,490 @@
|
|||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
|
const DIAMONDS = [
|
||||||
|
{ left: '5%', top: '12%', size: 10, dur: '9s', delay: '0s' },
|
||||||
|
{ left: '18%', top: '58%', size: 8, dur: '13s', delay: '2s' },
|
||||||
|
{ left: '30%', top: '22%', size: 14, dur: '11s', delay: '0.5s' },
|
||||||
|
{ left: '48%', top: '72%', size: 7, dur: '10s', delay: '1.5s' },
|
||||||
|
{ left: '58%', top: '20%', size: 16, dur: '12s', delay: '0.8s' },
|
||||||
|
{ left: '70%', top: '65%', size: 10, dur: '8s', delay: '3s' },
|
||||||
|
{ left: '82%', top: '30%', size: 13, dur: '14s', delay: '1s' },
|
||||||
|
{ left: '90%', top: '75%', size: 9, dur: '9s', delay: '4s' },
|
||||||
|
{ left: '38%', top: '45%', size: 6, dur: '15s', delay: '2.5s' },
|
||||||
|
{ left: '62%', top: '88%', size: 11, dur: '11s', delay: '0.3s' },
|
||||||
|
{ left: '12%', top: '82%', size: 8, dur: '13s', delay: '1.8s' },
|
||||||
|
{ left: '76%', top: '10%', size: 12, dur: '10s', delay: '3.5s' },
|
||||||
|
];
|
||||||
|
const RINGS = [
|
||||||
|
{ left: '10%', top: '35%', size: 60, dur: '7s', delay: '0s' },
|
||||||
|
{ left: '78%', top: '18%', size: 44, dur: '9s', delay: '2.5s' },
|
||||||
|
{ left: '45%', top: '62%', size: 80, dur: '11s', delay: '1s' },
|
||||||
|
{ left: '88%', top: '55%', size: 36, dur: '8s', delay: '3.5s' },
|
||||||
|
];
|
||||||
|
const SCANLINES = [
|
||||||
|
{ top: '28%', width: '160px', left: '5%', dur: '8s', delay: '0s' },
|
||||||
|
{ top: '52%', width: '120px', left: '74%', dur: '10s', delay: '3s' },
|
||||||
|
{ top: '18%', width: '200px', left: '38%', dur: '12s', delay: '1.5s' },
|
||||||
|
{ top: '75%', width: '140px', left: '20%', dur: '9s', delay: '2s' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const PAGE_CSS = `
|
||||||
|
@keyframes aboutFadeUp {
|
||||||
|
from { opacity:0; transform:translateY(16px); }
|
||||||
|
to { opacity:1; transform:translateY(0); }
|
||||||
|
}
|
||||||
|
@keyframes aboutHeroPulse {
|
||||||
|
0%,100% { opacity:.2; transform:translate(-50%,-50%) scale(1); }
|
||||||
|
50% { opacity:.38; transform:translate(-50%,-50%) scale(1.12); }
|
||||||
|
}
|
||||||
|
.ab-orb {
|
||||||
|
position:absolute; top:50%; left:50%;
|
||||||
|
width:1100px; height:700px;
|
||||||
|
background:radial-gradient(ellipse at center,
|
||||||
|
rgba(245,197,24,.07) 0%, rgba(37,99,235,.05) 45%, transparent 70%);
|
||||||
|
border-radius:50%; pointer-events:none;
|
||||||
|
animation:aboutHeroPulse 10s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.ab-nav {
|
||||||
|
position:fixed; top:0; left:0; right:0; z-index:100;
|
||||||
|
height:64px; display:flex; align-items:center;
|
||||||
|
justify-content:space-between; padding:0 40px;
|
||||||
|
background:rgba(7,8,15,.85);
|
||||||
|
backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
|
||||||
|
border-bottom:1px solid rgba(255,255,255,.07);
|
||||||
|
}
|
||||||
|
.ab-nav__logo {
|
||||||
|
font-family:'Russo One',sans-serif; font-size:19px;
|
||||||
|
letter-spacing:.04em; text-transform:uppercase;
|
||||||
|
color:#eceef8; text-decoration:none;
|
||||||
|
}
|
||||||
|
.ab-nav__logo span { color:#f5c518; }
|
||||||
|
.ab-nav__links { display:flex; align-items:center; gap:28px; }
|
||||||
|
.ab-nav-link {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:13px;
|
||||||
|
font-weight:600; letter-spacing:.1em; text-transform:uppercase;
|
||||||
|
color:#7a85a8; text-decoration:none; cursor:pointer;
|
||||||
|
background:none; border:none; padding:0; transition:color .2s;
|
||||||
|
}
|
||||||
|
.ab-nav-link:hover { color:#eceef8; }
|
||||||
|
.ab-nav-signin {
|
||||||
|
padding:8px 18px;
|
||||||
|
background:rgba(245,197,24,.08); border:1px solid rgba(245,197,24,.35);
|
||||||
|
border-radius:6px; font-family:'Barlow Condensed',sans-serif;
|
||||||
|
font-size:13px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
|
||||||
|
color:#f5c518; cursor:pointer; transition:background .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
.ab-nav-signin:hover { background:rgba(245,197,24,.16); border-color:rgba(245,197,24,.6); }
|
||||||
|
.ab-wrap { width:100%; max-width:1100px; margin:0 auto; padding:0 24px; }
|
||||||
|
.ab-eyebrow {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:11px;
|
||||||
|
font-weight:700; letter-spacing:.3em; text-transform:uppercase;
|
||||||
|
color:#f5c518; display:flex; align-items:center; justify-content:center;
|
||||||
|
gap:12px; margin-bottom:14px;
|
||||||
|
}
|
||||||
|
.ab-eyebrow::before,.ab-eyebrow::after {
|
||||||
|
content:''; display:block; width:36px; height:1px;
|
||||||
|
background:rgba(245,197,24,.4);
|
||||||
|
}
|
||||||
|
.ab-section-title {
|
||||||
|
font-family:'Russo One',sans-serif;
|
||||||
|
font-size:clamp(30px,5vw,48px); text-transform:uppercase;
|
||||||
|
letter-spacing:.02em; color:#eceef8;
|
||||||
|
text-align:center; margin-bottom:56px;
|
||||||
|
}
|
||||||
|
.ab-feature-card {
|
||||||
|
background:#111426; border:1px solid rgba(255,255,255,.07);
|
||||||
|
border-radius:12px; padding:28px 24px;
|
||||||
|
transition:border-color .25s, transform .25s;
|
||||||
|
}
|
||||||
|
.ab-feature-card:hover { border-color:rgba(245,197,24,.2); transform:translateY(-3px); }
|
||||||
|
.ab-prose-card {
|
||||||
|
background:#111426; border:1px solid rgba(255,255,255,.07);
|
||||||
|
border-radius:16px; padding:44px 52px;
|
||||||
|
position:relative; overflow:hidden; max-width:800px; margin:0 auto;
|
||||||
|
}
|
||||||
|
.ab-prose-card::before {
|
||||||
|
content:''; position:absolute; top:0; left:0; right:0; height:2px;
|
||||||
|
background:linear-gradient(to right,transparent,rgba(245,197,24,.5),transparent);
|
||||||
|
}
|
||||||
|
.ab-grade-card {
|
||||||
|
background:#111426; border:1px solid rgba(255,255,255,.07);
|
||||||
|
border-radius:16px; padding:32px 28px; flex:1; min-width:270px;
|
||||||
|
position:relative; overflow:hidden;
|
||||||
|
transition:border-color .25s, transform .25s;
|
||||||
|
}
|
||||||
|
.ab-grade-card:hover { transform:translateY(-4px); }
|
||||||
|
.ab-footer-link {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:12px;
|
||||||
|
letter-spacing:.12em; text-transform:uppercase;
|
||||||
|
color:#7a85a8; text-decoration:none; transition:color .2s;
|
||||||
|
}
|
||||||
|
.ab-footer-link:hover { color:#eceef8; }
|
||||||
|
@media (max-width:768px) {
|
||||||
|
.ab-nav { padding:0 20px; }
|
||||||
|
.ab-nav-link.ab-hide-mobile { display:none; }
|
||||||
|
.ab-prose-card { padding:28px 24px; }
|
||||||
|
}
|
||||||
|
@media (max-width:500px) {
|
||||||
|
.ab-nav__links { gap:12px; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const TECH_STACK = [
|
||||||
|
{
|
||||||
|
title: 'Node.js + Express',
|
||||||
|
desc: 'The backend is straightforward Express — routes for auth, matchmaking, leaderboards, and WebSocket connections for live 1v1 games. No framework magic, just Node doing what Node does.',
|
||||||
|
icon: (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f5c518" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<circle cx="12" cy="12" r="10" />
|
||||||
|
<path d="M12 8v4l3 3" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'PostgreSQL + Prisma',
|
||||||
|
desc: 'All user data, match results, and ELO history live in Postgres. Prisma handles the schema and migrations so changes to the data model don\'t turn into SQL archaeology.',
|
||||||
|
icon: (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f5c518" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<ellipse cx="12" cy="5" rx="9" ry="3" />
|
||||||
|
<path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3" />
|
||||||
|
<path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'React + Vite',
|
||||||
|
desc: 'The frontend is plain React with Vite — no Next.js, no SSR, no overhead. Fast dev builds, component-based UI, and straightforward state management with hooks.',
|
||||||
|
icon: (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f5c518" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Google OAuth',
|
||||||
|
desc: 'Sign-in goes through Google OAuth via Passport.js. JWTs handle sessions on the client. No passwords to store, no email verification flows — sign in once and you\'re in.',
|
||||||
|
icon: (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f5c518" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="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>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Gemini API',
|
||||||
|
desc: 'English sections use Google Gemini to generate reading passages, comprehension questions, and check free-response answers. Every session gets fresh content — no memorizing answers.',
|
||||||
|
icon: (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f5c518" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ELO Ranking',
|
||||||
|
desc: 'The same rating system used in chess and competitive gaming — adapted for 1v1 quiz matches. Win against a higher-rated player, gain more points. Lose to a lower-rated one, feel it.',
|
||||||
|
icon: (
|
||||||
|
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="#f5c518" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round">
|
||||||
|
<polyline points="23 6 13.5 15.5 8.5 10.5 1 18" />
|
||||||
|
<polyline points="17 6 23 6 23 12" />
|
||||||
|
</svg>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const GRADES = [
|
||||||
|
{
|
||||||
|
id: 'G3',
|
||||||
|
label: 'Grade 3',
|
||||||
|
color: '#c9824a',
|
||||||
|
desc: 'The first EQAO checkpoint in elementary school. Grade 3 math covers number sense, basic operations, patterning, simple measurement, and geometry. Questions test foundational understanding, not tricks.',
|
||||||
|
topics: ['Number Sense', 'Patterning', 'Measurement', 'Geometry'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'G6',
|
||||||
|
label: 'Grade 6',
|
||||||
|
color: '#94a3b8',
|
||||||
|
desc: "The follow-up at the end of elementary. Problems get more complex — multi-step reasoning, fractions, data management, and proportional thinking. A noticeable step up from Grade 3.",
|
||||||
|
topics: ['Fractions', 'Data Management', 'Algebra', 'Proportional Reasoning'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'G9',
|
||||||
|
label: 'Grade 9',
|
||||||
|
color: '#f5c518',
|
||||||
|
desc: 'The high school transition point. Grade 9 math introduces linear relations, polynomial expressions, and analytic geometry. This is where abstract thinking starts to matter.',
|
||||||
|
topics: ['Linear Relations', 'Algebra', 'Analytic Geometry', 'Financial Literacy'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function About() {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{PAGE_CSS}</style>
|
||||||
|
|
||||||
|
{/* Background */}
|
||||||
|
<div className="arena-bg">
|
||||||
|
<div className="arena-grain" />
|
||||||
|
<div className="arena-shapes">
|
||||||
|
{DIAMONDS.map((d, i) => (
|
||||||
|
<div key={i} className="shape shape--diamond" style={{ left: d.left, top: d.top, width: d.size, height: d.size, animationDuration: d.dur, animationDelay: d.delay }} />
|
||||||
|
))}
|
||||||
|
{RINGS.map((r, i) => (
|
||||||
|
<div key={i} className="shape shape--ring" style={{ left: r.left, top: r.top, width: r.size, height: r.size, animationDuration: r.dur, animationDelay: r.delay }} />
|
||||||
|
))}
|
||||||
|
{SCANLINES.map((s, i) => (
|
||||||
|
<div key={i} className="shape shape--line" style={{ top: s.top, left: s.left, width: s.width, animationDuration: s.dur, animationDelay: s.delay }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nav */}
|
||||||
|
<nav className="ab-nav">
|
||||||
|
<a href="/" className="ab-nav__logo">RANKED<span>EQAO</span></a>
|
||||||
|
<div className="ab-nav__links">
|
||||||
|
<a href="/#how-it-works" className="ab-nav-link ab-hide-mobile">How It Works</a>
|
||||||
|
<a href="/#leaderboard" className="ab-nav-link ab-hide-mobile">Leaderboard</a>
|
||||||
|
<a href="/faq" className="ab-nav-link ab-hide-mobile">FAQ</a>
|
||||||
|
<a href="/#signin" className="ab-nav-signin">Sign In</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||||
|
|
||||||
|
{/* HERO */}
|
||||||
|
<section style={{
|
||||||
|
minHeight: '68vh', display: 'flex', flexDirection: 'column',
|
||||||
|
alignItems: 'center', justifyContent: 'center',
|
||||||
|
padding: '120px 0 80px', textAlign: 'center',
|
||||||
|
position: 'relative', overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
<div className="ab-orb" />
|
||||||
|
<div className="ab-wrap" style={{ position: 'relative', zIndex: 1 }}>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 11, fontWeight: 700,
|
||||||
|
letterSpacing: '0.3em', textTransform: 'uppercase', color: '#f5c518',
|
||||||
|
marginBottom: 28, opacity: 0, animation: 'aboutFadeUp .6s .05s ease both',
|
||||||
|
}}>
|
||||||
|
Independent Ontario Project
|
||||||
|
</p>
|
||||||
|
<h1 style={{
|
||||||
|
fontFamily: "'Russo One',sans-serif",
|
||||||
|
fontSize: 'clamp(58px,13vw,130px)',
|
||||||
|
lineHeight: 0.88, letterSpacing: '-0.01em',
|
||||||
|
textTransform: 'uppercase', marginBottom: 36,
|
||||||
|
}}>
|
||||||
|
<span style={{ display: 'block', color: '#eceef8', opacity: 0, animation: 'aboutFadeUp .7s .15s cubic-bezier(.16,1,.3,1) both' }}>
|
||||||
|
ABOUT
|
||||||
|
</span>
|
||||||
|
<span style={{ display: 'block', color: '#f5c518', textShadow: '0 0 60px rgba(245,197,24,.4)', opacity: 0, animation: 'aboutFadeUp .7s .3s cubic-bezier(.16,1,.3,1) both' }}>
|
||||||
|
RANKED EQAO
|
||||||
|
</span>
|
||||||
|
</h1>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 'clamp(15px,2.4vw,19px)',
|
||||||
|
color: '#9ba5c4', maxWidth: 540, margin: '0 auto', lineHeight: 1.7,
|
||||||
|
opacity: 0, animation: 'aboutFadeUp .6s .5s ease both',
|
||||||
|
}}>
|
||||||
|
A free, competitive quiz platform for Ontario students. Practice EQAO-style math, race against real opponents, and climb a leaderboard — because test prep shouldn't feel like a chore.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* MISSION */}
|
||||||
|
<section style={{
|
||||||
|
padding: '100px 0',
|
||||||
|
background: 'rgba(13,15,28,.78)',
|
||||||
|
backdropFilter: 'blur(2px)', WebkitBackdropFilter: 'blur(2px)',
|
||||||
|
}}>
|
||||||
|
<div className="ab-wrap">
|
||||||
|
<p className="ab-eyebrow">Why It Exists</p>
|
||||||
|
<h2 className="ab-section-title">The Mission</h2>
|
||||||
|
<div className="ab-prose-card">
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 17, color: '#9ba5c4',
|
||||||
|
lineHeight: 1.8, marginBottom: 22,
|
||||||
|
}}>
|
||||||
|
EQAO prep is, by design, pretty boring. Worksheets, practice tests, the same problems repeated until they stop feeling like problems and start feeling like punishment. Nobody's excited about it — including the students who actually need the practice.
|
||||||
|
</p>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 17, color: '#9ba5c4',
|
||||||
|
lineHeight: 1.8, marginBottom: 22,
|
||||||
|
}}>
|
||||||
|
Ranked EQAO is an attempt to fix that. By wrapping real EQAO curriculum in a competitive structure — live 1v1 matchmaking, ELO ratings, grade leaderboards — it turns the same content into something students might actually choose to do on their own. The questions are authentic. The format makes them matter.
|
||||||
|
</p>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 17, color: '#9ba5c4',
|
||||||
|
lineHeight: 1.8,
|
||||||
|
}}>
|
||||||
|
It's not affiliated with EQAO or the Ontario government. It's just a side project built by someone who wanted to see if competition could make standardized test prep less miserable.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* HOW IT WAS BUILT */}
|
||||||
|
<section style={{ padding: '100px 0' }}>
|
||||||
|
<div className="ab-wrap">
|
||||||
|
<p className="ab-eyebrow">Under The Hood</p>
|
||||||
|
<h2 className="ab-section-title">How It Was Built</h2>
|
||||||
|
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit,minmax(290px,1fr))', gap: 16 }}>
|
||||||
|
{TECH_STACK.map((tech, i) => (
|
||||||
|
<div key={i} className="ab-feature-card">
|
||||||
|
<div style={{
|
||||||
|
width: 44, height: 44,
|
||||||
|
background: 'rgba(245,197,24,.07)', border: '1px solid rgba(245,197,24,.18)',
|
||||||
|
borderRadius: 10, display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||||
|
marginBottom: 16,
|
||||||
|
}}>
|
||||||
|
{tech.icon}
|
||||||
|
</div>
|
||||||
|
<h3 style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 18, fontWeight: 700,
|
||||||
|
letterSpacing: '0.05em', textTransform: 'uppercase', color: '#eceef8', marginBottom: 8,
|
||||||
|
}}>
|
||||||
|
{tech.title}
|
||||||
|
</h3>
|
||||||
|
<p style={{ fontFamily: "'Barlow',sans-serif", fontSize: 14, color: '#7a85a8', lineHeight: 1.65 }}>
|
||||||
|
{tech.desc}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* THE GRADES */}
|
||||||
|
<section style={{
|
||||||
|
padding: '100px 0',
|
||||||
|
background: 'rgba(13,15,28,.78)',
|
||||||
|
backdropFilter: 'blur(2px)', WebkitBackdropFilter: 'blur(2px)',
|
||||||
|
}}>
|
||||||
|
<div className="ab-wrap">
|
||||||
|
<p className="ab-eyebrow">Coverage</p>
|
||||||
|
<h2 className="ab-section-title">Grade Levels</h2>
|
||||||
|
<div style={{ display: 'flex', gap: 20, flexWrap: 'wrap' }}>
|
||||||
|
{GRADES.map((g) => (
|
||||||
|
<div key={g.id} className="ab-grade-card" style={{ borderColor: `rgba(${hexToRgb(g.color)},.18)` }}>
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute', top: 0, left: 0, right: 0, height: 2,
|
||||||
|
background: `linear-gradient(to right, transparent, ${g.color}, transparent)`,
|
||||||
|
}} />
|
||||||
|
<div style={{
|
||||||
|
fontFamily: "'Russo One',sans-serif", fontSize: 54, lineHeight: 1,
|
||||||
|
color: g.color, marginBottom: 4,
|
||||||
|
textShadow: `0 0 28px ${g.color}55`,
|
||||||
|
}}>
|
||||||
|
{g.id}
|
||||||
|
</div>
|
||||||
|
<div style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 13, fontWeight: 700,
|
||||||
|
letterSpacing: '0.16em', textTransform: 'uppercase', color: '#7a85a8', marginBottom: 18,
|
||||||
|
}}>
|
||||||
|
{g.label}
|
||||||
|
</div>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 14, color: '#7a85a8',
|
||||||
|
lineHeight: 1.65, marginBottom: 22,
|
||||||
|
}}>
|
||||||
|
{g.desc}
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: 7 }}>
|
||||||
|
{g.topics.map(topic => (
|
||||||
|
<span key={topic} style={{
|
||||||
|
padding: '4px 10px',
|
||||||
|
background: `${g.color}14`,
|
||||||
|
border: `1px solid ${g.color}38`,
|
||||||
|
borderRadius: 4,
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 11,
|
||||||
|
fontWeight: 700, letterSpacing: '0.08em', textTransform: 'uppercase',
|
||||||
|
color: g.color,
|
||||||
|
}}>
|
||||||
|
{topic}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* CONTACT */}
|
||||||
|
<section style={{ padding: '100px 0 80px' }}>
|
||||||
|
<div className="ab-wrap">
|
||||||
|
<div style={{ textAlign: 'center', maxWidth: 580, margin: '0 auto' }}>
|
||||||
|
<p className="ab-eyebrow">Get In Touch</p>
|
||||||
|
<h2 style={{
|
||||||
|
fontFamily: "'Russo One',sans-serif",
|
||||||
|
fontSize: 'clamp(28px,5vw,44px)', textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.02em', color: '#eceef8', marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
Have Feedback?
|
||||||
|
</h2>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 16, color: '#7a85a8',
|
||||||
|
lineHeight: 1.75, marginBottom: 36,
|
||||||
|
}}>
|
||||||
|
Found a bug? Question not rendering right? Spotted a math error? This project is built and maintained by one person — feedback actually gets read. If something's broken or something could be better, reach out.
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 16 }}>
|
||||||
|
<Link
|
||||||
|
to="/faq"
|
||||||
|
style={{
|
||||||
|
padding: '14px 36px',
|
||||||
|
background: 'rgba(245,197,24,.08)', border: '1px solid rgba(245,197,24,.35)',
|
||||||
|
borderRadius: 8, fontFamily: "'Barlow Condensed',sans-serif",
|
||||||
|
fontSize: 14, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase',
|
||||||
|
color: '#f5c518', textDecoration: 'none', display: 'inline-block',
|
||||||
|
transition: 'background .2s, border-color .2s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => { e.currentTarget.style.background = 'rgba(245,197,24,.16)'; e.currentTarget.style.borderColor = 'rgba(245,197,24,.6)'; }}
|
||||||
|
onMouseLeave={e => { e.currentTarget.style.background = 'rgba(245,197,24,.08)'; e.currentTarget.style.borderColor = 'rgba(245,197,24,.35)'; }}
|
||||||
|
>
|
||||||
|
Read the FAQ
|
||||||
|
</Link>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 13, color: '#4a5470',
|
||||||
|
letterSpacing: '0.04em',
|
||||||
|
}}>
|
||||||
|
Or find the project on GitHub and open an issue.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
|
<footer style={{
|
||||||
|
background: 'rgba(7,8,15,.95)',
|
||||||
|
borderTop: '1px solid rgba(255,255,255,.07)',
|
||||||
|
padding: '40px 24px',
|
||||||
|
}}>
|
||||||
|
<div className="ab-wrap" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 18 }}>
|
||||||
|
<Link to="/" style={{ textDecoration: 'none' }}>
|
||||||
|
<span style={{ fontFamily: "'Russo One',sans-serif", fontSize: 16, letterSpacing: '0.04em', color: '#eceef8' }}>
|
||||||
|
RANKED<span style={{ color: '#f5c518' }}>EQAO</span>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<div style={{ display: 'flex', gap: 28, flexWrap: 'wrap', justifyContent: 'center' }}>
|
||||||
|
<Link to="/" className="ab-footer-link">Home</Link>
|
||||||
|
<Link to="/faq" className="ab-footer-link">FAQ</Link>
|
||||||
|
</div>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 11,
|
||||||
|
color: 'rgba(122,133,168,.45)', letterSpacing: '0.04em', textAlign: 'center',
|
||||||
|
}}>
|
||||||
|
Ranked EQAO — Built for Ontario students · Not affiliated with EQAO or the Ontario government.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
const r = parseInt(hex.slice(1, 3), 16);
|
||||||
|
const g = parseInt(hex.slice(3, 5), 16);
|
||||||
|
const b = parseInt(hex.slice(5, 7), 16);
|
||||||
|
return `${r},${g},${b}`;
|
||||||
|
}
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { Link, useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
|
const DIAMONDS = [
|
||||||
|
{ left: '5%', top: '12%', size: 10, dur: '9s', delay: '0s' },
|
||||||
|
{ left: '18%', top: '58%', size: 8, dur: '13s', delay: '2s' },
|
||||||
|
{ left: '30%', top: '22%', size: 14, dur: '11s', delay: '0.5s' },
|
||||||
|
{ left: '48%', top: '72%', size: 7, dur: '10s', delay: '1.5s' },
|
||||||
|
{ left: '58%', top: '20%', size: 16, dur: '12s', delay: '0.8s' },
|
||||||
|
{ left: '70%', top: '65%', size: 10, dur: '8s', delay: '3s' },
|
||||||
|
{ left: '82%', top: '30%', size: 13, dur: '14s', delay: '1s' },
|
||||||
|
{ left: '90%', top: '75%', size: 9, dur: '9s', delay: '4s' },
|
||||||
|
{ left: '38%', top: '45%', size: 6, dur: '15s', delay: '2.5s' },
|
||||||
|
{ left: '62%', top: '88%', size: 11, dur: '11s', delay: '0.3s' },
|
||||||
|
{ left: '12%', top: '82%', size: 8, dur: '13s', delay: '1.8s' },
|
||||||
|
{ left: '76%', top: '10%', size: 12, dur: '10s', delay: '3.5s' },
|
||||||
|
];
|
||||||
|
const RINGS = [
|
||||||
|
{ left: '10%', top: '35%', size: 60, dur: '7s', delay: '0s' },
|
||||||
|
{ left: '78%', top: '18%', size: 44, dur: '9s', delay: '2.5s' },
|
||||||
|
{ left: '45%', top: '62%', size: 80, dur: '11s', delay: '1s' },
|
||||||
|
{ left: '88%', top: '55%', size: 36, dur: '8s', delay: '3.5s' },
|
||||||
|
];
|
||||||
|
const SCANLINES = [
|
||||||
|
{ top: '28%', width: '160px', left: '5%', dur: '8s', delay: '0s' },
|
||||||
|
{ top: '52%', width: '120px', left: '74%', dur: '10s', delay: '3s' },
|
||||||
|
{ top: '18%', width: '200px', left: '38%', dur: '12s', delay: '1.5s' },
|
||||||
|
{ top: '75%', width: '140px', left: '20%', dur: '9s', delay: '2s' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const PAGE_CSS = `
|
||||||
|
@keyframes faqFadeUp {
|
||||||
|
from { opacity:0; transform:translateY(16px); }
|
||||||
|
to { opacity:1; transform:translateY(0); }
|
||||||
|
}
|
||||||
|
@keyframes faqHeroPulse {
|
||||||
|
0%,100% { opacity:.2; transform:translate(-50%,-50%) scale(1); }
|
||||||
|
50% { opacity:.38; transform:translate(-50%,-50%) scale(1.12); }
|
||||||
|
}
|
||||||
|
.fq-orb {
|
||||||
|
position:absolute; top:50%; left:50%;
|
||||||
|
width:900px; height:600px;
|
||||||
|
background:radial-gradient(ellipse at center,
|
||||||
|
rgba(245,197,24,.06) 0%, rgba(37,99,235,.04) 45%, transparent 70%);
|
||||||
|
border-radius:50%; pointer-events:none;
|
||||||
|
animation:faqHeroPulse 10s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.fq-nav {
|
||||||
|
position:fixed; top:0; left:0; right:0; z-index:100;
|
||||||
|
height:64px; display:flex; align-items:center;
|
||||||
|
justify-content:space-between; padding:0 40px;
|
||||||
|
background:rgba(7,8,15,.85);
|
||||||
|
backdrop-filter:blur(20px); -webkit-backdrop-filter:blur(20px);
|
||||||
|
border-bottom:1px solid rgba(255,255,255,.07);
|
||||||
|
}
|
||||||
|
.fq-nav__logo {
|
||||||
|
font-family:'Russo One',sans-serif; font-size:19px;
|
||||||
|
letter-spacing:.04em; text-transform:uppercase;
|
||||||
|
color:#eceef8; text-decoration:none;
|
||||||
|
}
|
||||||
|
.fq-nav__logo span { color:#f5c518; }
|
||||||
|
.fq-nav__links { display:flex; align-items:center; gap:28px; }
|
||||||
|
.fq-nav-link {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:13px;
|
||||||
|
font-weight:600; letter-spacing:.1em; text-transform:uppercase;
|
||||||
|
color:#7a85a8; text-decoration:none; cursor:pointer;
|
||||||
|
background:none; border:none; padding:0; transition:color .2s;
|
||||||
|
}
|
||||||
|
.fq-nav-link:hover { color:#eceef8; }
|
||||||
|
.fq-nav-signin {
|
||||||
|
padding:8px 18px;
|
||||||
|
background:rgba(245,197,24,.08); border:1px solid rgba(245,197,24,.35);
|
||||||
|
border-radius:6px; font-family:'Barlow Condensed',sans-serif;
|
||||||
|
font-size:13px; font-weight:700; letter-spacing:.1em; text-transform:uppercase;
|
||||||
|
color:#f5c518; cursor:pointer; transition:background .2s, border-color .2s;
|
||||||
|
}
|
||||||
|
.fq-nav-signin:hover { background:rgba(245,197,24,.16); border-color:rgba(245,197,24,.6); }
|
||||||
|
.fq-wrap { width:100%; max-width:800px; margin:0 auto; padding:0 24px; }
|
||||||
|
.fq-eyebrow {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:11px;
|
||||||
|
font-weight:700; letter-spacing:.3em; text-transform:uppercase;
|
||||||
|
color:#f5c518; display:flex; align-items:center; justify-content:center;
|
||||||
|
gap:12px; margin-bottom:14px;
|
||||||
|
}
|
||||||
|
.fq-eyebrow::before,.fq-eyebrow::after {
|
||||||
|
content:''; display:block; width:36px; height:1px;
|
||||||
|
background:rgba(245,197,24,.4);
|
||||||
|
}
|
||||||
|
.fq-item {
|
||||||
|
border-bottom:1px solid rgba(255,255,255,.07);
|
||||||
|
}
|
||||||
|
.fq-item:first-child {
|
||||||
|
border-top:1px solid rgba(255,255,255,.07);
|
||||||
|
}
|
||||||
|
.fq-question {
|
||||||
|
width:100%; display:flex; align-items:center;
|
||||||
|
justify-content:space-between; padding:22px 0;
|
||||||
|
background:none; border:none; cursor:pointer; text-align:left; gap:16px;
|
||||||
|
transition:opacity .15s;
|
||||||
|
}
|
||||||
|
.fq-question:hover .fq-q-text { color:#eceef8; }
|
||||||
|
.fq-q-num {
|
||||||
|
font-family:'Russo One',sans-serif; font-size:12px;
|
||||||
|
color:rgba(245,197,24,.35); min-width:28px; flex-shrink:0;
|
||||||
|
transition:color .2s;
|
||||||
|
}
|
||||||
|
.fq-item--open .fq-q-num { color:rgba(245,197,24,.7); }
|
||||||
|
.fq-q-text {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:20px;
|
||||||
|
font-weight:700; letter-spacing:.03em; color:#9ba5c4;
|
||||||
|
transition:color .2s; flex:1;
|
||||||
|
}
|
||||||
|
.fq-item--open .fq-q-text { color:#eceef8; }
|
||||||
|
.fq-chevron {
|
||||||
|
flex-shrink:0; transition:transform .3s cubic-bezier(.16,1,.3,1);
|
||||||
|
}
|
||||||
|
.fq-item--open .fq-chevron { transform:rotate(180deg); }
|
||||||
|
.fq-answer-wrap {
|
||||||
|
overflow:hidden;
|
||||||
|
transition:max-height .38s cubic-bezier(.16,1,.3,1);
|
||||||
|
}
|
||||||
|
.fq-answer {
|
||||||
|
padding:0 0 26px 44px;
|
||||||
|
font-family:'Barlow',sans-serif; font-size:15px;
|
||||||
|
color:#7a85a8; line-height:1.75;
|
||||||
|
transition:opacity .25s ease;
|
||||||
|
}
|
||||||
|
.fq-answer strong { color:#9ba5c4; font-weight:600; }
|
||||||
|
.fq-footer-link {
|
||||||
|
font-family:'Barlow Condensed',sans-serif; font-size:12px;
|
||||||
|
letter-spacing:.12em; text-transform:uppercase;
|
||||||
|
color:#7a85a8; text-decoration:none; transition:color .2s;
|
||||||
|
}
|
||||||
|
.fq-footer-link:hover { color:#eceef8; }
|
||||||
|
@media (max-width:768px) {
|
||||||
|
.fq-nav { padding:0 20px; }
|
||||||
|
.fq-nav-link.fq-hide-mobile { display:none; }
|
||||||
|
.fq-answer { padding-left:28px; }
|
||||||
|
}
|
||||||
|
@media (max-width:500px) {
|
||||||
|
.fq-nav__links { gap:12px; }
|
||||||
|
.fq-q-text { font-size:17px; }
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const FAQS = [
|
||||||
|
{
|
||||||
|
q: 'What is EQAO?',
|
||||||
|
a: 'EQAO stands for the Education Quality and Accountability Office. It\'s Ontario\'s standardized testing body, responsible for the province-wide assessments that all students in Grades 3, 6, and 9 take. The tests cover math and literacy. This app focuses on the math portion.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Is this official?',
|
||||||
|
a: 'No. Ranked EQAO is an independent project — it has no affiliation with EQAO, the Ontario government, or any school board. It\'s built by one developer using publicly known curriculum expectations. Think of it as fan-made study content.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Is it free?',
|
||||||
|
a: 'Yes, completely. No ads, no paywalls, no subscription, no premium tier. It costs nothing and always will. The goal is to be actually useful to students, and charging for it would work against that.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'How does ranking work?',
|
||||||
|
a: 'Ranked EQAO uses an ELO system — the same rating model used in chess and most competitive games. Everyone starts at 800. When you win a match, you gain points; when you lose, you lose points. How many you gain or lose depends on the difference in ratings between you and your opponent.\n\nFive tiers: Bronze (800), Silver (900), Gold (1100), Platinum (1300), Diamond (1500+).',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'What grades are supported?',
|
||||||
|
a: 'Grade 3, Grade 6, and Grade 9 math. Each grade has its own separate queue, leaderboard, and ELO rating — so your Grade 6 rank doesn\'t affect your Grade 9 rank. You can play any grade you want.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'How are questions generated?',
|
||||||
|
a: 'Math questions are built from pre-written templates that match EQAO curriculum expectations for each grade. The values — numbers, units, names, scenarios — are randomized on every attempt, so you can\'t just memorize answers. The structure stays consistent with EQAO question style.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'What is a ranked match?',
|
||||||
|
a: 'A ranked match is a 1v1 race. You and an opponent from the matchmaking queue get the same set of questions at the same time. First player to correctly answer all questions wins the match and gains ELO. The other player loses ELO. Speed and accuracy both matter.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'What happens if my opponent disconnects?',
|
||||||
|
a: 'If your opponent goes inactive for more than 20 seconds — closes the tab, loses connection, or just stops responding — they\'re marked as a forfeit. You get the win and the ELO that comes with it. This avoids draws or unfair timeouts from connection issues.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Can I change my username?',
|
||||||
|
a: 'Yes. Go to Settings from your dashboard and you\'ll see the option to update your username. Usernames must be 3–20 characters and can only contain letters, numbers, and underscores. Changes take effect immediately.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Why do I have to get 100% to advance?',
|
||||||
|
a: "That's how the real EQAO test works — every question counts and there's no partial credit. This app reflects that. Getting 9 out of 11 correct isn't a pass in the actual test, and it's not a pass here either. The point is mastery, not getting close enough.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Is there a mobile app?',
|
||||||
|
a: 'Not yet. There\'s no native iOS or Android app. The web app is built to be responsive and should work reasonably well on mobile browsers, but it\'s designed primarily for desktop play. A dedicated mobile app is something that could happen in the future.',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
q: 'Will English or language questions be added?',
|
||||||
|
a: 'Yes — English is planned. The backend already has a Gemini integration for generating reading passages and comprehension questions, as well as checking free-response answers. It\'s partially built and will be available in a future update.',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function FAQ() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [openIndex, setOpenIndex] = useState(null);
|
||||||
|
|
||||||
|
function toggle(i) {
|
||||||
|
setOpenIndex(prev => (prev === i ? null : i));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<style>{PAGE_CSS}</style>
|
||||||
|
|
||||||
|
{/* Background */}
|
||||||
|
<div className="arena-bg">
|
||||||
|
<div className="arena-grain" />
|
||||||
|
<div className="arena-shapes">
|
||||||
|
{DIAMONDS.map((d, i) => (
|
||||||
|
<div key={i} className="shape shape--diamond" style={{ left: d.left, top: d.top, width: d.size, height: d.size, animationDuration: d.dur, animationDelay: d.delay }} />
|
||||||
|
))}
|
||||||
|
{RINGS.map((r, i) => (
|
||||||
|
<div key={i} className="shape shape--ring" style={{ left: r.left, top: r.top, width: r.size, height: r.size, animationDuration: r.dur, animationDelay: r.delay }} />
|
||||||
|
))}
|
||||||
|
{SCANLINES.map((s, i) => (
|
||||||
|
<div key={i} className="shape shape--line" style={{ top: s.top, left: s.left, width: s.width, animationDuration: s.dur, animationDelay: s.delay }} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Nav */}
|
||||||
|
<nav className="fq-nav">
|
||||||
|
<a href="/" className="fq-nav__logo">RANKED<span>EQAO</span></a>
|
||||||
|
<div className="fq-nav__links">
|
||||||
|
<a href="/#how-it-works" className="fq-nav-link fq-hide-mobile">How It Works</a>
|
||||||
|
<a href="/#leaderboard" className="fq-nav-link fq-hide-mobile">Leaderboard</a>
|
||||||
|
<a href="/about" className="fq-nav-link fq-hide-mobile">About</a>
|
||||||
|
<a href="/#signin" className="fq-nav-signin">Sign In</a>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div style={{ position: 'relative', zIndex: 1 }}>
|
||||||
|
|
||||||
|
{/* HERO */}
|
||||||
|
<section style={{
|
||||||
|
minHeight: '50vh', display: 'flex', flexDirection: 'column',
|
||||||
|
alignItems: 'center', justifyContent: 'center',
|
||||||
|
padding: '120px 0 60px', textAlign: 'center',
|
||||||
|
position: 'relative', overflow: 'hidden',
|
||||||
|
}}>
|
||||||
|
<div className="fq-orb" />
|
||||||
|
<div style={{ position: 'relative', zIndex: 1, width: '100%' }}>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 11, fontWeight: 700,
|
||||||
|
letterSpacing: '0.3em', textTransform: 'uppercase', color: '#f5c518',
|
||||||
|
marginBottom: 20, opacity: 0, animation: 'faqFadeUp .6s .05s ease both',
|
||||||
|
}}>
|
||||||
|
Got Questions?
|
||||||
|
</p>
|
||||||
|
<h1 style={{
|
||||||
|
fontFamily: "'Russo One',sans-serif",
|
||||||
|
fontSize: 'clamp(72px,16vw,160px)',
|
||||||
|
lineHeight: 0.85, letterSpacing: '-0.02em',
|
||||||
|
textTransform: 'uppercase', color: '#f5c518',
|
||||||
|
textShadow: '0 0 80px rgba(245,197,24,.3)',
|
||||||
|
marginBottom: 28, opacity: 0,
|
||||||
|
animation: 'faqFadeUp .7s .15s cubic-bezier(.16,1,.3,1) both',
|
||||||
|
}}>
|
||||||
|
FAQ
|
||||||
|
</h1>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif",
|
||||||
|
fontSize: 'clamp(14px,2.2vw,18px)', fontWeight: 500,
|
||||||
|
letterSpacing: '0.08em', textTransform: 'uppercase',
|
||||||
|
color: '#7a85a8', opacity: 0,
|
||||||
|
animation: 'faqFadeUp .6s .35s ease both',
|
||||||
|
}}>
|
||||||
|
Everything you need to know about Ranked EQAO
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* ACCORDION */}
|
||||||
|
<section style={{
|
||||||
|
padding: '60px 0 100px',
|
||||||
|
background: 'rgba(13,15,28,.78)',
|
||||||
|
backdropFilter: 'blur(2px)', WebkitBackdropFilter: 'blur(2px)',
|
||||||
|
}}>
|
||||||
|
<div className="fq-wrap">
|
||||||
|
{FAQS.map((item, i) => {
|
||||||
|
const isOpen = openIndex === i;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
key={i}
|
||||||
|
className={`fq-item${isOpen ? ' fq-item--open' : ''}`}
|
||||||
|
>
|
||||||
|
<button className="fq-question" onClick={() => toggle(i)} aria-expanded={isOpen}>
|
||||||
|
<span className="fq-q-num">{String(i + 1).padStart(2, '0')}</span>
|
||||||
|
<span className="fq-q-text">{item.q}</span>
|
||||||
|
<svg
|
||||||
|
className="fq-chevron"
|
||||||
|
width="20" height="20" viewBox="0 0 24 24"
|
||||||
|
fill="none" stroke="rgba(122,133,168,.6)"
|
||||||
|
strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"
|
||||||
|
>
|
||||||
|
<polyline points="6 9 12 15 18 9" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div
|
||||||
|
className="fq-answer-wrap"
|
||||||
|
style={{ maxHeight: isOpen ? '600px' : '0' }}
|
||||||
|
>
|
||||||
|
<p
|
||||||
|
className="fq-answer"
|
||||||
|
style={{ opacity: isOpen ? 1 : 0 }}
|
||||||
|
>
|
||||||
|
{item.a.split('\n\n').map((para, pi) => (
|
||||||
|
<span key={pi} style={{ display: 'block', marginBottom: pi < item.a.split('\n\n').length - 1 ? 14 : 0 }}>
|
||||||
|
{para}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Bottom CTA */}
|
||||||
|
<div style={{ textAlign: 'center', marginTop: 72, padding: '0 24px' }}>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif", fontSize: 16,
|
||||||
|
letterSpacing: '0.06em', color: '#4a5470', marginBottom: 20,
|
||||||
|
}}>
|
||||||
|
Still have a question?
|
||||||
|
</p>
|
||||||
|
<div style={{ display: 'flex', gap: 14, justifyContent: 'center', flexWrap: 'wrap' }}>
|
||||||
|
<Link
|
||||||
|
to="/about"
|
||||||
|
style={{
|
||||||
|
padding: '12px 28px',
|
||||||
|
background: 'rgba(245,197,24,.08)', border: '1px solid rgba(245,197,24,.35)',
|
||||||
|
borderRadius: 8, fontFamily: "'Barlow Condensed',sans-serif",
|
||||||
|
fontSize: 13, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase',
|
||||||
|
color: '#f5c518', textDecoration: 'none', display: 'inline-block',
|
||||||
|
transition: 'background .2s, border-color .2s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => { e.currentTarget.style.background = 'rgba(245,197,24,.16)'; e.currentTarget.style.borderColor = 'rgba(245,197,24,.6)'; }}
|
||||||
|
onMouseLeave={e => { e.currentTarget.style.background = 'rgba(245,197,24,.08)'; e.currentTarget.style.borderColor = 'rgba(245,197,24,.35)'; }}
|
||||||
|
>
|
||||||
|
Read About The Project
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={() => navigate('/')}
|
||||||
|
style={{
|
||||||
|
padding: '12px 28px', background: 'transparent', color: '#eceef8',
|
||||||
|
border: '1.5px solid rgba(255,255,255,.15)', borderRadius: 8,
|
||||||
|
fontFamily: "'Barlow Condensed',sans-serif",
|
||||||
|
fontSize: 13, fontWeight: 700, letterSpacing: '0.1em', textTransform: 'uppercase',
|
||||||
|
cursor: 'pointer', transition: 'border-color .2s',
|
||||||
|
}}
|
||||||
|
onMouseEnter={e => { e.currentTarget.style.borderColor = 'rgba(255,255,255,.4)'; }}
|
||||||
|
onMouseLeave={e => { e.currentTarget.style.borderColor = 'rgba(255,255,255,.15)'; }}
|
||||||
|
>
|
||||||
|
Back to Home
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{/* FOOTER */}
|
||||||
|
<footer style={{
|
||||||
|
background: 'rgba(7,8,15,.95)',
|
||||||
|
borderTop: '1px solid rgba(255,255,255,.07)',
|
||||||
|
padding: '40px 24px',
|
||||||
|
}}>
|
||||||
|
<div className="fq-wrap" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 18, maxWidth: 1100 }}>
|
||||||
|
<Link to="/" style={{ textDecoration: 'none' }}>
|
||||||
|
<span style={{ fontFamily: "'Russo One',sans-serif", fontSize: 16, letterSpacing: '0.04em', color: '#eceef8' }}>
|
||||||
|
RANKED<span style={{ color: '#f5c518' }}>EQAO</span>
|
||||||
|
</span>
|
||||||
|
</Link>
|
||||||
|
<div style={{ display: 'flex', gap: 28, flexWrap: 'wrap', justifyContent: 'center' }}>
|
||||||
|
<Link to="/" className="fq-footer-link">Home</Link>
|
||||||
|
<Link to="/about" className="fq-footer-link">About</Link>
|
||||||
|
</div>
|
||||||
|
<p style={{
|
||||||
|
fontFamily: "'Barlow',sans-serif", fontSize: 11,
|
||||||
|
color: 'rgba(122,133,168,.45)', letterSpacing: '0.04em', textAlign: 'center',
|
||||||
|
}}>
|
||||||
|
Ranked EQAO — Built for Ontario students · Not affiliated with EQAO or the Ontario government.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "MatchResult" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"userId" TEXT NOT NULL,
|
||||||
|
"grade" "Grade" NOT NULL,
|
||||||
|
"won" BOOLEAN NOT NULL,
|
||||||
|
"eloChange" INTEGER NOT NULL,
|
||||||
|
"eloAfter" INTEGER NOT NULL,
|
||||||
|
"timeSpentMs" INTEGER,
|
||||||
|
"playedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "MatchResult_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "MatchResult" ADD CONSTRAINT "MatchResult_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ADD COLUMN "passwordHash" TEXT,
|
||||||
|
ALTER COLUMN "googleId" DROP NOT NULL;
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
Reference in New Issue
Block a user