mirror of
https://github.com/rishikanthc/Scriberr.git
synced 2026-06-28 06:46:25 +00:00
fix: responsive design
This commit is contained in:
committed by
Rishikanth Chandrasekaran
parent
aba5562624
commit
3bbcbcfd63
@@ -1044,6 +1044,26 @@ const docTemplate = `{
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/events": {
|
||||
"get": {
|
||||
"description": "Subscribe to server-sent events",
|
||||
"produces": [
|
||||
"text/event-stream"
|
||||
],
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "SSE Events",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "stream",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/llm/config": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
@@ -1038,6 +1038,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/events": {
|
||||
"get": {
|
||||
"description": "Subscribe to server-sent events",
|
||||
"produces": [
|
||||
"text/event-stream"
|
||||
],
|
||||
"tags": [
|
||||
"events"
|
||||
],
|
||||
"summary": "SSE Events",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "stream",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/api/v1/llm/config": {
|
||||
"get": {
|
||||
"security": [
|
||||
|
||||
@@ -1427,6 +1427,19 @@ paths:
|
||||
summary: Validate OpenAI API Key
|
||||
tags:
|
||||
- config
|
||||
/api/v1/events:
|
||||
get:
|
||||
description: Subscribe to server-sent events
|
||||
produces:
|
||||
- text/event-stream
|
||||
responses:
|
||||
"200":
|
||||
description: stream
|
||||
schema:
|
||||
type: string
|
||||
summary: SSE Events
|
||||
tags:
|
||||
- events
|
||||
/api/v1/llm/config:
|
||||
get:
|
||||
description: Get the current active LLM configuration
|
||||
|
||||
@@ -23,8 +23,6 @@ import (
|
||||
"scriberr/internal/transcription/adapters"
|
||||
"scriberr/internal/transcription/registry"
|
||||
"scriberr/pkg/logger"
|
||||
|
||||
_ "scriberr/api-docs" // Import generated Swagger docs
|
||||
)
|
||||
|
||||
// Version information (set by GoReleaser)
|
||||
|
||||
@@ -7,8 +7,6 @@ import (
|
||||
"scriberr/pkg/middleware"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerFiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
)
|
||||
|
||||
// SetupRoutes sets up all API routes
|
||||
@@ -46,9 +44,6 @@ func SetupRoutes(handler *Handler, authService *auth.AuthService) *gin.Engine {
|
||||
// Health check endpoint (no auth required)
|
||||
router.GET("/health", handler.HealthCheck)
|
||||
|
||||
// Swagger documentation
|
||||
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
|
||||
|
||||
// CLI install script alias (root level for easier access)
|
||||
router.GET("/install.sh", handler.GetInstallScript)
|
||||
router.GET("/install-cli.sh", handler.GetInstallScript)
|
||||
|
||||
@@ -4,7 +4,7 @@ install:
|
||||
npm install
|
||||
|
||||
generate-api:
|
||||
cd ../.. && swag init -g server/main.go -o api-docs --dir cmd,internal
|
||||
cd ../.. && swag init -g server/main.go -o web/project-site/public/api --outputTypes json --dir cmd,internal
|
||||
|
||||
build: generate-api
|
||||
npm run build
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"sync:spec": "mkdir -p public/api && cp -f ../../api-docs/swagger.json public/api/swagger.json || echo 'No swagger.json found, skipping copy'",
|
||||
"sync:spec": "echo 'Swagger spec is generated by make generate-api'",
|
||||
"sync:undoc": "node ./scripts/gen-endpoints.mjs",
|
||||
"dev": "npm run sync:spec && npm run sync:undoc && vite",
|
||||
"build": "npm run sync:spec && npm run sync:undoc && tsc -b && vite build",
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import React from 'react';
|
||||
import { Link, useLocation } from 'react-router-dom';
|
||||
import { ScriberrLogo } from './components/ScriberrLogo';
|
||||
import { Github, Book, Code } from 'lucide-react';
|
||||
import { Button } from './components/ui/Button';
|
||||
import { Github, Book, Menu, X } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
|
||||
|
||||
import { GithubBadge } from './components/GithubBadge';
|
||||
|
||||
interface LayoutProps {
|
||||
children: React.ReactNode;
|
||||
@@ -11,6 +14,7 @@ interface LayoutProps {
|
||||
export function Layout({ children }: LayoutProps) {
|
||||
const location = useLocation();
|
||||
const isDocs = location.pathname.startsWith('/docs');
|
||||
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col font-sans selection:bg-[#FF6D20] selection:text-white">
|
||||
@@ -22,33 +26,55 @@ export function Layout({ children }: LayoutProps) {
|
||||
<ScriberrLogo />
|
||||
</Link>
|
||||
{isDocs && (
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-orange-100 text-[#FF6D20] border border-orange-200">
|
||||
<span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold bg-orange-100 text-[#FF6D20] border border-orange-200 font-heading">
|
||||
Docs
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<nav className="hidden md:flex items-center gap-8">
|
||||
<Link to="/#features" className="text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors">Features</Link>
|
||||
<Link to="/docs/intro" className="text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors">Documentation</Link>
|
||||
<Link to="/api" className="text-sm font-medium text-gray-600 hover:text-gray-900 transition-colors">API</Link>
|
||||
<Link to="/docs/intro" className="text-sm text-gray-600 hover:text-gray-900 transition-colors font-heading">Documentation</Link>
|
||||
<Link to="/api" className="text-sm text-gray-600 hover:text-gray-900 transition-colors font-heading">API</Link>
|
||||
</nav>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<a
|
||||
href="https://github.com/rishikanthc/Scriberr"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="p-2 text-gray-500 hover:text-gray-900 transition-colors"
|
||||
aria-label="GitHub"
|
||||
>
|
||||
<Github className="w-5 h-5" />
|
||||
</a>
|
||||
<div className="hidden sm:block">
|
||||
<Button variant="primary" size="sm">Get Started</Button>
|
||||
<div className="hidden md:block">
|
||||
<GithubBadge />
|
||||
</div>
|
||||
<button
|
||||
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||
className="md:hidden flex items-center justify-center w-10 h-10 rounded-xl bg-white/80 border border-gray-200 shadow-sm text-gray-600 hover:text-[#FF6D20] hover:border-orange-200 hover:bg-orange-50 transition-all duration-200"
|
||||
aria-label="Toggle menu"
|
||||
>
|
||||
{isMenuOpen ? <X className="w-5 h-5" strokeWidth={2.5} /> : <Menu className="w-5 h-5" strokeWidth={2.5} />}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile Menu Overlay */}
|
||||
{isMenuOpen && (
|
||||
<div className="md:hidden absolute top-16 left-0 right-0 bg-white border-b border-gray-100 shadow-lg animate-fade-in z-40">
|
||||
<nav className="flex flex-col p-4 space-y-4">
|
||||
<Link
|
||||
to="/docs/intro"
|
||||
className="text-sm font-medium text-gray-600 hover:text-gray-900 py-2 border-b border-gray-50 font-heading"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
Documentation
|
||||
</Link>
|
||||
<Link
|
||||
to="/api"
|
||||
className="text-sm font-medium text-gray-600 hover:text-gray-900 py-2 border-b border-gray-50 font-heading"
|
||||
onClick={() => setIsMenuOpen(false)}
|
||||
>
|
||||
API
|
||||
</Link>
|
||||
<div className="pt-2">
|
||||
<GithubBadge />
|
||||
</div>
|
||||
</nav>
|
||||
</div>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* Main Content */}
|
||||
@@ -57,21 +83,23 @@ export function Layout({ children }: LayoutProps) {
|
||||
</main>
|
||||
|
||||
{/* Footer */}
|
||||
<footer className="border-t border-gray-100 bg-gray-50 py-12">
|
||||
<footer className="border-t border-gray-100 bg-gray-50 py-6">
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 flex flex-col md:flex-row justify-between items-center gap-6">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-sm text-gray-500">© 2025 Scriberr. All rights reserved.</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-6">
|
||||
<a href="#" className="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<a
|
||||
href="https://github.com/rishikanthc/scriberr"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-gray-400 hover:text-gray-600 transition-colors"
|
||||
>
|
||||
<Github className="w-5 h-5" />
|
||||
</a>
|
||||
<Link to="/docs" className="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<Link to="/docs/intro" className="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<Book className="w-5 h-5" />
|
||||
</Link>
|
||||
<a href="#" className="text-gray-400 hover:text-gray-600 transition-colors">
|
||||
<Code className="w-5 h-5" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
56
web/project-site/src/components/GithubBadge.tsx
Normal file
56
web/project-site/src/components/GithubBadge.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Github, Star, GitFork } from 'lucide-react';
|
||||
|
||||
interface RepoStats {
|
||||
stargazers_count: number;
|
||||
forks_count: number;
|
||||
}
|
||||
|
||||
export function GithubBadge() {
|
||||
const [stats, setStats] = useState<RepoStats | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetch('https://api.github.com/repos/rishikanthc/scriberr')
|
||||
.then(res => res.json())
|
||||
.then(data => {
|
||||
setStats({
|
||||
stargazers_count: data.stargazers_count,
|
||||
forks_count: data.forks_count
|
||||
});
|
||||
})
|
||||
.catch(err => console.error('Failed to fetch repo stats:', err));
|
||||
}, []);
|
||||
|
||||
const formatCount = (count: number) => {
|
||||
if (count >= 1000) {
|
||||
return (count / 1000).toFixed(1) + 'k';
|
||||
}
|
||||
return count;
|
||||
};
|
||||
|
||||
return (
|
||||
<a
|
||||
href="https://github.com/rishikanthc/scriberr"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="group flex items-center gap-0 rounded-md overflow-hidden border border-gray-200 shadow-sm hover:shadow transition-all duration-200"
|
||||
>
|
||||
<div className="flex items-center gap-2 px-3 py-1.5 bg-gray-50 group-hover:bg-gray-100 transition-colors border-r border-gray-200">
|
||||
<Github className="w-4 h-4 text-gray-700" />
|
||||
<span className="text-xs font-semibold text-gray-700 font-heading">Star</span>
|
||||
</div>
|
||||
{stats && (
|
||||
<div className="flex items-center bg-white">
|
||||
<div className="flex items-center gap-1 px-2 py-1.5 border-r border-gray-100 last:border-0 hover:bg-gray-50 transition-colors">
|
||||
<Star className="w-3.5 h-3.5 text-amber-400 fill-amber-400" />
|
||||
<span className="text-xs font-medium text-gray-600 font-mono">{formatCount(stats.stargazers_count)}</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1 px-2 py-1.5 hover:bg-gray-50 transition-colors">
|
||||
<GitFork className="w-3.5 h-3.5 text-gray-400" />
|
||||
<span className="text-xs font-medium text-gray-600 font-mono">{formatCount(stats.forks_count)}</span>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -16,7 +16,7 @@ export function Button({
|
||||
...props
|
||||
}: ButtonProps) {
|
||||
|
||||
const baseStyles = "inline-flex items-center justify-center font-medium transition-all duration-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
|
||||
const baseStyles = "cursor-pointer inline-flex items-center justify-center font-medium transition-all duration-300 rounded-xl focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed";
|
||||
|
||||
const variants = {
|
||||
primary: "bg-gray-900 text-white shadow-lg hover:shadow-xl hover:-translate-y-0.5 focus:ring-gray-900",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=Work+Sans:wght@300;400;500;600&display=swap');
|
||||
@import "tailwindcss";
|
||||
|
||||
@theme {
|
||||
@@ -5,6 +6,8 @@
|
||||
--image-brand-gradient: var(--brand-gradient);
|
||||
|
||||
--font-sans: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
--font-heading: "Plus Jakarta Sans", sans-serif;
|
||||
--font-body: 'Work Sans', sans-serif;
|
||||
|
||||
--animate-fade-in: fadeIn 0.5s ease-out;
|
||||
--animate-slide-up: slideUp 0.5s ease-out;
|
||||
@@ -41,8 +44,6 @@
|
||||
--color-bg-primary: #ffffff;
|
||||
--color-bg-secondary: #f9fafb;
|
||||
--color-text-primary: #111827;
|
||||
--font-heading: 'Plus Jakarta Sans', sans-serif;
|
||||
--font-body: 'Work Sans', sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
@@ -3,6 +3,8 @@ import { Link, useLocation } from 'react-router-dom';
|
||||
import { Layout } from '../Layout';
|
||||
import { Section } from '../components/ui/Section';
|
||||
import { TableOfContents } from '../components/TableOfContents';
|
||||
import { useState } from 'react';
|
||||
import { Menu, X } from 'lucide-react';
|
||||
|
||||
interface DocsLayoutProps {
|
||||
children: React.ReactNode;
|
||||
@@ -10,10 +12,10 @@ interface DocsLayoutProps {
|
||||
|
||||
export function DocsLayout({ children }: DocsLayoutProps) {
|
||||
const location = useLocation();
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
|
||||
const navItems = [
|
||||
{ path: '/docs/intro', label: 'Introduction' },
|
||||
{ path: '/docs/features', label: 'Features' },
|
||||
{ path: '/docs/installation', label: 'Installation' },
|
||||
{ path: '/docs/configuration', label: 'Configuration' },
|
||||
{ path: '/docs/usage', label: 'Usage Guide' },
|
||||
@@ -23,9 +25,20 @@ export function DocsLayout({ children }: DocsLayoutProps) {
|
||||
<Layout>
|
||||
<Section className="py-0">
|
||||
<div className="flex w-full max-w-7xl mx-auto">
|
||||
{/* Sidebar Navigation - Fixed & Sticky */}
|
||||
<aside className="hidden lg:block w-64 shrink-0 fixed top-24 bottom-0 overflow-y-auto pr-8">
|
||||
<div className="py-2 space-y-8">
|
||||
{/* Mobile Sidebar Backdrop */}
|
||||
{isSidebarOpen && (
|
||||
<div
|
||||
className="fixed inset-0 bg-black/20 z-30 lg:hidden"
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Sidebar Navigation */}
|
||||
<aside className={`
|
||||
fixed top-16 bottom-0 left-0 w-64 bg-white border-r border-gray-100 z-40 transform transition-transform duration-300 lg:translate-x-0 lg:static lg:border-r-0 lg:block lg:z-auto
|
||||
${isSidebarOpen ? 'translate-x-0' : '-translate-x-full'}
|
||||
`}>
|
||||
<div className="h-full overflow-y-auto px-4 lg:px-0 py-6 lg:py-2 space-y-8">
|
||||
<div>
|
||||
<ul className="space-y-1">
|
||||
{navItems.map((item) => {
|
||||
@@ -34,6 +47,7 @@ export function DocsLayout({ children }: DocsLayoutProps) {
|
||||
<li key={item.path}>
|
||||
<Link
|
||||
to={item.path}
|
||||
onClick={() => setIsSidebarOpen(false)}
|
||||
className={`block px-3 py-1.5 rounded-md text-sm font-[family-name:var(--font-heading)] transition-colors duration-200 ${isActive
|
||||
? 'text-[#FF6D20] font-medium bg-orange-50'
|
||||
: 'text-gray-500 hover:text-gray-900 hover:bg-gray-50'
|
||||
@@ -49,16 +63,32 @@ export function DocsLayout({ children }: DocsLayoutProps) {
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
{/* Main Content Area - Fluid with max-width - Offset for fixed sidebar */}
|
||||
<div className="flex-1 min-w-0 lg:pl-72 lg:pr-8 pt-0 pb-12">
|
||||
<article className="docs-content max-w-3xl">
|
||||
{/* Main Content Area */}
|
||||
<div className="flex-1 min-w-0 px-4 sm:px-6 lg:pl-12 lg:pr-8 pt-6 lg:pt-0 pb-12 w-full">
|
||||
{/* Mobile Sidebar Toggle */}
|
||||
<div className="lg:hidden mb-6 flex items-center justify-between sticky top-0 bg-white/80 backdrop-blur-md py-4 z-20 border-b border-gray-100 -mx-4 px-4 sm:-mx-6 sm:px-6">
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className="flex items-center gap-2 px-3 py-1.5 rounded-lg bg-orange-50 border border-orange-100 text-[#FF6D20] font-semibold text-sm hover:bg-orange-100 transition-colors"
|
||||
>
|
||||
{isSidebarOpen ? <X className="w-4 h-4" strokeWidth={2.5} /> : <Menu className="w-4 h-4" strokeWidth={2.5} />}
|
||||
<span>Menu</span>
|
||||
</button>
|
||||
<div className="text-xs font-bold text-gray-900 uppercase tracking-wider bg-gray-100 px-2 py-1 rounded">
|
||||
{navItems.find(i => i.path === location.pathname)?.label || 'Docs'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<article className="docs-content max-w-3xl mx-auto lg:mx-0">
|
||||
{children}
|
||||
</article>
|
||||
</div>
|
||||
|
||||
{/* Right Sidebar - For TOC */}
|
||||
<div className="hidden xl:block shrink-0">
|
||||
<TableOfContents />
|
||||
<div className="hidden xl:block w-64 shrink-0">
|
||||
<div className="sticky top-24">
|
||||
<TableOfContents />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Section>
|
||||
|
||||
@@ -22,7 +22,7 @@ export function Home() {
|
||||
return (
|
||||
<div className="space-y-12">
|
||||
{/* Hero Section */}
|
||||
<Section className="min-h-[80vh] flex flex-col items-center justify-center text-center">
|
||||
<Section className="!py-12 sm:py-10 min-h-[auto] md:min-h-[80vh] flex flex-col items-center justify-center text-center">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 30 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
@@ -34,7 +34,7 @@ export function Home() {
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-orange-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-2 w-2 bg-orange-500"></span>
|
||||
</span>
|
||||
v1.0 Now Available
|
||||
v1.2.0 Now Available
|
||||
</div>
|
||||
|
||||
<Heading level={1}>
|
||||
@@ -47,12 +47,9 @@ export function Home() {
|
||||
State-of-the-art AI models, running entirely on your machine.
|
||||
</Paragraph>
|
||||
|
||||
<div className="flex flex-col sm:flex-row items-center justify-center gap-4 pt-4">
|
||||
<Link to="/docs/installation">
|
||||
<Button size="lg">Download Now</Button>
|
||||
</Link>
|
||||
<div className="flex items-center justify-center pt-4">
|
||||
<Link to="/docs/intro">
|
||||
<Button variant="secondary" size="lg">View Documentation</Button>
|
||||
<Button size="lg" className="px-8">Get Started</Button>
|
||||
</Link>
|
||||
</div>
|
||||
</motion.div>
|
||||
@@ -62,7 +59,7 @@ export function Home() {
|
||||
initial={{ opacity: 0, y: 50 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 1, delay: 0.2 }}
|
||||
className="relative w-full max-w-6xl mx-auto mt-20 perspective-1000"
|
||||
className="relative w-full max-w-6xl mx-auto mt-8 md:mt-20 perspective-1000"
|
||||
>
|
||||
{/* Gradient Glow */}
|
||||
<div className="absolute inset-0 bg-[image:var(--image-brand-gradient)] opacity-20 rounded-[2rem] blur-3xl -z-10 transform scale-90"></div>
|
||||
@@ -75,11 +72,10 @@ export function Home() {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Floating Mobile Mockup */}
|
||||
<motion.div
|
||||
animate={{ y: [0, -10, 0] }}
|
||||
transition={{ duration: 6, repeat: Infinity, ease: "easeInOut" }}
|
||||
className="absolute -bottom-12 -right-4 md:-right-12 w-[240px] md:w-[300px] rounded-[2.5rem] border-8 border-gray-900 overflow-hidden shadow-2xl bg-white"
|
||||
className="hidden md:block absolute -bottom-12 -right-12 w-[300px] rounded-[2.5rem] border-8 border-gray-900 overflow-hidden shadow-2xl bg-white"
|
||||
>
|
||||
<img
|
||||
src="/screenshots/mobile-transcript-light.PNG"
|
||||
|
||||
Reference in New Issue
Block a user