diff --git a/frontend/src/components/NavBarItem.tsx b/frontend/src/components/NavBarItem.tsx
index 72935e6..4ced3eb 100644
--- a/frontend/src/components/NavBarItem.tsx
+++ b/frontend/src/components/NavBarItem.tsx
@@ -1,6 +1,5 @@
-import React, {useEffect, useRef} from 'react'
+import React, {useEffect, useRef, useState} from 'react'
import Link from 'next/link'
-import { useState } from 'react'
import { mdiChevronUp, mdiChevronDown } from '@mdi/js'
import BaseDivider from './BaseDivider'
import BaseIcon from './BaseIcon'
@@ -129,4 +128,4 @@ export default function NavBarItem({ item }: Props) {
}
return
{NavBarItemComponentContents}
-}
+}
\ No newline at end of file
diff --git a/frontend/src/layouts/Authenticated.tsx b/frontend/src/layouts/Authenticated.tsx
index 1b9907d..26c3572 100644
--- a/frontend/src/layouts/Authenticated.tsx
+++ b/frontend/src/layouts/Authenticated.tsx
@@ -1,5 +1,4 @@
-import React, { ReactNode, useEffect } from 'react'
-import { useState } from 'react'
+import React, { ReactNode, useEffect, useState } from 'react'
import jwt from 'jsonwebtoken';
import { mdiForwardburger, mdiBackburger, mdiMenu } from '@mdi/js'
import menuAside from '../menuAside'
@@ -126,4 +125,4 @@ export default function LayoutAuthenticated({
)
-}
+}
\ No newline at end of file
diff --git a/frontend/src/menuAside.ts b/frontend/src/menuAside.ts
index 0ddc339..08f8dac 100644
--- a/frontend/src/menuAside.ts
+++ b/frontend/src/menuAside.ts
@@ -2,6 +2,11 @@ import * as icon from '@mdi/js';
import { MenuAsideItem } from './interfaces'
const menuAside: MenuAsideItem[] = [
+ {
+ href: '/autoscroller',
+ icon: icon.mdiSpeedometer,
+ label: 'Autoscroller',
+ },
{
href: '/dashboard',
icon: icon.mdiViewDashboardOutline,
@@ -88,4 +93,4 @@ const menuAside: MenuAsideItem[] = [
},
]
-export default menuAside
+export default menuAside
\ No newline at end of file
diff --git a/frontend/src/pages/autoscroller.tsx b/frontend/src/pages/autoscroller.tsx
new file mode 100644
index 0000000..419ff9c
--- /dev/null
+++ b/frontend/src/pages/autoscroller.tsx
@@ -0,0 +1,242 @@
+import { mdiPlay, mdiPause, mdiFullscreen, mdiArrowLeft, mdiArrowRight, mdiFormatSize, mdiSpeedometer, mdiFolderOpen } from '@mdi/js'
+import Head from 'next/head'
+import React, { ReactElement, useEffect, useRef, useState } from 'react'
+import CardBox from '../components/CardBox'
+import LayoutAuthenticated from '../layouts/Authenticated'
+import SectionMain from '../components/SectionMain'
+import SectionTitleLineWithButton from '../components/SectionTitleLineWithButton'
+import { getPageTitle } from '../config'
+import { useAppDispatch, useAppSelector } from '../stores/hooks'
+import { fetch as fetchTexts } from '../stores/texts/textsSlice'
+import { fetch as fetchFolders } from '../stores/folders/foldersSlice'
+import BaseButton from '../components/BaseButton'
+import BaseButtons from '../components/BaseButtons'
+import BaseIcon from '../components/BaseIcon'
+
+const AutoscrollerPage = () => {
+ const dispatch = useAppDispatch()
+ const texts = useAppSelector((state) => state.texts.texts)
+ const folders = useAppSelector((state) => state.folders.folders)
+ const [selectedTextId, setSelectedTextId] = useState(null)
+ const [isPlaying, setIsPlaying] = useState(false)
+ const [speed, setSpeed] = useState(2)
+ const [fontSize, setFontSize] = useState(24)
+ const scrollRef = useRef(null)
+ const requestRef = useRef()
+
+ const selectedText = texts.find((t: any) => t.id === selectedTextId)
+
+ useEffect(() => {
+ dispatch(fetchTexts({}))
+ dispatch(fetchFolders({}))
+ }, [dispatch])
+
+ const scroll = () => {
+ if (scrollRef.current && isPlaying) {
+ scrollRef.current.scrollTop += speed / 2
+ }
+ requestRef.current = requestAnimationFrame(scroll)
+ }
+
+ useEffect(() => {
+ requestRef.current = requestAnimationFrame(scroll)
+ return () => {
+ if (requestRef.current) {
+ cancelAnimationFrame(requestRef.current)
+ }
+ }
+ }, [isPlaying, speed])
+
+ const toggleFullscreen = () => {
+ if (!document.fullscreenElement) {
+ document.documentElement.requestFullscreen()
+ } else {
+ if (document.exitFullscreen) {
+ document.exitFullscreen()
+ }
+ }
+ }
+
+ const nextPage = () => {
+ if (scrollRef.current) {
+ scrollRef.current.scrollTop += scrollRef.current.clientHeight
+ }
+ }
+
+ const prevPage = () => {
+ if (scrollRef.current) {
+ scrollRef.current.scrollTop -= scrollRef.current.clientHeight
+ }
+ }
+
+ return (
+ <>
+
+ {getPageTitle('Autoscroller')}
+
+
+
+
+
+
+
+
+
+ {/* Sidebar - Title Selection */}
+
+
+
+
+
+ Choose Title
+
+ {folders.map((folder: any) => (
+
+
+ {folder.name}
+
+
+ {texts
+ .filter((t: any) => t.folderId === folder.id)
+ .map((text: any) => (
+
+ ))}
+
+
+ ))}
+ {/* Texts without folders */}
+ {texts.filter((t: any) => !t.folderId).length > 0 && (
+
+
+ Other
+
+ {texts
+ .filter((t: any) => !t.folderId)
+ .map((text: any) => (
+
+ ))}
+
+ )}
+
+
+
+
+ {/* Main Scroller Area */}
+
+ {selectedText ? (
+ <>
+
+
+
{selectedText.title}
+
+
+
+
+
+ {selectedText.content}
+
+ {/* Padding bottom to allow scrolling to the very end */}
+
+
+
+
+
+ setIsPlaying(!isPlaying)}
+ label={isPlaying ? 'Pause' : 'Play'}
+ className='w-32'
+ />
+
+
+
+ >
+ ) : (
+
+
+
+
Select a title from the menu to start scrolling
+
+
+ )}
+
+
+
+ >
+ )
+}
+
+AutoscrollerPage.getLayout = function getLayout(page: ReactElement) {
+ return {page}
+}
+
+export default AutoscrollerPage
\ No newline at end of file
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index df28ae4..a122328 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -1,166 +1,112 @@
-
import React, { useEffect, useState } from 'react';
import type { ReactElement } from 'react';
import Head from 'next/head';
-import Link from 'next/link';
import BaseButton from '../components/BaseButton';
import CardBox from '../components/CardBox';
import SectionFullScreen from '../components/SectionFullScreen';
import LayoutGuest from '../layouts/Guest';
-import BaseDivider from '../components/BaseDivider';
import BaseButtons from '../components/BaseButtons';
import { getPageTitle } from '../config';
import { useAppSelector } from '../stores/hooks';
-import CardBoxComponentTitle from "../components/CardBoxComponentTitle";
-import { getPexelsImage, getPexelsVideo } from '../helpers/pexels';
-
+import { mdiSpeedometer, mdiPlayCircleOutline } from '@mdi/js';
+import { getPexelsImage } from '../helpers/pexels';
export default function Starter() {
- const [illustrationImage, setIllustrationImage] = useState({
- src: undefined,
- photographer: undefined,
- photographer_url: undefined,
- })
- const [illustrationVideo, setIllustrationVideo] = useState({video_files: []})
- const [contentType, setContentType] = useState('image');
- const [contentPosition, setContentPosition] = useState('background');
+ const [illustrationImage, setIllustrationImage] = useState(null)
const textColor = useAppSelector((state) => state.style.linkColor);
- const title = 'Autoscroller App'
+ const title = 'Autoscroller Pro'
- // Fetch Pexels image/video
useEffect(() => {
async function fetchData() {
const image = await getPexelsImage();
- const video = await getPexelsVideo();
setIllustrationImage(image);
- setIllustrationVideo(video);
}
fetchData();
}, []);
- const imageBlock = (image) => (
-
- );
-
- const videoBlock = (video) => {
- if (video?.video_files?.length > 0) {
- return (
-
-
-
-
)
- }
- };
-
return (
-
{getPageTitle('Starter Page')}
+
{getPageTitle('Home')}
-
-
- {contentType === 'image' && contentPosition !== 'background'
- ? imageBlock(illustrationImage)
- : null}
- {contentType === 'video' && contentPosition !== 'background'
- ? videoBlock(illustrationVideo)
- : null}
-
-
-
-
-
-
-
-
+
+
+
+
+
+ Smooth Autoscrolling for Professionals
+
+
+ The ultimate tool for speakers, musicians, and readers. Organize your texts into folders, adjust speed in real-time, and read with comfort.
+
+
-
-
-
-
+
+
+
Ready to start your session?
+
+
+
+
+
+
+
+
+
+
+
+
+ {illustrationImage && (
+
+ )}
+
-
-
© 2026 {title}. All rights reserved
-
- Privacy Policy
-
-
+
);
}
Starter.getLayout = function getLayout(page: ReactElement) {
return {page};
-};
-
+};
\ No newline at end of file