Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions app/components/ReadingTime.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
'use client';

import React, { useEffect, useState } from 'react';

interface ReadingTimeProps {
/**
* Optional key to force recalculation when routes or chapters change.
*/
chapterKey?: string;
}

export default function ReadingTime({ chapterKey }: ReadingTimeProps) {
const [readingTime, setReadingTime] = useState<number | null>(null);

useEffect(() => {
const calculateReadingTime = () => {
// Look for the specific content area first, then fallbacks
const contentElement =
document.getElementById('reading-content') ||
document.querySelector('.course-content') ||
document.querySelector('main');

if (contentElement) {
// Retrieve pure text content
const text = contentElement.textContent || '';
// Extract words using whitespace regex matcher
const words = text.trim().split(/\s+/).filter((w) => w.length > 0).length;
// Average reading speed: 200 words per minute, rounded up
const time = Math.ceil(words / 200);
setReadingTime(time || 1); // Minimum 1 min read
} else {
setReadingTime(null);
}
};

// Delay slightly to ensure React has fully rendered and painted the child components to the DOM
const timer = setTimeout(calculateReadingTime, 150);

return () => clearTimeout(timer);
}, [chapterKey]);

if (readingTime === null) {
return (
<div className="flex items-center gap-2 text-xs sm:text-sm text-[#c7a669] mt-3 font-medium bg-[#2c1a04]/40 px-3.5 py-1.5 rounded-full w-max border border-[#c7a669]/10 animate-pulse select-none">
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className="w-4 h-4 text-[#c7a669]/50 animate-spin"
>
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
<span className="opacity-70">Calculating reading time...</span>
</div>
);
}

return (
<>
<style>{`
@keyframes clock-hand-spin {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.reading-time-badge:hover .reading-time-clock-hand {
animation: clock-hand-spin 1.8s infinite linear;
}
`}</style>
<div className="reading-time-badge group flex items-center gap-2.5 text-xs sm:text-sm md:text-base text-[#c7a669] mt-3 font-medium bg-[#2c1a04]/60 backdrop-blur-xs px-4 py-2 rounded-full w-max border border-[#c7a669]/30 shadow-md select-none transition-all duration-300 hover:border-[#c7a669]/70 hover:bg-[#3d2506]/80 hover:text-[#FAE8D7] hover:-translate-y-[1px]">
{/* Sleek SVG Clock with animated rotating hands on hover */}
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className="w-4.5 h-4.5 text-[#c7a669] transition-transform duration-300 group-hover:scale-105 group-hover:text-[#FAE8D7]"
>
<circle cx="12" cy="12" r="10" />
<polyline
points="12 6 12 12 15 14"
className="reading-time-clock-hand origin-center"
style={{ transformOrigin: '12px 12px', transition: 'transform 0.5s ease' }}
/>
</svg>
<span className="font-semibold tracking-wide" style={{ fontFamily: 'Rockwell, Serif, serif' }}>
{readingTime} min read
</span>
</div>
</>
);
}
22 changes: 13 additions & 9 deletions app/sem1/c/[chapter]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { moduleQuizzes } from "@/lib/quizData";
import ChapterQuizInline from "../components/ChapterQuizInline";
import { ArrowBigLeft, ArrowBigRight } from "lucide-react";
import { Righteous } from "next/font/google";
import ReadingTime from "@/app/components/ReadingTime";
import BookmarkButton from "../../../components/BookmarkButton";
const righteous = Righteous({
subsets: ['latin'],
Expand All @@ -34,7 +35,7 @@ type ChapterProps = {

export default async function ChapterPage({ params }: ChapterProps) {
const { chapter: chapterId } = await params;
const currentIndex = chapters.findIndex((c) => c.id === chapterId);
const currentIndex = chapters.findIndex((c) => c.id === chapterId);
const chapter = chapters[currentIndex];

if (!chapter) {
Expand All @@ -54,7 +55,7 @@ export default async function ChapterPage({ params }: ChapterProps) {
ch6: "c-file-memory-preprocessors",
};

const chapterQuiz = moduleQuizzes.find(async (quiz) => quiz.slug === chapterQuizSlugMap[(await params).chapter]);
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[chapterId]);

return (
<div className="flex flex-col bg-[#1B0D00] min-h-full p-2 pt-6 text-[#e2d1c1]">
Expand All @@ -63,12 +64,13 @@ export default async function ChapterPage({ params }: ChapterProps) {
<h1 className={`text-4xl font-bold ${righteous.className} mb-2`}>
Programming in C
</h1>
<div className="flex items-center justify-between">
<p className={`text-2xl mt-[-8px] ${righteous.className}`}>
{chapter.title}
</p>
<BookmarkButton title={`C Programming : ${chapter.title}`} />
</div>
<div className="flex items-center justify-between">
<p className={`text-2xl mt-[-8px] ${righteous.className}`}>
{chapter.title}
</p>
<BookmarkButton title={`C Programming : ${chapter.title}`} />
</div>
<ReadingTime chapterKey={chapter.id} />

{/* Navigation Buttons */}
<div className="flex justify-between mt-3">
Expand Down Expand Up @@ -98,7 +100,9 @@ export default async function ChapterPage({ params }: ChapterProps) {
</div>

<hr className="my-6 border-t-3" />
<ChapterComponent />
<div id="reading-content">
<ChapterComponent />
</div>

{chapterQuiz ? (
<div className="mt-12">
Expand Down
15 changes: 10 additions & 5 deletions app/sem1/em1/[chapter]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Righteous } from "next/font/google";
import BookmarkButton from "../../../components/BookmarkButton";
import { moduleQuizzes } from "@/lib/quizData";
import ChapterQuizInline from "../components/ChapterQuizInline";
import ReadingTime from "@/app/components/ReadingTime";
const righteous = Righteous({
subsets: ["latin"],
weight: "400",
Expand All @@ -26,11 +27,12 @@ const chapters = [
];

type ChapterProps = {
params: { chapter: string };
params: Promise<{ chapter: string }>;
};

export default function ChapterPage({ params }: ChapterProps) {
const currentIndex = chapters.findIndex((c) => c.id === params.chapter);
export default async function ChapterPage({ params }: ChapterProps) {
const { chapter: chapterId } = await params;
const currentIndex = chapters.findIndex((c) => c.id === chapterId);
const chapter = chapters[currentIndex];

if (!chapter) {
Expand All @@ -47,7 +49,7 @@ export default function ChapterPage({ params }: ChapterProps) {
ch3: "em1-ordinary-differential-equations",
ch4: "em1-laplace-transforms",
};
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[params.chapter]);
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[chapterId]);

return (
<div className="flex flex-col bg-[#1B0D00] min-h-full p-2 pt-6 text-[#e2d1c1]">
Expand All @@ -63,6 +65,7 @@ export default function ChapterPage({ params }: ChapterProps) {
</p>
<BookmarkButton title={`Em1: ${chapter.title}`} />
</div>
<ReadingTime chapterKey={chapter.id} />

{/* Top Navigation */}
<div className="flex justify-between mt-4">
Expand Down Expand Up @@ -92,7 +95,9 @@ export default function ChapterPage({ params }: ChapterProps) {
<hr className="my-6 border-t border-[#c7a669] opacity-40" />

{/* Chapter Body */}
<ChapterComponent />
<div id="reading-content">
<ChapterComponent />
</div>

{chapterQuiz ? (
<div className="mt-12">
Expand Down
32 changes: 19 additions & 13 deletions app/sem1/ep/[chapter]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import { Ch5Content } from "../content/chapter5";
import ChapterQuizInline from "../components/ChapterQuizInline";
import { ArrowBigLeft, ArrowBigRight } from "lucide-react";
import { Righteous } from "next/font/google";
import { moduleQuizzes } from "@/lib/quizData";
import ReadingTime from "@/app/components/ReadingTime";

import BookmarkButton from "../../../components/BookmarkButton";

Expand All @@ -29,11 +31,12 @@ const chapters = [
];

type ChapterProps = {
params: { chapter: string };
params: Promise<{ chapter: string }>;
};

export default function ChapterPage({ params }: ChapterProps) {
const currentIndex = chapters.findIndex((c) => c.id === params.chapter);
export default async function ChapterPage({ params }: ChapterProps) {
const { chapter: chapterId } = await params;
const currentIndex = chapters.findIndex((c) => c.id === chapterId);
const chapter = chapters[currentIndex];

if (!chapter) {
Expand All @@ -44,15 +47,15 @@ export default function ChapterPage({ params }: ChapterProps) {
const prevChapter = currentIndex > 0 ? chapters[currentIndex - 1] : null;
const nextChapter = currentIndex < chapters.length - 1 ? chapters[currentIndex + 1] : null;

const chapterQuizSlugMap: Record<string, string> = {
ch1: "ep-vector-fields",
ch2: "ep-electrostatics-magnetostatics",
ch3: "ep-electrodynamics-maxwell",
ch4: "ep-superconductivity",
ch5: "ep-laser-fibre-optics",
};
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[params.chapter]);
const chapterQuizSlugMap: Record<string, string> = {
ch1: "ep-vector-fields",
ch2: "ep-electrostatics-magnetostatics",
ch3: "ep-electrodynamics-maxwell",
ch4: "ep-superconductivity",
ch5: "ep-laser-fibre-optics",
};

const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[chapterId]);

return (
<div className="flex flex-col bg-[#1B0D00] min-h-full p-2 pt-6 text-[#e2d1c1]">
Expand All @@ -67,6 +70,7 @@ export default function ChapterPage({ params }: ChapterProps) {
</p>
<BookmarkButton title={`EP: ${chapter.title}`} />
</div>
<ReadingTime chapterKey={chapter.id} />
{/* Navigation Buttons */}
<div className="flex justify-between mt-3">
{prevChapter ? (
Expand Down Expand Up @@ -95,7 +99,9 @@ export default function ChapterPage({ params }: ChapterProps) {
</div>

<hr className="my-6 border-t-3" />
<ChapterComponent />
<div id="reading-content">
<ChapterComponent />
</div>

{chapterQuiz ? (
<div className="mt-12">
Expand Down
15 changes: 10 additions & 5 deletions app/sem2/dsc/[chapter]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Ch4Content } from "../content/chapter4";
import { ArrowBigLeft, ArrowBigRight } from "lucide-react";
import { moduleQuizzes } from "@/lib/quizData";
import ChapterQuizInline from "../components/ChapterQuizInline";
import ReadingTime from "@/app/components/ReadingTime";

const righteous = Righteous({
subsets: ["latin"],
Expand All @@ -27,11 +28,12 @@ const chapters = [
];

type ChapterProps = {
params: { chapter: string };
params: Promise<{ chapter: string }>;
};

export default function ChapterPage({ params }: ChapterProps) {
const currentIndex = chapters.findIndex((c) => c.id === params.chapter);
export default async function ChapterPage({ params }: ChapterProps) {
const { chapter: chapterId } = await params;
const currentIndex = chapters.findIndex((c) => c.id === chapterId);
const chapter = chapters[currentIndex];

if (!chapter) {
Expand All @@ -46,7 +48,7 @@ export default function ChapterPage({ params }: ChapterProps) {
const chapterQuizSlugMap: Record<string, string> = {
ch1: "dsc-arrays",
};
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[params.chapter]);
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[chapterId]);

return (
<div className="flex flex-col bg-[#1B0D00] min-h-full p-2 pt-16 text-[#e2d1c1]">
Expand All @@ -62,6 +64,7 @@ export default function ChapterPage({ params }: ChapterProps) {
</p>
<BookmarkButton title={`DSC: ${chapter.title}`} />
</div>
<ReadingTime chapterKey={chapter.id} />

{/* Navigation */}
<div className="flex justify-between mt-3">
Expand Down Expand Up @@ -91,7 +94,9 @@ export default function ChapterPage({ params }: ChapterProps) {
</div>

<hr className="my-6 border-t-3" />
<ChapterComponent />
<div id="reading-content">
<ChapterComponent />
</div>

{chapterQuiz ? (
<div className="mt-12">
Expand Down
27 changes: 16 additions & 11 deletions app/sem2/em2/[chapter]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import BookmarkButton from "../../../components/BookmarkButton";

import { moduleQuizzes } from "@/lib/quizData";
import ChapterQuizInline from "../components/ChapterQuizInline";
import ReadingTime from "@/app/components/ReadingTime";

const righteous = Righteous({
subsets: ["latin"],
Expand All @@ -26,11 +27,12 @@ const chapters = [
];

type ChapterProps = {
params: { chapter: string };
params: Promise<{ chapter: string }>;
};

export default function ChapterPage({ params }: ChapterProps) {
const currentIndex = chapters.findIndex((c) => c.id === params.chapter);
export default async function ChapterPage({ params }: ChapterProps) {
const { chapter: chapterId } = await params;
const currentIndex = chapters.findIndex((c) => c.id === chapterId);
const chapter = chapters[currentIndex];

if (!chapter) {
Expand All @@ -48,7 +50,7 @@ export default function ChapterPage({ params }: ChapterProps) {
ch3: "em2-complex-variables",
ch4: "em2-integral-calculus",
};
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[params.chapter]);
const chapterQuiz = moduleQuizzes.find((quiz) => quiz.slug === chapterQuizSlugMap[chapterId]);

return (
<div className="flex flex-col bg-[#1B0D00] min-h-full p-2 pt-6 text-[#e2d1c1]">
Expand All @@ -57,12 +59,13 @@ export default function ChapterPage({ params }: ChapterProps) {
Engineering Mathematics II
</h1>

<div className="flex items-center justify-between">
<p className={`text-2xl mt-[-8px] ${righteous.className}`}>
{chapter.title}
</p>
<BookmarkButton title={`DSC: ${chapter.title}`} />
</div>
<div className="flex items-center justify-between">
<p className={`text-2xl mt-[-8px] ${righteous.className}`}>
{chapter.title}
</p>
<BookmarkButton title={`EM2: ${chapter.title}`} />
</div>
<ReadingTime chapterKey={chapter.id} />

{/* Top Navigation */}
<div className="flex justify-between mt-4">
Expand Down Expand Up @@ -93,7 +96,9 @@ export default function ChapterPage({ params }: ChapterProps) {

<hr className="my-6 border-t border-[#c7a669] opacity-40" />

<ChapterComponent />
<div id="reading-content">
<ChapterComponent />
</div>

{chapterQuiz ? (
<div className="mt-12">
Expand Down
Loading