diff --git a/client/index.html b/client/index.html index 4330dc3..88b6f29 100644 --- a/client/index.html +++ b/client/index.html @@ -1,16 +1,51 @@ + + + + Readable + - - - - - Web App Template - + + - -
- - + - \ No newline at end of file + + + + + + + + + + + + + + + + + + + +
+ + + diff --git a/client/public/favicon.ico b/client/public/favicon.ico new file mode 100644 index 0000000..7795e88 Binary files /dev/null and b/client/public/favicon.ico differ diff --git a/client/public/favicon.svg b/client/public/favicon.svg new file mode 100644 index 0000000..d272870 --- /dev/null +++ b/client/public/favicon.svg @@ -0,0 +1,10 @@ + + + favicon + + + + + + + \ No newline at end of file diff --git a/client/src/components/pdf/PdfActivityCard.tsx b/client/src/components/pdf/PdfActivityCard.tsx index 1e4f059..a2a8739 100644 --- a/client/src/components/pdf/PdfActivityCard.tsx +++ b/client/src/components/pdf/PdfActivityCard.tsx @@ -96,7 +96,7 @@ export function PdfActivityCard({
- + {file.status} {recentlyCompletedByFileId[file.fileId] ? ( @@ -146,7 +146,7 @@ export function PdfActivityCard({ {typeof fixedIssues === 'number' && fixedIssues > 0 ? ( - Fixed {fixedIssues} + Fixed {fixedIssues} Issues ) : null}
diff --git a/client/src/main.css b/client/src/main.css index 5399189..315afd0 100644 --- a/client/src/main.css +++ b/client/src/main.css @@ -10,17 +10,17 @@ --font-proxima-bold: 'proxima-nova-b', sans-serif; - --color-primary-color: var(--color-ucd-gunrock); - --color-secondary-color: var(--color-ucd-goldenstate); - --color-accent-color: var(--color-ucd-goldenstate); - --color-error-color: var(--color-ucd-doubledecker); - --color-info-color: var(--color-ucd-arboretum); - --color-success-color: var(--color-ucd-sage); - --color-warning-color: var(--color-ucd-poppy); + --color-primary-color: #022851; + --color-secondary-color: #ffbf00; + --color-accent-color: var(--color-ucd-sunflower); + --color-error-color: var(--color-ucd-merlot); + --color-info-color: var(--color-ucd-bodega); + --color-success-color: var(--color-ucd-redwood); + --color-warning-color: var(--color-ucd-california); } @plugin "daisyui/theme" { - name: 'walter'; + name: 'readable'; --color-base-100: var(--color-light-bg-100); --color-base-200: var(--color-light-bg-200); --color-base-300: var(--color-light-bg-300); @@ -28,15 +28,20 @@ --color-primary: var(--color-primary-color); --color-primary-content: var(--color-light-font); - --color-secondary: var(--color-secondary-color); --color-accent: var(--color-accent-color); --color-error: var(--color-error-color); + --color-error-content: var(--color-light-font); --color-info: var(--color-info-color); + --color-info-content: var(--color-light-font); --color-success: var(--color-success-color); + --color-success-content: var(--color-light-font); --color-warning: var(--color-warning-color); + --color-warning-content: var(--color-light-font); } -@layer base { + +html { + background-color: var(--color-light-bg-200); } .container { diff --git a/client/src/routeTree.gen.ts b/client/src/routeTree.gen.ts index cfb4fb9..758a2b7 100644 --- a/client/src/routeTree.gen.ts +++ b/client/src/routeTree.gen.ts @@ -9,14 +9,14 @@ // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified. import { Route as rootRouteImport } from './routes/__root' -import { Route as AboutRouteImport } from './routes/about' +import { Route as FAQsRouteImport } from './routes/FAQs' import { Route as authenticatedRouteRouteImport } from './routes/(authenticated)/route' import { Route as authenticatedIndexRouteImport } from './routes/(authenticated)/index' import { Route as authenticatedReportsFileIdIndexRouteImport } from './routes/(authenticated)/reports/$fileId/index' -const AboutRoute = AboutRouteImport.update({ - id: '/about', - path: '/about', +const FAQsRoute = FAQsRouteImport.update({ + id: '/FAQs', + path: '/FAQs', getParentRoute: () => rootRouteImport, } as any) const authenticatedRouteRoute = authenticatedRouteRouteImport.update({ @@ -36,47 +36,47 @@ const authenticatedReportsFileIdIndexRoute = } as any) export interface FileRoutesByFullPath { - '/about': typeof AboutRoute + '/FAQs': typeof FAQsRoute '/': typeof authenticatedIndexRoute '/reports/$fileId': typeof authenticatedReportsFileIdIndexRoute } export interface FileRoutesByTo { - '/about': typeof AboutRoute + '/FAQs': typeof FAQsRoute '/': typeof authenticatedIndexRoute '/reports/$fileId': typeof authenticatedReportsFileIdIndexRoute } export interface FileRoutesById { __root__: typeof rootRouteImport '/(authenticated)': typeof authenticatedRouteRouteWithChildren - '/about': typeof AboutRoute + '/FAQs': typeof FAQsRoute '/(authenticated)/': typeof authenticatedIndexRoute '/(authenticated)/reports/$fileId/': typeof authenticatedReportsFileIdIndexRoute } export interface FileRouteTypes { fileRoutesByFullPath: FileRoutesByFullPath - fullPaths: '/about' | '/' | '/reports/$fileId' + fullPaths: '/FAQs' | '/' | '/reports/$fileId' fileRoutesByTo: FileRoutesByTo - to: '/about' | '/' | '/reports/$fileId' + to: '/FAQs' | '/' | '/reports/$fileId' id: | '__root__' | '/(authenticated)' - | '/about' + | '/FAQs' | '/(authenticated)/' | '/(authenticated)/reports/$fileId/' fileRoutesById: FileRoutesById } export interface RootRouteChildren { authenticatedRouteRoute: typeof authenticatedRouteRouteWithChildren - AboutRoute: typeof AboutRoute + FAQsRoute: typeof FAQsRoute } declare module '@tanstack/react-router' { interface FileRoutesByPath { - '/about': { - id: '/about' - path: '/about' - fullPath: '/about' - preLoaderRoute: typeof AboutRouteImport + '/FAQs': { + id: '/FAQs' + path: '/FAQs' + fullPath: '/FAQs' + preLoaderRoute: typeof FAQsRouteImport parentRoute: typeof rootRouteImport } '/(authenticated)': { @@ -118,7 +118,7 @@ const authenticatedRouteRouteWithChildren = const rootRouteChildren: RootRouteChildren = { authenticatedRouteRoute: authenticatedRouteRouteWithChildren, - AboutRoute: AboutRoute, + FAQsRoute: FAQsRoute, } export const routeTree = rootRouteImport ._addFileChildren(rootRouteChildren) diff --git a/client/src/routes/(authenticated)/index.tsx b/client/src/routes/(authenticated)/index.tsx index 2798e82..823fcdb 100644 --- a/client/src/routes/(authenticated)/index.tsx +++ b/client/src/routes/(authenticated)/index.tsx @@ -7,6 +7,7 @@ import { myFilesQueryOptions } from '@/queries/files.ts'; import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; import { useCallback, useEffect } from 'react'; +import { Link } from '@tanstack/react-router'; export const Route = createFileRoute('/(authenticated)/')({ component: RouteComponent, @@ -54,14 +55,25 @@ function RouteComponent() { return (
-
-

- Readable -

-

PDF Accessibility Conversion Tool

-
- - {/* Upload Dropzone */} +
+

Make Your PDFs Accessible

+

+ Readable helps you meet modern accessibility requirements by + transforming standard PDFs into documents that are more compliant with{' '} + + WCAG and PDF/UA guidelines + + . +

+ + More Info + +
-
-
-
-

- Accessibility report +
+
+
+

+ Accessibility report for {file.originalFileName}

-
-
- {file.status} - {file.originalFileName} -
-
- Updated {formatDateTime(file.statusUpdatedAt)} -
+
+ {file.status} • + Updated {formatDateTime(file.statusUpdatedAt)} +
+
+
+
+ {isCompleted ? ( + + + Download PDF + + ) : ( + + )}
- - - Back -
{!isCompleted ? ( @@ -331,260 +347,235 @@ function RouteComponent() { ) : null}
-
- {isCompleted ? ( - - - Download PDF - - ) : ( - - )} -
- {beforeReport && afterReport && beforeCounts && afterCounts ? (
-
+
-
Before
-
+
Before
+
{beforeCounts.passed}/{beforeCounts.total}
-
+
{beforeCounts.failed} failed • {beforeCounts.needsManual} needs manual
-
+
Generated {formatDateTime(beforeReport.generatedAt)}
-
After
-
+
Changed
+
+ {afterCounts.passed - beforeCounts.passed >= 0 ? '+' : ''} + {afterCounts.passed - beforeCounts.passed} +
+
passed checks
+
+ +
+
After
+
{afterCounts.passed}/{afterCounts.total}
-
+
{afterCounts.failed} failed • {afterCounts.needsManual} needs manual
-
+
Generated {formatDateTime(afterReport.generatedAt)}
- -
-
Improvement
-
- {afterCounts.passed - beforeCounts.passed >= 0 ? '+' : ''} - {afterCounts.passed - beforeCounts.passed} -
-
passed checks
-
-
-
-
-

Still failing (After)

- {afterFailed.length === 0 ? ( -
- No failed checks found in the After report. -
- ) : ( -
- - - - - - - - - - {afterFailed.map((r, idx) => ( - - - - - - ))} - -
CategoryRuleStatus
- {r.category} - -
{r.rule}
- {r.description ? ( -
- {r.description} -
- ) : null} -
- - {r.status} - -
-
- )} +
+

Still failing (After)

+ {afterFailed.length === 0 ? ( +
+ + No failed checks found in the After report. +
-
- -
-
-

Needs manual check (After)

- {afterNeedsManual.length === 0 ? ( -
- - No “Needs manual check” items found in the After report. - -
- ) : ( -
- - - - - - - - - - {afterNeedsManual.map((r, idx) => ( - - - - - - ))} - -
CategoryRuleStatus
- {r.category} - -
{r.rule}
- {r.description ? ( -
- {r.description} -
- ) : null} -
- - {r.status} - -
-
- )} + ) : ( +
+ + + + + + + + + + + + + + + {afterFailed.map((r, idx) => ( + + + + + + ))} + +
CategoryRuleStatus
{r.category} +
{r.rule}
+ {r.description ? ( +
+ {r.description} +
+ ) : null} +
+ + {r.status} + +
-
+ )} + +

Needs manual check

+ {afterNeedsManual.length === 0 ? ( +
+ + No “Needs manual check” items found in the After report. + +
+ ) : ( +
+ + + + + + + + + + + + + + + {afterNeedsManual.map((r, idx) => ( + + + + + + ))} + +
CategoryRuleStatus
{r.category} +
{r.rule}
+ {r.description ? ( +
+ {r.description} +
+ ) : null} +
+ + {r.status} + +
+
+ )}
-
+

Before vs After breakdown

-
- {compareByCategory.map((c) => ( -
- -
- {c.category} - {c.afterFailedCount > 0 ? ( - - {c.afterFailedCount} failed - - ) : ( - - all passed - - )} - {c.afterNeedsManualCount > 0 ? ( - - {c.afterNeedsManualCount} manual - - ) : null} -
-
-
- - - - - - + + {compareByCategory.map((c) => ( +
+ +
+ {c.category} + {c.afterFailedCount > 0 ? ( + + {c.afterFailedCount} failed + + ) : ( + + all passed + + )} + {c.afterNeedsManualCount > 0 ? ( + + {c.afterNeedsManualCount} manual + + ) : null} +
+
+
+
RuleBeforeAfter
+ + + + + + + + + {c.rows.map((r) => ( + + + + - - - {c.rows.map((r) => ( - - - - - - ))} - -
RuleBeforeAfter
+
{r.rule}
+ {r.description ? ( +
+ {r.description} +
+ ) : null} +
+ {r.beforeStatus ? ( + + {r.beforeStatus} + + ) : ( + + — + + )} + + {r.afterStatus ? ( + + {r.afterStatus} + + ) : ( + + — + + )} +
-
{r.rule}
- {r.description ? ( -
- {r.description} -
- ) : null} -
- {r.beforeStatus ? ( - - {r.beforeStatus} - - ) : ( - - — - - )} - - {r.afterStatus ? ( - - {r.afterStatus} - - ) : ( - - — - - )} -
-
+ ))} + +
- ))} -
+
+ ))}
diff --git a/client/src/routes/FAQs.tsx b/client/src/routes/FAQs.tsx new file mode 100644 index 0000000..3510274 --- /dev/null +++ b/client/src/routes/FAQs.tsx @@ -0,0 +1,192 @@ +import { createFileRoute } from '@tanstack/react-router'; + +export const Route = createFileRoute('/FAQs')({ + component: FAQs, +}); + +function FAQs() { + return ( +
+
+

FAQ placeholder

+

+ Most PDFs are visually readable but structurally inaccessible. They + lack proper headings, document structure, reading order, and + descriptive text for images and links—features required by + accessibility regulations and essential for screen readers and + assistive technologies. +

+ +

+ Readable automatically analyzes your PDF and: +

+ +
    +
  • Detects and adds document structure and hierarchy
  • +
  • Identifies headings, lists, tables, and reading order
  • +
  • + Generates clear, context-aware descriptions for images and links + using AI +
  • +
  • Flags remaining accessibility issues in a detailed report
  • +
+ +

+ The result is a PDF that is dramatically closer to full accessibility + compliance—saving time, reducing manual remediation effort, and + helping ensure your documents are usable by everyone. +

+ +
+ +
+

+ About Readable +

+ +
+

+ Why Accessibility Matters +

+ +

+ Accessibility regulations such as WCAG, Section 508, and PDF/UA + require digital documents to be usable by people with + disabilities. While many PDFs look correct visually, most fail + accessibility checks because they lack: +

+ +
    +
  • Semantic structure (headings, sections, lists)
  • +
  • Logical reading order
  • +
  • Text alternatives for images and graphics
  • +
  • Meaningful descriptions for links and interactive content
  • +
+ +

+ Without these elements, screen readers cannot interpret the + document correctly—making it inaccessible to many users. +

+
+ +
+ +
+

+ How Readable Works +

+ +

+ Readable bridges the gap between visually correct PDFs and truly + accessible documents. When you upload a PDF, Readable + automatically: +

+ +
    +
  1. +
    + Analyzes the document structure +
    +

    + It detects headings, paragraphs, lists, tables, and layout + patterns to establish a logical hierarchy and reading order. +

    +
  2. + +
  3. +
    + Adds accessibility metadata +
    +

    + The PDF is enhanced with structural tags, titles, and + navigation elements required by accessibility standards. +

    +
  4. + +
  5. +
    + Generates intelligent descriptions using AI +
    +

    + Readable uses AI to create useful, context-aware descriptions + for: +

    +
      +
    • Images and figures
    • +
    • Links and references
    • +
    • Complex content blocks
    • +
    +

    + These descriptions are designed to be helpful—not generic—so + assistive technology users get meaningful information. +

    +
  6. + +
  7. +
    + Produces an accessibility report +
    +

    + After processing, Readable provides a report highlighting: +

    +
      +
    • What was automatically fixed
    • +
    • Remaining issues that may require manual review
    • +
    • + Recommendations for achieving higher compliance levels +
    • +
    +
  8. +
+
+ +
+ +
+

+ What You Get +

+ +
    +
  • Significantly improved WCAG and PDF/UA compliance
  • +
  • Dramatically reduced remediation time
  • +
  • Clear visibility into remaining accessibility gaps
  • +
  • A practical balance of automation and human review
  • +
+ +

+ Readable doesn’t claim to replace human judgment entirely—but it + does eliminate the most time-consuming and technical parts of PDF + accessibility work. +

+
+ +
+ +
+

+ Who Readable Is For +

+ +

Readable is designed for:

+ +
    +
  • Universities and research institutions
  • +
  • Government and public-sector organizations
  • +
  • Companies publishing reports, manuals, or forms
  • +
  • + Teams responsible for compliance, accessibility, or digital + content +
  • +
+ +

+ If you produce PDFs, Readable helps ensure they’re usable by + everyone. +

+
+
+
+
+ ); +} diff --git a/client/src/routes/__root.tsx b/client/src/routes/__root.tsx index 90f881e..a8171c8 100644 --- a/client/src/routes/__root.tsx +++ b/client/src/routes/__root.tsx @@ -1,4 +1,8 @@ -import { createRootRouteWithContext, Outlet } from '@tanstack/react-router'; +import { + createRootRouteWithContext, + Link, + Outlet, +} from '@tanstack/react-router'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { TanStackRouterDevtools } from '@tanstack/react-router-devtools'; import { RouterContext } from '../main.tsx'; @@ -7,6 +11,20 @@ import Footer from '@/components/pdf/Footer.tsx'; const RootLayout = () => (
+
+
+ +

Readable

+

+ PDF Accessibility Conversion Tool +

+ + FAQs +
+
diff --git a/client/src/routes/about.tsx b/client/src/routes/about.tsx deleted file mode 100644 index ff9bd01..0000000 --- a/client/src/routes/about.tsx +++ /dev/null @@ -1,60 +0,0 @@ -import { createFileRoute, Link } from '@tanstack/react-router'; - -export const Route = createFileRoute('/about')({ - component: About, -}); - -function About() { - return ( -
- {/* Homepage Link */} -
- - - - - Home - -
- - {/* Floating Elements */} -
-
-
-
- - {/* Main Content */} -
-
- {/* Hero Section */} -
-

- 🚀 About Us -

-

- UC Davis is the place to be for cutting-edge web development. -

-
-
-
- - {/* Footer */} - -
- ); -}