From a5be6d75522ed5200838b4f139c9c5401866eb03 Mon Sep 17 00:00:00 2001 From: Ku6epXBOCTuK Date: Thu, 11 Jun 2026 11:59:15 +0500 Subject: [PATCH] docs: add reference design --- ref/.gitignore | 15 + ref/app/dossier/[slug]/page.tsx | 187 ++ ref/app/globals.css | 230 ++ ref/app/layout.tsx | 41 + ref/app/page.tsx | 119 + ref/app/protocol/page.tsx | 127 + ref/components.json | 21 + ref/components/doc-elements.tsx | 106 + ref/components/dossier-card.tsx | 58 + ref/components/site-chrome.tsx | 68 + ref/components/ui/button.tsx | 58 + ref/lib/dossier-content.tsx | 261 +++ ref/lib/dossiers.ts | 87 + ref/lib/utils.ts | 6 + ref/next-env.d.ts | 6 + ref/next.config.mjs | 11 + ref/package.json | 33 + ref/pnpm-lock.yaml | 3884 +++++++++++++++++++++++++++++++ ref/postcss.config.mjs | 8 + ref/public/apple-icon.png | Bin 0 -> 2626 bytes ref/public/blueprint-lopata.png | Bin 0 -> 2168196 bytes ref/public/classified-photo.png | Bin 0 -> 2622255 bytes ref/public/emblem.png | Bin 0 -> 2049154 bytes ref/public/icon-dark-32x32.png | Bin 0 -> 585 bytes ref/public/icon-light-32x32.png | Bin 0 -> 566 bytes ref/public/icon.svg | 26 + ref/public/placeholder-logo.png | Bin 0 -> 568 bytes ref/public/placeholder-logo.svg | 1 + ref/public/placeholder-user.jpg | Bin 0 -> 1635 bytes ref/public/placeholder.jpg | Bin 0 -> 1064 bytes ref/public/placeholder.svg | 1 + ref/tsconfig.json | 41 + 32 files changed, 5395 insertions(+) create mode 100644 ref/.gitignore create mode 100644 ref/app/dossier/[slug]/page.tsx create mode 100644 ref/app/globals.css create mode 100644 ref/app/layout.tsx create mode 100644 ref/app/page.tsx create mode 100644 ref/app/protocol/page.tsx create mode 100644 ref/components.json create mode 100644 ref/components/doc-elements.tsx create mode 100644 ref/components/dossier-card.tsx create mode 100644 ref/components/site-chrome.tsx create mode 100644 ref/components/ui/button.tsx create mode 100644 ref/lib/dossier-content.tsx create mode 100644 ref/lib/dossiers.ts create mode 100644 ref/lib/utils.ts create mode 100644 ref/next-env.d.ts create mode 100644 ref/next.config.mjs create mode 100644 ref/package.json create mode 100644 ref/pnpm-lock.yaml create mode 100644 ref/postcss.config.mjs create mode 100644 ref/public/apple-icon.png create mode 100644 ref/public/blueprint-lopata.png create mode 100644 ref/public/classified-photo.png create mode 100644 ref/public/emblem.png create mode 100644 ref/public/icon-dark-32x32.png create mode 100644 ref/public/icon-light-32x32.png create mode 100644 ref/public/icon.svg create mode 100644 ref/public/placeholder-logo.png create mode 100644 ref/public/placeholder-logo.svg create mode 100644 ref/public/placeholder-user.jpg create mode 100644 ref/public/placeholder.jpg create mode 100644 ref/public/placeholder.svg create mode 100644 ref/tsconfig.json diff --git a/ref/.gitignore b/ref/.gitignore new file mode 100644 index 0000000..97eb8c0 --- /dev/null +++ b/ref/.gitignore @@ -0,0 +1,15 @@ +# v0 sandbox internal files +__v0_runtime_loader.js +__v0_devtools.tsx +__v0_jsx-dev-runtime.ts +.snowflake/ +.v0-trash/ +.vercel/ + +# Environment variables +.env*.local + +# Common ignores +node_modules +.next/ +.DS_Store \ No newline at end of file diff --git a/ref/app/dossier/[slug]/page.tsx b/ref/app/dossier/[slug]/page.tsx new file mode 100644 index 0000000..94c36af --- /dev/null +++ b/ref/app/dossier/[slug]/page.tsx @@ -0,0 +1,187 @@ +import { notFound } from 'next/navigation' +import Link from 'next/link' +import Image from 'next/image' +import { DOSSIERS, getDossier } from '@/lib/dossiers' +import { CONTENT } from '@/lib/dossier-content' +import { SiteHeader, SiteFooter } from '@/components/site-chrome' +import { + Stamp, + OverlayStamp, + FieldRow, + SectionTitle, + Perforation, +} from '@/components/doc-elements' + +export function generateStaticParams() { + return DOSSIERS.map((d) => ({ slug: d.slug })) +} + +export default async function DossierPage({ + params, +}: { + params: Promise<{ slug: string }> +}) { + const { slug } = await params + const dossier = getDossier(slug) + const content = CONTENT[slug] + if (!dossier || !content) notFound() + + return ( +
+ + +
+
+ {dossier.classification} + Дело № {dossier.code} + Экз. № 1 +
+
+ +
+ + ← Вернуться в картотеку + + +
+
+ + {/* Cross-references */} +
+

+ См. также +

+
+
+ {DOSSIERS.filter((d) => d.slug !== slug) + .slice(0, 4) + .map((d) => ( + + + {d.name} + + + {d.code} + + + ))} +
+
+ +
+ +
+
+ + +
+ ) +} diff --git a/ref/app/globals.css b/ref/app/globals.css new file mode 100644 index 0000000..abe1388 --- /dev/null +++ b/ref/app/globals.css @@ -0,0 +1,230 @@ +@import 'tailwindcss'; +@import 'tw-animate-css'; +@import 'shadcn/tailwind.css'; + +@custom-variant dark (&:is(.dark *)); + +@theme inline { + --font-heading: var(--font-oswald); + --font-sans: var(--font-plex-mono), 'Geist Fallback'; + --font-mono: var(--font-plex-mono), 'Geist Mono Fallback'; + --color-sidebar-ring: var(--sidebar-ring); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar: var(--sidebar); + --color-chart-5: var(--chart-5); + --color-chart-4: var(--chart-4); + --color-chart-3: var(--chart-3); + --color-chart-2: var(--chart-2); + --color-chart-1: var(--chart-1); + --color-ring: var(--ring); + --color-input: var(--input); + --color-border: var(--border); + --color-destructive: var(--destructive); + --color-accent-foreground: var(--accent-foreground); + --color-accent: var(--accent); + --color-muted-foreground: var(--muted-foreground); + --color-muted: var(--muted); + --color-secondary-foreground: var(--secondary-foreground); + --color-secondary: var(--secondary); + --color-primary-foreground: var(--primary-foreground); + --color-primary: var(--primary); + --color-popover-foreground: var(--popover-foreground); + --color-popover: var(--popover); + --color-card-foreground: var(--card-foreground); + --color-card: var(--card); + --color-foreground: var(--foreground); + --color-background: var(--background); + --color-stamp: var(--stamp); + --color-ink: var(--ink); + --color-ink-soft: var(--ink-soft); + --radius-sm: calc(var(--radius) * 0.6); + --radius-md: calc(var(--radius) * 0.8); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) * 1.4); +} + +:root { + /* Aged paper / Soviet document palette — warm, low-contrast, sepia ink */ + --background: oklch(0.9 0.028 78); + --foreground: oklch(0.32 0.03 52); + --card: oklch(0.945 0.024 84); + --card-foreground: oklch(0.32 0.03 52); + --popover: oklch(0.945 0.024 84); + --popover-foreground: oklch(0.32 0.03 52); + --primary: oklch(0.47 0.16 30); + --primary-foreground: oklch(0.95 0.022 88); + --secondary: oklch(0.86 0.03 78); + --secondary-foreground: oklch(0.34 0.03 52); + --muted: oklch(0.87 0.026 80); + --muted-foreground: oklch(0.5 0.028 56); + --accent: oklch(0.47 0.16 30); + --accent-foreground: oklch(0.95 0.022 88); + --destructive: oklch(0.47 0.16 30); + --border: oklch(0.56 0.035 58 / 0.45); + --input: oklch(0.56 0.035 58 / 0.45); + --ring: oklch(0.47 0.16 30); + --stamp: oklch(0.46 0.17 30); + --ink: oklch(0.32 0.03 52); + --ink-soft: oklch(0.5 0.028 56); + --chart-1: oklch(0.47 0.16 30); + --chart-2: oklch(0.5 0.02 60); + --chart-3: oklch(0.4 0.02 55); + --chart-4: oklch(0.6 0.05 80); + --chart-5: oklch(0.3 0.02 55); + --radius: 0.125rem; + --sidebar: oklch(0.9 0.022 84); + --sidebar-foreground: oklch(0.32 0.03 52); + --sidebar-primary: oklch(0.47 0.16 30); + --sidebar-primary-foreground: oklch(0.95 0.022 88); + --sidebar-accent: oklch(0.86 0.03 78); + --sidebar-accent-foreground: oklch(0.34 0.03 52); + --sidebar-border: oklch(0.56 0.035 58 / 0.45); + --sidebar-ring: oklch(0.47 0.16 30); +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + /* layered paper: faint ledger rules + grain + warm vignette */ + background-image: + /* vignette */ + radial-gradient( + 130% 120% at 50% 0%, + transparent 55%, + oklch(0.3 0.04 52 / 0.12) 100% + ), + /* fine grain */ + radial-gradient(oklch(0.3 0.03 52 / 0.05) 1px, transparent 1.4px), + radial-gradient(oklch(0.3 0.03 52 / 0.04) 1px, transparent 1.4px), + /* warm base wash */ + linear-gradient(180deg, oklch(0.91 0.03 80), oklch(0.88 0.03 76)); + background-size: 100% 100%, 16px 16px, 22px 22px, 100% 100%; + background-position: 0 0, 0 0, 8px 11px, 0 0; + background-attachment: fixed; + } + html { + @apply font-mono; + } +} + +@layer utilities { + .font-heading { + font-family: var(--font-heading); + } + + /* ---- Paper sheet: the core surface, replaces hard black rectangles ---- */ + .paper { + position: relative; + background-color: var(--card); + background-image: + linear-gradient( + oklch(0.5 0.03 56 / 0.05) 1px, + transparent 1px + ), + radial-gradient(oklch(0.3 0.03 52 / 0.04) 1px, transparent 1.3px); + background-size: 100% 30px, 18px 18px; + border: 1px solid oklch(0.5 0.04 56 / 0.35); + box-shadow: + 0 1px 0 oklch(1 0 0 / 0.4) inset, + 0 18px 40px -24px oklch(0.25 0.04 52 / 0.55), + 0 2px 6px -3px oklch(0.25 0.04 52 / 0.3); + } + /* darker "cover/file" sheet */ + .paper-strong { + background-color: oklch(0.5 0.04 56 / 0.06); + } + + /* tape strip for pinning sheets */ + .tape { + position: absolute; + height: 1.6rem; + width: 6.5rem; + background: oklch(0.78 0.06 92 / 0.55); + box-shadow: 0 1px 4px oklch(0.25 0.04 52 / 0.25); + backdrop-filter: blur(0.5px); + } + + /* ---- Document divider rules (replace border-b-2 black) ---- */ + .rule { + border: 0; + border-top: 1px solid oklch(0.45 0.04 56 / 0.5); + box-shadow: 0 3px 0 -2px oklch(0.45 0.04 56 / 0.5); + } + .rule-red { + border-top-color: var(--primary); + box-shadow: 0 3px 0 -2px var(--primary); + } + + /* ---- Classification rubber stamp: rotated, distressed, translucent ---- */ + .stamp { + --c: var(--stamp); + display: inline-flex; + align-items: center; + border: 2.5px solid var(--c); + color: var(--c); + text-transform: uppercase; + letter-spacing: 0.1em; + font-weight: 700; + border-radius: 3px; + box-shadow: inset 0 0 0 1.5px var(--c); + opacity: 0.78; + mix-blend-mode: multiply; + /* worn ink texture */ + -webkit-mask-image: radial-gradient( + oklch(0 0 0) 60%, + transparent 62% + ), + url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.4 1.1'/%3E%3C/filter%3E%3Crect width='80' height='40' filter='url(%23n)'/%3E%3C/svg%3E"); + -webkit-mask-composite: source-over; + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='80' height='40'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2'/%3E%3CfeColorMatrix type='matrix' values='0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -1.4 1.1'/%3E%3C/filter%3E%3Crect width='80' height='40' filter='url(%23n)'/%3E%3C/svg%3E"); + mask-mode: luminance; + } + .stamp-skew { + transform: rotate(-7deg); + } + .stamp-ink { + --c: var(--ink-soft); + } + + /* big translucent overlay stamp */ + .stamp-overlay { + color: var(--stamp); + border: 4px solid var(--stamp); + opacity: 0.22; + mix-blend-mode: multiply; + text-transform: uppercase; + font-weight: 700; + letter-spacing: 0.12em; + border-radius: 6px; + } + + /* redacted bar with toner-bleed edges */ + .redacted { + background: var(--ink); + color: transparent; + user-select: none; + border-radius: 1px; + box-shadow: 0 0 1px 1px oklch(0.3 0.03 52 / 0.4); + } + + /* ledger-style top banner */ + .banner-classified { + background: + repeating-linear-gradient( + 45deg, + oklch(0.42 0.16 30), + oklch(0.42 0.16 30) 14px, + oklch(0.38 0.15 30) 14px, + oklch(0.38 0.15 30) 28px + ); + color: var(--primary-foreground); + } +} diff --git a/ref/app/layout.tsx b/ref/app/layout.tsx new file mode 100644 index 0000000..e1e486c --- /dev/null +++ b/ref/app/layout.tsx @@ -0,0 +1,41 @@ +import type { Metadata } from 'next' +import { Oswald, IBM_Plex_Mono } from 'next/font/google' +import { Analytics } from '@vercel/analytics/next' +import './globals.css' + +const oswald = Oswald({ + variable: '--font-oswald', + subsets: ['latin', 'cyrillic'], + weight: ['400', '500', '600', '700'], +}) + +const plexMono = IBM_Plex_Mono({ + variable: '--font-plex-mono', + subsets: ['latin', 'cyrillic'], + weight: ['400', '500', '600', '700'], +}) + +export const metadata: Metadata = { + title: 'АРХИВ ДНТ-7 // Дачно-Наблюдательный Трибунал', + description: + 'Рассекреченный архив Дачно-Наблюдательного Трибунала №7. Документы, инструкции и уставы по противодействию рептилоидным агентам планеты Нибиру.', + generator: 'v0.app', +} + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode +}>) { + return ( + + + {children} + {process.env.NODE_ENV === 'production' && } + + + ) +} diff --git a/ref/app/page.tsx b/ref/app/page.tsx new file mode 100644 index 0000000..ac50e87 --- /dev/null +++ b/ref/app/page.tsx @@ -0,0 +1,119 @@ +import Image from 'next/image' +import { DOSSIERS } from '@/lib/dossiers' +import { DossierCard } from '@/components/dossier-card' +import { SiteHeader, SiteFooter } from '@/components/site-chrome' +import { Stamp, OverlayStamp, Perforation } from '@/components/doc-elements' + +const tilts = ['-0.6deg', '0.5deg', '-0.4deg', '0.7deg', '-0.5deg', '0.4deg'] + +export default function HomePage() { + return ( +
+ + + {/* Classified banner */} +
+
+ Сов. Секретно + Экз. № 1 из 3 + Хранить вечно +
+
+ +
+ {/* Hero document header */} +
+
+ + {/* Stat strip */} +
+ {[ + { k: 'Дел в картотеке', v: '147' }, + { k: 'Заземлено агентов', v: '38' }, + { k: 'Активных участков', v: '9' }, + { k: 'Уровень тревоги', v: 'III' }, + ].map((s, i) => ( +
+

+ {s.v} +

+

+ {s.k} +

+
+ ))} +
+ + {/* Catalog */} +
+
+
+

+ Картотека дел +

+
+
+

+ Опись · лист 1 +

+
+ +
+ {DOSSIERS.map((d, i) => ( + + ))} +
+
+ +
+ +
+
+ + +
+ ) +} diff --git a/ref/app/protocol/page.tsx b/ref/app/protocol/page.tsx new file mode 100644 index 0000000..2ee1efa --- /dev/null +++ b/ref/app/protocol/page.tsx @@ -0,0 +1,127 @@ +import Image from 'next/image' +import { SiteHeader, SiteFooter } from '@/components/site-chrome' +import { Stamp, SectionTitle, Perforation } from '@/components/doc-elements' + +const STEPS = [ + { + n: '01', + t: 'Обнаружение', + d: 'Зафиксировать признаки маскировки: немигающий взгляд, любовь к солнцу, неприятие соли, идеальная рассада без труда.', + }, + { + n: '02', + t: 'Доклад', + d: 'Сообщить старшему по кооперативу через условный сигнал (три удара по бочке с водой).', + }, + { + n: '03', + t: 'Локализация', + d: 'Применить изделие Л.О.П.А.Т.А. (ИНВ-001) для заземления. Соблюдать меры безопасности.', + }, + { + n: '04', + t: 'Экранирование', + d: 'Накрыть изделием В.Е.Д.Р.О. (ИНВ-022) для блокировки канала связи с орбитой.', + }, + { + n: '05', + t: 'Регистрация', + d: 'Внести запись в журнал формы Б-2. Присвоить делу номер. Хранить вечно.', + }, +] + +export default function ProtocolPage() { + return ( +
+ + +
+
+ Для служебного пользования + Форма Б-2 + Изучить под роспись +
+
+ +
+
+
+ +
+ +
+
+ + +
+ ) +} diff --git a/ref/components.json b/ref/components.json new file mode 100644 index 0000000..45921ec --- /dev/null +++ b/ref/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "base-nova", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/ref/components/doc-elements.tsx b/ref/components/doc-elements.tsx new file mode 100644 index 0000000..acf6715 --- /dev/null +++ b/ref/components/doc-elements.tsx @@ -0,0 +1,106 @@ +import { cn } from '@/lib/utils' +import type { ReactNode } from 'react' + +export function Stamp({ + children, + className, + skew = true, + variant = 'red', +}: { + children: ReactNode + className?: string + skew?: boolean + variant?: 'red' | 'ink' +}) { + return ( + + {children} + + ) +} + +export function OverlayStamp({ + children, + className, +}: { + children: ReactNode + className?: string +}) { + return ( + + ) +} + +export function Redacted({ children }: { children: ReactNode }) { + return {children} +} + +export function FieldRow({ + label, + children, +}: { + label: string + children: ReactNode +}) { + return ( +
+
+ {label} +
+
{children}
+
+ ) +} + +export function SectionTitle({ + index, + children, +}: { + index: string + children: ReactNode +}) { + return ( +

+ + {index} + + + {children} + +

+ ) +} + +export function Perforation({ className }: { className?: string }) { + return ( + + ) +} diff --git a/ref/components/dossier-card.tsx b/ref/components/dossier-card.tsx new file mode 100644 index 0000000..a4c2b20 --- /dev/null +++ b/ref/components/dossier-card.tsx @@ -0,0 +1,58 @@ +import Link from 'next/link' +import type { Dossier } from '@/lib/dossiers' +import { Stamp } from '@/components/doc-elements' + +const classColor: Record = { + 'СОВ. СЕКРЕТНО': 'text-primary', + 'СЕКРЕТНО': 'stamp-ink', + 'ДСП': 'stamp-ink', + 'РАССЕКРЕЧЕНО': 'stamp-ink opacity-60', +} + +export function DossierCard({ d, rotate }: { d: Dossier; rotate?: string }) { + return ( + + {/* tape pin */} +