Merge branch 'main' into production

This commit is contained in:
0x1eef 2024-02-28 22:49:36 -03:00
commit 4696f5c724
26 changed files with 5425 additions and 293 deletions

View file

@ -67,7 +67,7 @@ GEM
nanoc-core (~> 4.11, >= 4.11.15)
nanoc-gzip.rb (0.2.3)
nanoc (~> 4.12)
nanoc-tidy.rb (0.3.0)
nanoc-tidy.rb (0.4.0)
nanoc-webpack.rb (0.5.6)
ryo.rb (~> 0.4)
nio4r (2.7.0)

View file

@ -10,7 +10,7 @@ html[lang="en"] {
}
html[lang="ar"] {
font-family: "Amiri Quran Regular";
font-family: "Mada Regular";
}
html {
@ -29,98 +29,33 @@ body .root {
body .root .content.theme {
margin: 0 auto;
height: 100%;
width: 85%;
height: 80%;
max-width: $max-width;
header {
h1 {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
font-size: xx-large;
margin: 0;
height: 75px;
a {
text-decoration: none;
}
.br {
width: 100%;
}
nav, div {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
nav {
height: 40px;
.react-select {
.react-select.theme {
.selected-option {
display: flex;
flex-direction: row;
align-items: center;
}
.react-select.theme {
justify-content: flex-end;
}
.react-select.language {
min-width: 50px;
text-align: right;
ul {
position:relative;
right: 75px;
li {
align-items: center;
font-family: "Kanit Regular";
border-radius: 15px;
span {
display: flex;
height: 100%;
align-items: center;
padding-right: 7px;
}
&:hover {
text-decoration: none;
}
}
li.en {
font-size: medium;
}
}
}
.br {
width: 100%;
}
}
}
ul.body {
clear: both;
list-style-type: none;
padding: 0;
margin: 0;
height: 100%;
}
footer {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: flex-end;
width: 100%;
margin-top: 15px;
.br {
width: 100%;
}
}
.scroll-y {
overflow-y: scroll !important;
overflow-y: auto !important;
}
}
/* English-specific rules */
body .root .content.en {
h1 {
font-size: x-large;
@ -134,7 +69,8 @@ body .root .content.en {
top: 5px;
li.ar span {
position: relative;
bottom: 8px;
bottom: 3px;
right: 3px;
}
}
}
@ -148,6 +84,7 @@ body .root .content.en {
}
}
/* Arabic-specific rules */
body .root .content.theme.ar {
header {
nav, div {
@ -156,6 +93,10 @@ body .root .content.theme.ar {
ul {
top: 15px;
right: 55px;
li.en span {
position: relative;
right: 3px;
}
}
.selected-option.ar {
font-size: x-large;

View file

@ -3,8 +3,8 @@
}
.play.icon, .pause.icon {
width: 32px;
height: 22px;
width: 20px;
height: 20px;
}
.pause.icon {
@ -28,14 +28,15 @@
.stalled.icon {
display: inline-block;
position: relative;
left: -34px;
left: 30px;
top: 10px;
height: 4px;
div {
display: inline-block;
position: absolute;
left: 8px;
width: 6px;
width: 5px;
animation: stalled 0.8s cubic-bezier(0, 0.5, 0.5, 1) infinite;
}
@ -77,35 +78,8 @@
.root .content.theme {
footer {
.sound-on.icon, .sound-off.icon {
width: 32px;
height: 32px;
g { transform: translate(32px,30px); }
}
.play.icon {
g { transform: translate(-16px, 0); }
}
.pause.icon {
g { transform: translate(-14px, 0); }
}
}
}
.root .content.theme.ar {
ul.stream span.title {
.sound-on.icon, .sound-off.icon {
height: 40px;
transform: rotate(180deg) translate(0, 3px);
}
}
footer {
.sound-on.icon, .sound-off.icon {
transform: rotate(180deg);
g {transform: translate(32px, 0); }
}
.right-arrow.icon {
transform: rotate(180deg);
width: 28px;
height: 28px;
}
}
}

View file

@ -55,7 +55,6 @@
display: flex;
align-items: center;
justify-content: end;
font-family: "Amiri Quran Regular";
font-size: large;
}
}

View file

@ -4,53 +4,18 @@
body .root .content.theme {
@import "breakpoints";
height: 75%;
@media (max-device-height: 700px) {
height: 65%;
}
@media (min-device-height: 1024px) {
height: 80%;
}
@media (max-width: $breakpoint-sm) {
height: 75%;
}
header {
.react-select.language {
ul li {
border-radius: 15px;
}
}
}
ul.body.index {
li.surah a {
display: flex;
align-items: center;
border-radius: 10px;
min-height: 45px;
width: 98%;
&:visited, &:active, &:link {
text-decoration: none;
}
span.id {
width: 45px;
font-weight: bolder;
font-size: large;
text-align: center;
}
span.name {
font-weight: normal;
}
span.transliterated {
display: flex;
flex-grow: 1;
justify-content: flex-end;
padding: 0 10px 0 0;
@media screen and (max-width: $max-width) {
@media (max-width: $breakpoint-sm) {
display: none;
}
}
@ -58,35 +23,15 @@ body .root .content.theme {
}
footer {
flex-wrap: unset;
a {
display: flex;
flex-direction: row;
align-items: center;
width: 100%;
span {
padding: 0 0 0 7px;
}
}
input {
padding: 7.5px;
border-radius: 10px;
border: 1px solid #000;
border-radius: 5px;
}
@media (max-width: $breakpoint-sm) {
flex-wrap: wrap;
flex-direction: column-reverse;
align-items: flex-start;
@media(max-width: $breakpoint-sm) {
a {
place-content: center;
width: 100%;
justify-content: center;
}
input {
margin-bottom: 10px;
width: 100%;
max-width: unset;
}
.right-arrow.icon, .surah-index-filter {
.right-arrow, .surah-index-filter {
display: none;
}
}
@ -94,11 +39,12 @@ body .root .content.theme {
}
/* English-specific rules */
body .root .content.theme.en {
ul.body.index li.surah {
}
}
/* Arabic-specific rules */
body .root .content.theme.ar {
@import "breakpoints";
font-size: larger;
@ -106,7 +52,6 @@ body .root .content.theme.ar {
ul.body.index {
li.surah {
a {
font-family: "Mada Regular";
span.id {
font-size: x-large;
}
@ -117,18 +62,13 @@ body .root .content.theme.ar {
}
}
}
footer {
flex-direction: row;
@media (max-width: $breakpoint-sm) {
flex-direction: column-reverse;
}
font-family: "Mada Regular";
a span {
padding: 0 7px 0 0;
a {
flex-direction: row-reverse;
}
}
}
@import "themes/blue";
@import "themes/green";
@import "vendor/tail";

View file

@ -3,37 +3,8 @@
@import "components/Icon";
body .root .content.theme {
height: 80%;
@media (max-device-height: 968px) { height: 75%; }
@media (max-height: 800px) { height: 70%; }
@media (max-height: 700px) { height: 65%; }
@media (max-height: 600px) { height: 60%; }
@media (max-height: 500px) { height: 55%; }
@media (max-height: 400px) { height: 45%; }
@media (max-height: 330px) { height: 35%; }
ul.body.stream {
scrollbar-gutter: stable;
overflow: hidden;
li.ayah {
padding: 10px 0 10px 0;
span.title {
display: flex;
align-items: center;
}
.svg.sound-on, .svg.sound-off {
cursor: pointer;
height: 24px;
width: 24px;
g { transform: translate(0px, 18px); }
}
p {
width: 100%;
margin: 0;
}
}
li.ayah.fade {
@keyframes FadeIn {
0% { opacity: 0;}
@ -47,39 +18,26 @@ body .root .content.theme {
}
footer {
.timer {
display: flex;
flex-direction: column;
align-items: flex-end;
height: 24px;
width: 40px;
font-family: "Kanit Regular";
text-align: center;
font-weight: bold;
font-size: large;
.sound-off.icon, .sound-on.icon {
transform: translate(0, 5px);
}
}
}
.content.theme.en {
header {
nav {
height: 30px;
/* English-specific rules */
body .root .content.theme.en {
ul.body.stream {
.sound-off.icon, .sound-on.icon {
transform: translate(0, 10px);
}
}
}
/* Arabic-specific rules */
body .root .content.theme.ar {
header {
nav, div {
.localized-name {
display: flex;
font-size: x-large;
justify-content: flex-end;
align-items: flex-end;
width: 100%;
line-height: 1;
}
.surah-name {
flex-direction: row-reverse;
.transliterated-name {
display: none;
}
@ -87,31 +45,24 @@ body .root .content.theme.ar {
}
ul.body.stream {
li.ayah:first-child {
padding: 35px 0 10px 0;
}
li.ayah {
padding: 20px 0 10px 0;
span.title {
font-size: larger;
height: 0;
font-family: "Mada Regular";
.title {
display: block;
margin-bottom: 5px;
}
p {
line-height: 3;
font-size: x-large;
font-weight: normal;
.sound-off.icon, .sound-on.icon {
transform: rotate(180deg) translate(0, -4px);
}
}
padding: 0;
}
footer {
.timer {
align-items: flex-start;
justify-content: flex-start;
}
}
}
@import "themes/blue";
@import "themes/green";
@import "vendor/tail";

View file

@ -7,3 +7,28 @@
.root .content.theme.blue.ar {
direction: rtl;
}
.root .content.theme.blue {
@import "blue/colors";
.color-primary {
color: $primary-color;
}
.color-secondary {
color: $secondary-color;
}
.color-accent {
color: $accent-color;
}
.react-select.theme {
.selected-option {
.circle {
background: $primary-color;
border-radius: 10px;
}
}
}
}

View file

@ -2,7 +2,7 @@
@import "themes/blue/colors";
.react-select.theme {
.selected-option.blue .circle {
.selected-option .circle {
background: $primary-color;
}
ul li.blue .circle {

View file

@ -1,4 +1,4 @@
.root .content.theme.blue {
.root .surah-index.content.theme.blue {
@import "themes/blue/colors";
@import "breakpoints";
@ -34,9 +34,9 @@
}
}
input {
border: 1px solid $primary-color;
border: 1px solid $secondary-color;
&:focus {
outline-color: $primary-color;
outline-color: $secondary-color;
}
}
}

View file

@ -1,5 +1,6 @@
.root .content.theme.blue {
@import "themes/blue/colors";
@import "breakpoints";
header {
div {

View file

@ -12,3 +12,28 @@
.selected-option { color: $primary-color; }
}
}
.root .content.theme.green {
@import "green/colors";
.color-primary {
color: $primary-color;
}
.color-secondary {
color: $secondary-color;
}
.color-accent {
color: $accent-color;
}
.react-select.theme {
.selected-option {
.circle {
background: $primary-color;
border-radius: 10px;
}
}
}
}

View file

@ -1,3 +1,3 @@
$primary-color: lighten(#606850, 20%);
$secondary-color: darken($primary-color, 15%);
$secondary-color: darken($primary-color, 10%);
$accent-color: #FFF;

View file

@ -1,4 +1,4 @@
.root .content.theme.green {
.root .surah-index.content.theme.green {
@import "breakpoints";
@import "themes/green/colors";
@ -11,6 +11,9 @@
&:hover {
background: $primary-color;
color: $accent-color;
span:first-child {
color: $accent-color;
}
}
}
}
@ -23,7 +26,6 @@
a {
&:active, &:link, &:visited {
color: $primary-color;
text-decoration: none;
}
&:hover {
font-weight: bold;

View file

@ -1,5 +1,6 @@
.root .content.theme.green {
@import "themes/green/colors";
@import "breakpoints";
header {
h1, h1 a { color: $primary-color; }

5238
src/css/vendor/tail.scss vendored Normal file

File diff suppressed because one or more lines are too long

Binary file not shown.

View file

@ -29,7 +29,7 @@
<span class="percentage">Loading...</span>
</div>
</div>
<div class="root" data-locale="<%= context.locale %>"></div>
<div class="root h-full" data-locale="<%= context.locale %>"></div>
<%= inline_json("/i18n.json") %>
<%= inline_json("/surahs.json") %>
<script src="/js/loaders/surah-index-loader.js"></script>

View file

@ -151,7 +151,7 @@ export function RefreshIcon({ onClick }: Props) {
export function StalledIcon() {
return (
<div className="stalled icon">
<div className="stalled icon flex justify-end w-16">
<div />
<div />
<div />

View file

@ -31,7 +31,7 @@ export function Select({ value, children, onChange, className }: Props) {
return (
<div className={classnames("react-select", className)}>
<span
className={classnames("selected-option", selectedOption.props.value)}
className="selected-option"
onClick={e => [e.stopPropagation(), setOpen(true)]}
>
{selectedOption.props.children}

View file

@ -29,7 +29,10 @@ export function Stream({
return (
<ul
lang={locale}
className={classNames("body", "stream", ...className)}
className={classNames(
"body stream scroll-y list-none p-0 h-5/6",
...className,
)}
ref={ref}
>
{stream.map((ayah: Quran.Ayah) => {
@ -51,7 +54,7 @@ export function Stream({
{formatNumber(surah.ayat.length, locale)}
</span>
</span>
<p>{ayah.text}</p>
<p className="m-0 mb-3">{ayah.text}</p>
</li>
);
})}

View file

@ -28,7 +28,7 @@ export function SurahIndexFilter({ t, locale, setIndex, surahs }: Props) {
return (
<input
className="surah-index-filter"
className="p-3 h-4 surah-index-filter"
type="text"
placeholder={t(locale, "filter")}
onChange={onInput}

View file

@ -52,7 +52,7 @@ export function Timer({
}, [soundOn, isStalled, isPaused, ms]);
return (
<div className="timer">
<div className="timer w-10 flex justify-end">
{ms / 1000 <= 0
? formatNumber(0, locale)
: formatNumber(ms / 1000, locale)}

View file

@ -11,7 +11,6 @@ import postman, { item } from "postman";
item.css("/css/surah-index.css"),
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
item.font("Mada Regular", "url(/fonts/mada-regular.ttf"),
item.font("Amiri Quran Regular", "url(/fonts/amiri-quran-regular.ttf"),
item.progress((percent: number) => {
progressBar.value = percent;
progressNumber.innerText = `${percent.toFixed(0)}%`;

View file

@ -16,7 +16,6 @@ import * as Quran from "lib/Quran";
item.script("/js/surah-stream.js"),
item.css("/css/surah-stream.css"),
item.font("Kanit Regular", "url(/fonts/kanit-regular.ttf)"),
item.font("Amiri Quran Regular", "url(/fonts/amiri-quran-regular.ttf"),
item.json(`/${locale}/${surahId}/surah.json`, { className: "surah" }),
...recitations.map((recitation: Quran.Recitation) => {
const path = ["/durations", `${recitation.id}`, `${surahId}.json`].join(

View file

@ -31,34 +31,56 @@ function SurahIndex({ locale, surahs, t }: Props) {
return (
<div
ref={ref}
className={classNames("invisible", "content", "theme", theme, locale)}
className={classNames(
"flex flex-col invisible h-full content surah-index theme",
theme,
locale,
)}
>
<header>
<h1>
<a href={`/${locale}/`}>{t(locale, "TheNobleQuran")}</a>
<header
className={classNames("flex flex-col", {
"h-20": locale !== "ar",
"h-22": locale === "ar",
})}
>
<h1 className="flex justify-center p-0 mt-2">
<a className="no-underline" href={`/${locale}/`}>
{t(locale, "TheNobleQuran")}
</a>
</h1>
<nav>
<nav className="flex flex-row justify-between">
<LanguageSelect locale={locale} />
<ThemeSelect theme={theme} setTheme={setTheme} />
</nav>
</header>
<ul className="body index scroll-y">
<ul className="body index scroll-y list-none p-0 h-5/6">
{index.map((surah, key) => (
<li className="surah" key={key}>
<a href={`/${locale}/${surah.slug}/`}>
<span className="id">{formatNumber(surah.id, locale)}</span>
<span className="name">{surah.localizedName}</span>
<span className="transliterated" lang="en">
<a
className="flex items-center h-10 color-primary no-underline"
href={`/${locale}/${surah.slug}/`}
>
<span className="color-secondary font-extrabold w-10 text-center">
{formatNumber(surah.id, locale)}
</span>
<span>{surah.localizedName}</span>
<span
className="flex justify-end grow pr-3 transliterated"
lang="en"
>
{surah.transliteratedName}
</span>
</a>
</li>
))}
</ul>
<footer>
<a href={`/${locale}/random/`}>
<footer className="flex flex-row justify-between h-16">
<a
className="flex flex-row items-center no-underline"
href={`/${locale}/random/`}
>
<RightArrow />
<span>{t(locale, "ChooseRandomChapter")}</span>
<span className="pl-3">{t(locale, "ChooseRandomChapter")}</span>
</a>
<SurahIndexFilter
t={t}

View file

@ -56,18 +56,29 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
return (
<article
ref={ref}
className={classNames("invisible", "content", "theme", theme, locale)}
className={classNames(
"flex flex-col invisible h-full content theme",
locale,
theme,
)}
>
{readyToRender && (
<header>
<h1>
<a href={`/${locale}/`}>{t(locale, "TheNobleQuran")}</a>
<header
className={classNames("flex flex-col", {
"h-24": locale !== "ar",
"h-26": locale === "ar",
})}
>
<h1 className="flex justify-center p-0 mt-2">
<a className="no-underline color-primary" href={`/${locale}/`}>
{t(locale, "TheNobleQuran")}
</a>
</h1>
<nav>
<nav className="flex flex-row justify-between">
<LanguageSelect locale={locale} path={surah.slug} />
<ThemeSelect theme={theme} setTheme={setTheme} />
</nav>
<div>
<div className="flex justify-between surah-name">
<span className="localized-name" lang={locale}>
{surah.localizedName}
</span>
@ -88,7 +99,7 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
t={t}
/>
)}
<footer>
<footer className="flex justify-between items-center h-16">
{readyToRender && isPaused && !endOfStream && (
<PlayIcon onClick={() => setIsPaused(false)} />
)}
@ -96,17 +107,19 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
<PauseIcon onClick={() => setIsPaused(true)} />
)}
{readyToRender && !endOfStream && (
<AudioControl
recitation={recitation}
surah={surah}
ayah={ayah}
onPlay={() => setSoundOn(true)}
onPause={() => setSoundOn(false)}
onPlaying={() => setIsStalled(false)}
onStall={() => setIsStalled(true)}
/>
<div className="flex w-14 justify-end">
<AudioControl
recitation={recitation}
surah={surah}
ayah={ayah}
onPlay={() => setSoundOn(true)}
onPause={() => setSoundOn(false)}
onPlaying={() => setIsStalled(false)}
onStall={() => setIsStalled(true)}
/>
</div>
)}
{readyToRender && !endOfStream && (
{readyToRender && !endOfStream && !isStalled && (
<Timer
surah={surah}
setStream={setStream}
@ -118,11 +131,10 @@ function SurahStream({ node, recitations, locale, paused, t }: Props) {
isStalled={isStalled}
/>
)}
{readyToRender && soundOn && isStalled && <StalledIcon />}
{readyToRender && endOfStream && (
<RefreshIcon onClick={() => setStream([])} />
)}
<div className="br" />
{readyToRender && soundOn && isStalled && <StalledIcon />}
</footer>
</article>
);