feat(frontend): visual QA pass — unified page titles, CTAs, native controls, 404 fixes

- add PageTitle and apply it on /download, /themes, /configs, /showcase, /link, 404
- reset range-slider thumbs in the theme editor; fold color picker into the swatch
- hide horizontal scrollbars in the ClickGui mock; clip html to kill 100vw pan
- pin the footer under short pages via flex AppShell; split 404 art from caption
- one landing CTA geometry (lv-cta-lg / quiet); CLOUD eyebrow; auth caption scrim
- showcase error hides sort/pager; query retry limited to 1
This commit is contained in:
loki5512344 2026-09-28 13:11:46 +02:00
parent 10d90dce93
commit 7bb981b58e
Signed by: boba
GPG key ID: 253067914055423B
26 changed files with 198 additions and 73 deletions

View file

@ -7,7 +7,7 @@ import { TopBar } from './topbar/TopBar'
export function AppShell() {
const { t } = useTranslation('common')
return (
<>
<div className="flex min-h-dvh flex-col">
<a
href="#main"
className="sr-only focus:not-sr-only focus:absolute focus:left-4 focus:top-2 focus:z-50 focus:rounded-md focus:bg-slate focus:px-3 focus:py-2"
@ -16,10 +16,10 @@ export function AppShell() {
</a>
<KonamiEffect />
<TopBar />
<main id="main" className="mx-auto w-full max-w-[1120px] px-4 pt-(--topbar-h) sm:px-6">
<main id="main" className="mx-auto flex w-full max-w-[1120px] flex-1 flex-col px-4 pt-(--topbar-h) sm:px-6">
<Outlet />
</main>
<Footer />
</>
</div>
)
}

View file

@ -1,4 +1,4 @@
import type { ButtonHTMLAttributes } from 'react'
import type { AnchorHTMLAttributes, ButtonHTMLAttributes } from 'react'
import { Link, type LinkProps } from 'react-router'
type Variant = 'primary' | 'quiet' | 'danger'
@ -35,3 +35,12 @@ export function Button({
export function ButtonLink({ variant = 'primary', className = '', ...rest }: LinkProps & { variant?: Variant }) {
return <Link className={`${BASE} ${VARIANTS[variant]} ${className}`} {...rest} />
}
/** Like ButtonLink but escapes to a plain <a> for external/href CTAs (e.g. GitHub downloads). */
export function ButtonAnchor({
variant = 'primary',
className = '',
...rest
}: AnchorHTMLAttributes<HTMLAnchorElement> & { variant?: Variant }) {
return <a className={`${BASE} ${VARIANTS[variant]} ${className}`} {...rest} />
}

View file

@ -0,0 +1,16 @@
import type { ReactNode } from 'react'
/**
* The canonical page heading. Every top-level page (except the landing hero and the
* auth panes) uses this so titles share one size, weight and uppercase treatment.
*/
export function PageTitle({ children, lead }: { children: ReactNode; lead?: ReactNode }) {
return (
<header className="flex flex-col gap-2">
<h1 className="font-display text-3xl font-black uppercase leading-tight tracking-[0.01em] text-snow">
{children}
</h1>
{lead ? <p className="max-w-[60ch] text-frost">{lead}</p> : null}
</header>
)
}

View file

@ -2,6 +2,7 @@ import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { expect, test, vi } from 'vitest'
import { Button } from '../Button'
import { PageTitle } from '../PageTitle'
import { TextField } from '../TextField'
import { ShareCode } from '../ShareCode'
import { Notice } from '../Notice'
@ -34,3 +35,11 @@ test('error notice is an alert', () => {
render(<Notice tone="error">Неверный код</Notice>)
expect(screen.getByRole('alert')).toHaveTextContent('Неверный код')
})
test('page title renders one h1 with an optional lead', () => {
render(
<PageTitle lead="Короткое описание">Мои конфиги</PageTitle>,
)
expect(screen.getByRole('heading', { level: 1, name: 'Мои конфиги' })).toBeInTheDocument()
expect(screen.getByText('Короткое описание')).toBeInTheDocument()
})