NightPrince's picture
Initial commit: Quran Recitation Validator v2.2.0
cd7dcf2
Raw
History Blame Contribute Delete
15.5 kB
:root {
--bg: #0b1a10;
--surface: #122318;
--surface2: #1a3326;
--surface3: #203d2f;
--gold: #c9a84c;
--gold-light: #e2c97e;
--gold-dim: #8a6f2e;
--green: #2e7d52;
--green-light: #3da368;
--text: #e8e0d0;
--text-dim: #9e9688;
--sub: #e05252; /* substitution */
--del: #e09c52; /* deletion */
--ins: #5282e0; /* insertion */
--ok: #52c97b; /* correct */
--border: #2a4a38;
--radius: 12px;
--radius-lg: 20px;
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { font-size: 16px; }
body {
font-family: 'Cairo', 'Amiri', sans-serif;
background: var(--bg);
color: var(--text);
min-height: 100vh;
direction: rtl;
background-image:
radial-gradient(ellipse at 20% 0%, rgba(46,125,82,.18) 0%, transparent 55%),
radial-gradient(ellipse at 80% 100%, rgba(201,168,76,.10) 0%, transparent 55%);
}
/* ── Scrollbar ── */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: var(--surface); }
::-webkit-scrollbar-thumb { background: var(--gold-dim); border-radius: 3px; }
/* ── Geometric border utility ── */
.geo-border {
border: 1px solid var(--border);
position: relative;
}
.geo-border::before, .geo-border::after {
content: '❖';
position: absolute;
color: var(--gold-dim);
font-size: .75rem;
line-height: 1;
}
.geo-border::before { top: -9px; right: 12px; }
.geo-border::after { bottom: -9px; left: 12px; }
/* ── Animations ── */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes shimmer {
0% { background-position: -200% center; }
100% { background-position: 200% center; }
}
@keyframes pulse-ring {
0% { box-shadow: 0 0 0 0 rgba(201,168,76,.35); }
70% { box-shadow: 0 0 0 10px rgba(201,168,76,0); }
100% { box-shadow: 0 0 0 0 rgba(201,168,76,0); }
}
@keyframes spin {
to { transform: rotate(360deg); }
}
.animate-fade-up { animation: fadeUp .45s ease both; }
/* ── Layout ── */
.app {
max-width: 860px;
margin: 0 auto;
padding: 2rem 1.25rem 4rem;
display: flex;
flex-direction: column;
gap: 1.75rem;
}
/* ── Header ── */
.header {
text-align: center;
padding: 2rem 1rem 1.5rem;
position: relative;
}
.header-ornament {
font-size: 1.1rem;
color: var(--gold-dim);
letter-spacing: .25em;
margin-bottom: .6rem;
}
.header h1 {
font-family: 'Amiri', serif;
font-size: 2.4rem;
font-weight: 700;
color: var(--gold-light);
line-height: 1.3;
text-shadow: 0 2px 20px rgba(201,168,76,.3);
}
.header-sub {
color: var(--text-dim);
font-size: .95rem;
margin-top: .5rem;
}
.header-line {
display: flex;
align-items: center;
gap: .75rem;
margin: 1rem auto 0;
width: fit-content;
}
.header-line span {
height: 1px;
width: 60px;
background: linear-gradient(90deg, transparent, var(--gold-dim));
}
.header-line span:last-child {
background: linear-gradient(270deg, transparent, var(--gold-dim));
}
.header-line .star { color: var(--gold); font-size: 1rem; }
/* ── Input card ── */
.input-card {
background: var(--surface);
border-radius: var(--radius-lg);
padding: 1.5rem;
border: 1px solid var(--border);
box-shadow: 0 4px 24px rgba(0,0,0,.3);
}
.input-label {
font-size: .85rem;
color: var(--gold);
font-weight: 600;
letter-spacing: .05em;
margin-bottom: .75rem;
display: flex;
align-items: center;
gap: .5rem;
}
.input-label svg { width: 16px; height: 16px; flex-shrink: 0; }
.verse-textarea {
width: 100%;
min-height: 120px;
background: var(--surface2);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--text);
font-family: 'Amiri Quran', 'Amiri', serif;
font-size: 1.55rem;
line-height: 2;
padding: 1rem 1.1rem;
resize: vertical;
outline: none;
transition: border-color .2s, box-shadow .2s;
direction: rtl;
}
.verse-textarea::placeholder {
color: var(--text-dim);
font-size: 1.2rem;
line-height: 2.2;
}
.verse-textarea:focus {
border-color: var(--gold-dim);
box-shadow: 0 0 0 3px rgba(201,168,76,.12);
}
/* ── Quick examples ── */
.examples {
margin-top: .85rem;
display: flex;
flex-wrap: wrap;
gap: .5rem;
align-items: center;
}
.examples-label {
font-size: .78rem;
color: var(--text-dim);
flex-shrink: 0;
}
.example-chip {
background: var(--surface3);
border: 1px solid var(--border);
border-radius: 20px;
padding: .25rem .75rem;
font-size: .8rem;
font-family: 'Amiri', serif;
color: var(--text-dim);
cursor: pointer;
transition: all .18s;
white-space: nowrap;
}
.example-chip:hover {
background: var(--surface2);
border-color: var(--gold-dim);
color: var(--gold-light);
}
/* ── Actions ── */
.actions {
display: flex;
gap: .75rem;
margin-top: 1rem;
justify-content: flex-start;
}
.btn-validate {
display: flex;
align-items: center;
gap: .55rem;
background: linear-gradient(135deg, #2e7d52, #1f5e3b);
border: 1px solid var(--green);
color: #fff;
font-family: 'Cairo', sans-serif;
font-weight: 700;
font-size: 1rem;
padding: .7rem 1.75rem;
border-radius: var(--radius);
cursor: pointer;
transition: all .2s;
letter-spacing: .02em;
}
.btn-validate:hover:not(:disabled) {
background: linear-gradient(135deg, #3da368, #2e7d52);
box-shadow: 0 4px 16px rgba(46,125,82,.4);
animation: pulse-ring 1.2s ease-out;
}
.btn-validate:disabled {
opacity: .55;
cursor: not-allowed;
}
.btn-validate .spinner {
width: 18px;
height: 18px;
border: 2px solid rgba(255,255,255,.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin .7s linear infinite;
}
.btn-clear {
background: transparent;
border: 1px solid var(--border);
color: var(--text-dim);
font-family: 'Cairo', sans-serif;
font-size: .9rem;
padding: .7rem 1.2rem;
border-radius: var(--radius);
cursor: pointer;
transition: all .2s;
}
.btn-clear:hover { border-color: var(--text-dim); color: var(--text); }
/* ── Result card ── */
.result-card {
background: var(--surface);
border-radius: var(--radius-lg);
border: 1px solid var(--border);
overflow: hidden;
box-shadow: 0 4px 24px rgba(0,0,0,.3);
}
/* ── Verdict banner ── */
.verdict-banner {
padding: 1.25rem 1.5rem;
display: flex;
align-items: center;
gap: 1rem;
}
.verdict-banner.correct {
background: linear-gradient(135deg, rgba(82,201,123,.15), rgba(46,125,82,.08));
border-bottom: 1px solid rgba(82,201,123,.25);
}
.verdict-banner.incorrect {
background: linear-gradient(135deg, rgba(224,82,82,.12), rgba(150,50,50,.06));
border-bottom: 1px solid rgba(224,82,82,.2);
}
.verdict-icon {
font-size: 2.5rem;
line-height: 1;
flex-shrink: 0;
}
.verdict-text h2 {
font-family: 'Amiri', serif;
font-size: 1.5rem;
font-weight: 700;
}
.verdict-banner.correct .verdict-text h2 { color: var(--ok); }
.verdict-banner.incorrect .verdict-text h2 { color: var(--sub); }
.verdict-text p {
font-size: .85rem;
color: var(--text-dim);
margin-top: .2rem;
}
/* ── Verse info ── */
.verse-info {
display: flex;
gap: .75rem;
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
}
.verse-badge {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
padding: .4rem .9rem;
font-size: .82rem;
display: flex;
flex-direction: column;
align-items: center;
gap: .1rem;
}
.verse-badge .badge-label {
color: var(--text-dim);
font-size: .7rem;
text-transform: uppercase;
letter-spacing: .05em;
}
.verse-badge .badge-value {
color: var(--gold-light);
font-weight: 600;
font-family: 'Amiri', serif;
}
.match-type-badge {
border-radius: 20px;
padding: .3rem .85rem;
font-size: .75rem;
font-weight: 700;
letter-spacing: .04em;
align-self: center;
margin-right: auto;
}
.match-exact { background: rgba(82,201,123,.15); color: var(--ok); border: 1px solid rgba(82,201,123,.3); }
.match-lemma { background: rgba(82,130,224,.15); color: #82a8e8; border: 1px solid rgba(82,130,224,.3); }
.match-root { background: rgba(201,168,76,.15); color: var(--gold); border: 1px solid rgba(201,168,76,.3); }
.match-fuzzy { background: rgba(224,156,82,.15); color: #e8a85a; border: 1px solid rgba(224,156,82,.3); }
/* ── WER gauge ── */
.wer-section {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.wer-header {
display: flex;
justify-content: space-between;
align-items: baseline;
margin-bottom: .5rem;
}
.wer-label { font-size: .8rem; color: var(--text-dim); }
.wer-value { font-size: .9rem; font-weight: 700; }
.wer-bar-bg {
height: 8px;
background: var(--surface3);
border-radius: 4px;
overflow: hidden;
}
.wer-bar-fill {
height: 100%;
border-radius: 4px;
transition: width .6s cubic-bezier(.4,0,.2,1);
}
.wer-ok { background: linear-gradient(90deg, #52c97b, #3da368); }
.wer-low { background: linear-gradient(90deg, #c9c952, #a3a33d); }
.wer-mid { background: linear-gradient(90deg, #e09c52, #b07030); }
.wer-high { background: linear-gradient(90deg, #e05252, #b03030); }
/* ── Mistakes list ── */
.mistakes-section {
padding: 1rem 1.5rem;
border-bottom: 1px solid var(--border);
}
.section-title {
font-size: .82rem;
color: var(--text-dim);
font-weight: 600;
letter-spacing: .06em;
margin-bottom: .85rem;
display: flex;
align-items: center;
gap: .5rem;
}
.section-title::after {
content: '';
flex: 1;
height: 1px;
background: var(--border);
}
.mistakes-list {
display: flex;
flex-direction: column;
gap: .55rem;
}
.mistake-row {
display: flex;
align-items: flex-start;
gap: .75rem;
padding: .65rem .9rem;
border-radius: var(--radius);
font-size: .92rem;
font-family: 'Amiri', serif;
line-height: 1.6;
animation: fadeUp .3s ease both;
}
.mistake-row.sub { background: rgba(224,82,82,.08); border: 1px solid rgba(224,82,82,.2); }
.mistake-row.del { background: rgba(224,156,82,.08); border: 1px solid rgba(224,156,82,.2); }
.mistake-row.ins { background: rgba(82,130,224,.08); border: 1px solid rgba(82,130,224,.2); }
.mistake-tag {
font-size: .65rem;
font-weight: 700;
letter-spacing: .06em;
padding: .2rem .55rem;
border-radius: 10px;
flex-shrink: 0;
margin-top: .2rem;
font-family: 'Cairo', sans-serif;
}
.mistake-row.sub .mistake-tag { background: rgba(224,82,82,.2); color: var(--sub); }
.mistake-row.del .mistake-tag { background: rgba(224,156,82,.2); color: var(--del); }
.mistake-row.ins .mistake-tag { background: rgba(82,130,224,.2); color: var(--ins); }
.mistake-text { color: var(--text); }
.mistake-text .word-wrong { color: var(--sub); font-weight: 700; }
.mistake-text .word-correct { color: var(--ok); font-weight: 700; }
.mistake-text .word-missing { color: var(--del); font-weight: 700; }
.mistake-text .word-extra { color: var(--ins); font-weight: 700; }
.arrow { color: var(--text-dim); margin: 0 .3rem; }
.no-mistakes {
text-align: center;
padding: .75rem;
color: var(--ok);
font-size: .9rem;
}
/* ── Correct verse display ── */
.verse-display {
padding: 1.25rem 1.5rem;
}
.correct-verse {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 1.2rem 1.5rem;
text-align: center;
position: relative;
overflow: hidden;
}
.correct-verse::before {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(135deg, rgba(201,168,76,.04), transparent);
pointer-events: none;
}
.correct-verse-text {
font-family: 'Amiri Quran', 'Amiri', serif;
font-size: 1.75rem;
line-height: 2.2;
color: var(--gold-light);
direction: rtl;
}
.correct-verse-meta {
margin-top: .5rem;
font-size: .8rem;
color: var(--text-dim);
}
/* ── Stats row ── */
.stats-row {
display: flex;
gap: .5rem;
padding: 0 1.5rem 1.25rem;
flex-wrap: wrap;
}
.stat-chip {
background: var(--surface2);
border: 1px solid var(--border);
border-radius: 8px;
padding: .35rem .8rem;
font-size: .78rem;
color: var(--text-dim);
display: flex;
gap: .3rem;
align-items: center;
}
.stat-chip span { color: var(--gold); font-weight: 600; }
/* ── Empty state ── */
.empty-state {
text-align: center;
padding: 3rem 1rem;
color: var(--text-dim);
}
.empty-icon {
font-size: 3.5rem;
margin-bottom: 1rem;
opacity: .4;
}
.empty-state p { font-family: 'Amiri', serif; font-size: 1.1rem; }
/* ── Footer ── */
.footer {
text-align: center;
color: var(--text-dim);
font-size: .78rem;
padding-top: 1rem;
border-top: 1px solid var(--border);
}
/* ── Tashkeel section ── */
.tashkeel-section { padding: 0 1.5rem 1rem; }
.tashkeel-list { display: flex; flex-direction: column; gap: .4rem; }
.tashkeel-row {
display: flex;
gap: .75rem;
align-items: baseline;
padding: .4rem .8rem;
background: rgba(201,168,76,.06);
border: 1px solid rgba(201,168,76,.2);
border-radius: 8px;
font-size: .87rem;
}
.tashkeel-word { color: var(--gold-light); font-family: 'Amiri', serif; font-weight: 700; }
.tashkeel-detail { color: var(--text-dim); }
.tashkeel-detail em { font-style: normal; color: var(--text); }
/* ── Multi-verse accordion ── */
.verses-accord-list { display: flex; flex-direction: column; gap: .4rem; padding: 0 1.5rem 1rem; }
.verse-accord {
border-radius: var(--radius);
overflow: hidden;
animation: fadeUp .35s ease both;
}
.verse-accord.accord-ok { border: 1px solid rgba(82,201,123,.2); }
.verse-accord.accord-err { border: 1px solid rgba(224,82,82,.2); }
.accord-header {
width: 100%;
display: flex;
align-items: center;
gap: .6rem;
padding: .65rem 1rem;
background: var(--surface2);
border: none;
cursor: pointer;
text-align: right;
direction: rtl;
color: var(--text);
font-family: 'Cairo', sans-serif;
font-size: .92rem;
transition: background .15s;
}
.accord-header:hover { background: var(--surface3); }
.accord-ok .accord-header { background: rgba(82,201,123,.06); }
.accord-err .accord-header { background: rgba(224,82,82,.06); }
.accord-icon { font-size: .9rem; flex-shrink: 0; }
.accord-key { font-weight: 700; color: var(--gold); min-width: 50px; }
.accord-surah { color: var(--text-dim); font-size: .82rem; }
.accord-wer { font-size: .78rem; font-weight: 600; margin-right: auto; }
.accord-chevron { margin-right: auto; color: var(--text-dim); display: flex; }
.accord-body {
padding: .75rem 1rem;
background: var(--surface);
border-top: 1px solid var(--border);
}
/* ── Responsive ── */
@media (max-width: 600px) {
.header h1 { font-size: 1.8rem; }
.verse-textarea { font-size: 1.3rem; }
.correct-verse-text { font-size: 1.4rem; }
.verse-info { gap: .5rem; }
.verses-accord-list { padding: 0 .75rem 1rem; }
}