Claude commited on
Commit
78dd858
·
unverified ·
1 Parent(s): b134c5d

feat(frontend): Sprint R1 — retro design system foundation

Browse files

Design system inspired by Xerox Star, Smalltalk-80, and classic Mac:

- Tailwind config: retro color palette (monochrome), pixel-perfect
shadows (outset/inset bevel, hard drop shadows), bitmap font tokens
- Global CSS: IBM Plex Mono font, retro scrollbars (Win 3.1 style),
dithered background patterns, inverted selection, dotted focus rings
- 9 primitive components in src/components/retro/:
RetroWindow (title bar + status bar + close button),
RetroButton (3D bevel outset/inset), RetroMenuBar (classic Mac menu),
RetroIcon (desktop icon with label), RetroCheckbox (pixel checkbox),
RetroInput, RetroTextarea, RetroSelect (all with inset wells),
RetroBadge (monochrome status indicators)
- RetroDemo page to showcase all components (temporary, removed in R2)
- Barrel export index.ts for clean imports

No existing components modified. Build passes (tsc + vite).

https://claude.ai/code/session_01WWohTtw2CxGRawmpH1tyrY

frontend/src/App.tsx CHANGED
@@ -3,12 +3,14 @@ import Admin from './pages/Admin.tsx'
3
  import Editor from './pages/Editor.tsx'
4
  import Home from './pages/Home.tsx'
5
  import Reader from './pages/Reader.tsx'
 
6
 
7
  type View =
8
  | { name: 'home' }
9
  | { name: 'reader'; manuscriptId: string; profileId: string }
10
  | { name: 'admin' }
11
  | { name: 'editor'; pageId: string }
 
12
 
13
  export default function App() {
14
  const [view, setView] = useState<View>({ name: 'home' })
@@ -37,6 +39,10 @@ export default function App() {
37
  )
38
  }
39
 
 
 
 
 
40
  return (
41
  <Home
42
  onOpenManuscript={(manuscriptId, profileId) =>
 
3
  import Editor from './pages/Editor.tsx'
4
  import Home from './pages/Home.tsx'
5
  import Reader from './pages/Reader.tsx'
6
+ import RetroDemo from './pages/RetroDemo.tsx'
7
 
8
  type View =
9
  | { name: 'home' }
10
  | { name: 'reader'; manuscriptId: string; profileId: string }
11
  | { name: 'admin' }
12
  | { name: 'editor'; pageId: string }
13
+ | { name: 'retro-demo' }
14
 
15
  export default function App() {
16
  const [view, setView] = useState<View>({ name: 'home' })
 
39
  )
40
  }
41
 
42
+ if (view.name === 'retro-demo') {
43
+ return <RetroDemo onBack={() => setView({ name: 'home' })} />
44
+ }
45
+
46
  return (
47
  <Home
48
  onOpenManuscript={(manuscriptId, profileId) =>
frontend/src/components/retro/RetroBadge.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReactNode } from 'react'
2
+
3
+ type Variant = 'default' | 'success' | 'warning' | 'error' | 'info'
4
+
5
+ interface Props {
6
+ children: ReactNode
7
+ variant?: Variant
8
+ className?: string
9
+ }
10
+
11
+ const variantStyles: Record<Variant, string> = {
12
+ default: 'bg-retro-gray text-retro-black',
13
+ success: 'bg-retro-black text-retro-white',
14
+ warning: 'bg-retro-white text-retro-black border-dashed',
15
+ error: 'bg-retro-white text-retro-black font-bold',
16
+ info: 'bg-retro-light text-retro-black',
17
+ }
18
+
19
+ export default function RetroBadge({
20
+ children,
21
+ variant = 'default',
22
+ className = '',
23
+ }: Props) {
24
+ return (
25
+ <span
26
+ className={`
27
+ inline-block
28
+ px-2 py-[1px]
29
+ text-retro-xs font-retro
30
+ border border-retro-black
31
+ ${variantStyles[variant]}
32
+ ${className}
33
+ `}
34
+ >
35
+ {children}
36
+ </span>
37
+ )
38
+ }
frontend/src/components/retro/RetroButton.tsx ADDED
@@ -0,0 +1,46 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReactNode, ButtonHTMLAttributes } from 'react'
2
+
3
+ interface Props extends ButtonHTMLAttributes<HTMLButtonElement> {
4
+ children: ReactNode
5
+ /** Render as a small compact button */
6
+ size?: 'sm' | 'md'
7
+ /** If true, renders in "pressed" state */
8
+ pressed?: boolean
9
+ }
10
+
11
+ export default function RetroButton({
12
+ children,
13
+ size = 'md',
14
+ pressed = false,
15
+ className = '',
16
+ disabled,
17
+ ...rest
18
+ }: Props) {
19
+ const padding = size === 'sm' ? 'px-2 py-[1px]' : 'px-3 py-[3px]'
20
+ const fontSize = size === 'sm' ? 'text-retro-xs' : 'text-retro-sm'
21
+
22
+ return (
23
+ <button
24
+ className={`
25
+ ${padding} ${fontSize}
26
+ font-retro font-medium
27
+ bg-retro-gray
28
+ border border-retro-black
29
+ ${pressed
30
+ ? 'shadow-retro-inset'
31
+ : 'shadow-retro-outset active:shadow-retro-inset'
32
+ }
33
+ ${disabled
34
+ ? 'text-retro-darkgray cursor-not-allowed'
35
+ : 'text-retro-black hover:bg-retro-light cursor-pointer'
36
+ }
37
+ select-none
38
+ ${className}
39
+ `}
40
+ disabled={disabled}
41
+ {...rest}
42
+ >
43
+ {children}
44
+ </button>
45
+ )
46
+ }
frontend/src/components/retro/RetroCheckbox.tsx ADDED
@@ -0,0 +1,48 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interface Props {
2
+ /** Label text next to the checkbox */
3
+ label: string
4
+ /** Controlled checked state */
5
+ checked: boolean
6
+ /** Change handler */
7
+ onChange: (checked: boolean) => void
8
+ /** Disabled state */
9
+ disabled?: boolean
10
+ /** Extra CSS classes on the wrapper */
11
+ className?: string
12
+ }
13
+
14
+ export default function RetroCheckbox({
15
+ label,
16
+ checked,
17
+ onChange,
18
+ disabled = false,
19
+ className = '',
20
+ }: Props) {
21
+ return (
22
+ <label
23
+ onClick={() => { if (!disabled) onChange(!checked) }}
24
+ className={`
25
+ inline-flex items-center gap-[6px]
26
+ text-retro-sm font-retro
27
+ ${disabled ? 'text-retro-darkgray cursor-not-allowed' : 'cursor-pointer'}
28
+ select-none
29
+ ${className}
30
+ `}
31
+ >
32
+ <span
33
+ className={`
34
+ inline-flex items-center justify-center
35
+ w-[13px] h-[13px]
36
+ border border-retro-black
37
+ bg-retro-white
38
+ shadow-retro-well
39
+ text-[10px] leading-none font-bold
40
+ shrink-0
41
+ `}
42
+ >
43
+ {checked && <span className="text-retro-black">x</span>}
44
+ </span>
45
+ {label}
46
+ </label>
47
+ )
48
+ }
frontend/src/components/retro/RetroIcon.tsx ADDED
@@ -0,0 +1,63 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ interface Props {
2
+ /** Label displayed below the icon */
3
+ label: string
4
+ /** Emoji or text character used as the icon glyph */
5
+ glyph: string
6
+ /** Click handler */
7
+ onClick?: () => void
8
+ /** If true, renders in selected/highlighted state */
9
+ selected?: boolean
10
+ /** Extra CSS classes */
11
+ className?: string
12
+ }
13
+
14
+ export default function RetroIcon({
15
+ label,
16
+ glyph,
17
+ onClick,
18
+ selected = false,
19
+ className = '',
20
+ }: Props) {
21
+ return (
22
+ <button
23
+ onClick={onClick}
24
+ className={`
25
+ flex flex-col items-center gap-1
26
+ p-2 w-[80px]
27
+ cursor-pointer select-none
28
+ ${className}
29
+ `}
30
+ onDoubleClick={onClick}
31
+ >
32
+ {/* Icon box */}
33
+ <div
34
+ className={`
35
+ w-[48px] h-[48px]
36
+ flex items-center justify-center
37
+ border border-retro-black
38
+ text-[24px] leading-none
39
+ ${selected
40
+ ? 'bg-retro-black text-retro-white'
41
+ : 'bg-retro-white text-retro-black hover:bg-retro-light'
42
+ }
43
+ shadow-retro-outset
44
+ `}
45
+ >
46
+ {glyph}
47
+ </div>
48
+ {/* Label */}
49
+ <span
50
+ className={`
51
+ text-retro-xs text-center leading-tight
52
+ max-w-full break-words
53
+ ${selected
54
+ ? 'bg-retro-select text-retro-select-text px-1'
55
+ : 'text-retro-black'
56
+ }
57
+ `}
58
+ >
59
+ {label}
60
+ </span>
61
+ </button>
62
+ )
63
+ }
frontend/src/components/retro/RetroInput.tsx ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { InputHTMLAttributes } from 'react'
2
+
3
+ interface Props extends InputHTMLAttributes<HTMLInputElement> {
4
+ /** Optional label rendered above the input */
5
+ label?: string
6
+ }
7
+
8
+ export default function RetroInput({ label, className = '', ...rest }: Props) {
9
+ return (
10
+ <div className="flex flex-col gap-[2px]">
11
+ {label && (
12
+ <label className="text-retro-xs font-retro font-medium text-retro-black">
13
+ {label}
14
+ </label>
15
+ )}
16
+ <input
17
+ className={`
18
+ px-2 py-[3px]
19
+ text-retro-sm font-retro
20
+ bg-retro-white text-retro-black
21
+ border border-retro-black
22
+ shadow-retro-well
23
+ placeholder:text-retro-darkgray
24
+ ${className}
25
+ `}
26
+ {...rest}
27
+ />
28
+ </div>
29
+ )
30
+ }
frontend/src/components/retro/RetroMenuBar.tsx ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReactNode } from 'react'
2
+
3
+ export interface MenuItem {
4
+ label: string
5
+ onClick?: () => void
6
+ disabled?: boolean
7
+ }
8
+
9
+ interface Props {
10
+ /** Left-aligned items (logo, menus) */
11
+ items?: MenuItem[]
12
+ /** Right-aligned content (search, status) */
13
+ right?: ReactNode
14
+ /** Extra CSS classes */
15
+ className?: string
16
+ }
17
+
18
+ export default function RetroMenuBar({ items = [], right, className = '' }: Props) {
19
+ return (
20
+ <div
21
+ className={`
22
+ flex items-center
23
+ bg-retro-gray
24
+ border-b-retro border-retro-black
25
+ shadow-retro-outset
26
+ px-1 py-[2px]
27
+ select-none shrink-0
28
+ ${className}
29
+ `}
30
+ >
31
+ {items.map((item, i) => (
32
+ <button
33
+ key={i}
34
+ onClick={item.onClick}
35
+ disabled={item.disabled}
36
+ className={`
37
+ px-3 py-[2px]
38
+ text-retro-sm font-retro font-medium
39
+ ${item.disabled
40
+ ? 'text-retro-darkgray cursor-not-allowed'
41
+ : 'text-retro-black hover:bg-retro-black hover:text-retro-white cursor-pointer'
42
+ }
43
+ `}
44
+ >
45
+ {item.label}
46
+ </button>
47
+ ))}
48
+ {right && (
49
+ <div className="ml-auto flex items-center gap-1">
50
+ {right}
51
+ </div>
52
+ )}
53
+ </div>
54
+ )
55
+ }
frontend/src/components/retro/RetroSelect.tsx ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { SelectHTMLAttributes } from 'react'
2
+
3
+ interface Props extends SelectHTMLAttributes<HTMLSelectElement> {
4
+ /** Optional label rendered above the select */
5
+ label?: string
6
+ /** Options: array of {value, label} */
7
+ options: { value: string; label: string }[]
8
+ }
9
+
10
+ export default function RetroSelect({ label, options, className = '', ...rest }: Props) {
11
+ return (
12
+ <div className="flex flex-col gap-[2px]">
13
+ {label && (
14
+ <label className="text-retro-xs font-retro font-medium text-retro-black">
15
+ {label}
16
+ </label>
17
+ )}
18
+ <select
19
+ className={`
20
+ px-2 py-[3px]
21
+ text-retro-sm font-retro
22
+ bg-retro-white text-retro-black
23
+ border border-retro-black
24
+ shadow-retro-well
25
+ cursor-pointer
26
+ ${className}
27
+ `}
28
+ {...rest}
29
+ >
30
+ {options.map((opt) => (
31
+ <option key={opt.value} value={opt.value}>
32
+ {opt.label}
33
+ </option>
34
+ ))}
35
+ </select>
36
+ </div>
37
+ )
38
+ }
frontend/src/components/retro/RetroTextarea.tsx ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { TextareaHTMLAttributes } from 'react'
2
+
3
+ interface Props extends TextareaHTMLAttributes<HTMLTextAreaElement> {
4
+ /** Optional label rendered above the textarea */
5
+ label?: string
6
+ }
7
+
8
+ export default function RetroTextarea({ label, className = '', ...rest }: Props) {
9
+ return (
10
+ <div className="flex flex-col gap-[2px]">
11
+ {label && (
12
+ <label className="text-retro-xs font-retro font-medium text-retro-black">
13
+ {label}
14
+ </label>
15
+ )}
16
+ <textarea
17
+ className={`
18
+ px-2 py-[3px]
19
+ text-retro-sm font-retro
20
+ bg-retro-white text-retro-black
21
+ border border-retro-black
22
+ shadow-retro-well
23
+ placeholder:text-retro-darkgray
24
+ retro-scroll
25
+ resize-y
26
+ ${className}
27
+ `}
28
+ {...rest}
29
+ />
30
+ </div>
31
+ )
32
+ }
frontend/src/components/retro/RetroWindow.tsx ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { ReactNode } from 'react'
2
+
3
+ interface Props {
4
+ /** Window title shown in the title bar */
5
+ title: string
6
+ /** Optional status text displayed in the bottom status bar */
7
+ statusBar?: string
8
+ /** Called when the close button is clicked (omit to hide the button) */
9
+ onClose?: () => void
10
+ /** Extra CSS classes for the outer container */
11
+ className?: string
12
+ /** Content rendered inside the window body */
13
+ children: ReactNode
14
+ /** If true, window body is scrollable with retro scrollbars */
15
+ scrollable?: boolean
16
+ /** If true, the window is rendered as "active" (darker title bar) */
17
+ active?: boolean
18
+ }
19
+
20
+ export default function RetroWindow({
21
+ title,
22
+ statusBar,
23
+ onClose,
24
+ className = '',
25
+ children,
26
+ scrollable = false,
27
+ active = true,
28
+ }: Props) {
29
+ return (
30
+ <div
31
+ className={`
32
+ flex flex-col
33
+ border-retro border-retro-black bg-retro-gray
34
+ shadow-retro
35
+ ${className}
36
+ `}
37
+ >
38
+ {/* ── Title bar ──────────────────────────────────────────── */}
39
+ <div
40
+ className={`
41
+ flex items-center gap-2 px-2 py-[3px]
42
+ select-none shrink-0
43
+ ${active ? 'bg-retro-black text-retro-white' : 'bg-retro-darkgray text-retro-white'}
44
+ `}
45
+ >
46
+ {onClose && (
47
+ <button
48
+ onClick={onClose}
49
+ className="
50
+ w-[14px] h-[14px] flex items-center justify-center
51
+ border border-retro-white bg-retro-gray text-retro-black
52
+ text-[9px] leading-none font-bold
53
+ hover:bg-retro-white active:bg-retro-darkgray
54
+ "
55
+ aria-label="Fermer"
56
+ >
57
+ x
58
+ </button>
59
+ )}
60
+ <span className="flex-1 text-retro-xs font-bold truncate tracking-wide">
61
+ {title}
62
+ </span>
63
+ </div>
64
+
65
+ {/* ── Content area ───────────────────────────────────────── */}
66
+ <div
67
+ className={`
68
+ flex-1 bg-retro-white
69
+ border-t-0
70
+ m-[3px] mt-0
71
+ shadow-retro-well
72
+ ${scrollable ? 'overflow-auto retro-scroll' : 'overflow-hidden'}
73
+ `}
74
+ >
75
+ {children}
76
+ </div>
77
+
78
+ {/* ── Status bar (optional) ──────────────────────────────── */}
79
+ {statusBar !== undefined && (
80
+ <div
81
+ className="
82
+ px-2 py-[2px]
83
+ text-retro-xs text-retro-black
84
+ border-t border-retro-darkgray
85
+ bg-retro-gray
86
+ shadow-retro-well
87
+ mx-[3px] mb-[3px]
88
+ truncate shrink-0
89
+ "
90
+ >
91
+ {statusBar}
92
+ </div>
93
+ )}
94
+ </div>
95
+ )
96
+ }
frontend/src/components/retro/index.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ export { default as RetroWindow } from './RetroWindow'
2
+ export { default as RetroButton } from './RetroButton'
3
+ export { default as RetroMenuBar } from './RetroMenuBar'
4
+ export { default as RetroIcon } from './RetroIcon'
5
+ export { default as RetroCheckbox } from './RetroCheckbox'
6
+ export { default as RetroInput } from './RetroInput'
7
+ export { default as RetroTextarea } from './RetroTextarea'
8
+ export { default as RetroSelect } from './RetroSelect'
9
+ export { default as RetroBadge } from './RetroBadge'
frontend/src/index.css CHANGED
@@ -1,3 +1,126 @@
 
 
 
1
  @tailwind base;
2
  @tailwind components;
3
  @tailwind utilities;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* ── Google Font: IBM Plex Mono (must precede @tailwind) ───────────── */
2
+ @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap');
3
+
4
  @tailwind base;
5
  @tailwind components;
6
  @tailwind utilities;
7
+
8
+ /* ══════════════════════════════════════════════════════════════════════
9
+ IIIF Studio — Retro Design System (global styles)
10
+ Inspired by Xerox Star, Smalltalk-80, GEOS, classic Mac
11
+ ══════════════════════════════════════════════════════════════════════ */
12
+
13
+ /* ── Base reset for retro look ─────────────────────────────────────── */
14
+ @layer base {
15
+ html {
16
+ font-family: 'IBM Plex Mono', 'Courier New', Courier, monospace;
17
+ font-size: 13px;
18
+ line-height: 1.5;
19
+ -webkit-font-smoothing: none;
20
+ -moz-osx-font-smoothing: unset;
21
+ background-color: #c0c0c0;
22
+ color: #000000;
23
+ }
24
+
25
+ body {
26
+ margin: 0;
27
+ padding: 0;
28
+ min-height: 100vh;
29
+ }
30
+
31
+ /* Disable subpixel antialiasing for that crisp pixel look */
32
+ * {
33
+ text-rendering: optimizeSpeed;
34
+ }
35
+
36
+ /* Focus ring: dotted black outline (classic retro) */
37
+ *:focus-visible {
38
+ outline: 1px dotted #000000;
39
+ outline-offset: 1px;
40
+ }
41
+
42
+ /* Selection: classic inverted */
43
+ ::selection {
44
+ background-color: #000080;
45
+ color: #ffffff;
46
+ }
47
+ }
48
+
49
+ /* ── Retro scrollbar (Windows 3.1 style) ───────────────────────────── */
50
+ @layer components {
51
+ /* Webkit-based browsers */
52
+ .retro-scroll::-webkit-scrollbar {
53
+ width: 16px;
54
+ height: 16px;
55
+ }
56
+
57
+ .retro-scroll::-webkit-scrollbar-track {
58
+ background:
59
+ repeating-conic-gradient(
60
+ #c0c0c0 0% 25%, #dfdfdf 0% 50%
61
+ ) 50% / 2px 2px;
62
+ }
63
+
64
+ .retro-scroll::-webkit-scrollbar-thumb {
65
+ background-color: #c0c0c0;
66
+ border: 1px solid #000000;
67
+ box-shadow:
68
+ inset -1px -1px 0 #808080,
69
+ inset 1px 1px 0 #ffffff;
70
+ }
71
+
72
+ .retro-scroll::-webkit-scrollbar-button {
73
+ background-color: #c0c0c0;
74
+ border: 1px solid #000000;
75
+ box-shadow:
76
+ inset -1px -1px 0 #808080,
77
+ inset 1px 1px 0 #ffffff;
78
+ display: block;
79
+ height: 16px;
80
+ width: 16px;
81
+ }
82
+
83
+ .retro-scroll::-webkit-scrollbar-corner {
84
+ background-color: #c0c0c0;
85
+ }
86
+
87
+ /* Firefox */
88
+ .retro-scroll {
89
+ scrollbar-width: auto;
90
+ scrollbar-color: #c0c0c0 #dfdfdf;
91
+ }
92
+ }
93
+
94
+ /* ── Desktop dithered background ───────────────────────────────────── */
95
+ @layer utilities {
96
+ .bg-retro-dither {
97
+ background-color: #c0c0c0;
98
+ background-image: url("data:image/svg+xml,%3Csvg width='4' height='4' viewBox='0 0 4 4' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='1' height='1' fill='%23a0a0a0'/%3E%3Crect x='2' y='2' width='1' height='1' fill='%23a0a0a0'/%3E%3C/svg%3E");
99
+ background-repeat: repeat;
100
+ }
101
+
102
+ .bg-retro-dither-dark {
103
+ background-color: #808080;
104
+ background-image: url("data:image/svg+xml,%3Csvg width='4' height='4' viewBox='0 0 4 4' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='1' height='1' fill='%23606060'/%3E%3Crect x='2' y='2' width='1' height='1' fill='%23606060'/%3E%3C/svg%3E");
105
+ background-repeat: repeat;
106
+ }
107
+
108
+ .bg-retro-dither-light {
109
+ background-color: #dfdfdf;
110
+ background-image: url("data:image/svg+xml,%3Csvg width='4' height='4' viewBox='0 0 4 4' xmlns='http://www.w3.org/2000/svg'%3E%3Crect x='0' y='0' width='1' height='1' fill='%23c8c8c8'/%3E%3Crect x='2' y='2' width='1' height='1' fill='%23c8c8c8'/%3E%3C/svg%3E");
111
+ background-repeat: repeat;
112
+ }
113
+
114
+ /* Horizontal lines pattern */
115
+ .bg-retro-lines {
116
+ background-color: #c0c0c0;
117
+ background-image: repeating-linear-gradient(
118
+ 0deg,
119
+ transparent,
120
+ transparent 1px,
121
+ #a0a0a0 1px,
122
+ #a0a0a0 2px
123
+ );
124
+ background-size: 100% 4px;
125
+ }
126
+ }
frontend/src/pages/RetroDemo.tsx ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { useState } from 'react'
2
+ import {
3
+ RetroWindow,
4
+ RetroButton,
5
+ RetroMenuBar,
6
+ RetroIcon,
7
+ RetroCheckbox,
8
+ RetroInput,
9
+ RetroTextarea,
10
+ RetroSelect,
11
+ RetroBadge,
12
+ } from '../components/retro'
13
+
14
+ export default function RetroDemo({ onBack }: { onBack: () => void }) {
15
+ const [checked1, setChecked1] = useState(true)
16
+ const [checked2, setChecked2] = useState(false)
17
+ const [selectedIcon, setSelectedIcon] = useState<string | null>(null)
18
+ const [inputVal, setInputVal] = useState('')
19
+ const [textareaVal, setTextareaVal] = useState('Explicit liber primus incipit secundus...')
20
+ const [selectVal, setSelectVal] = useState('medieval-illuminated')
21
+
22
+ return (
23
+ <div className="min-h-screen bg-retro-dither">
24
+ {/* ── Menu Bar ─────────────────────────────────────────── */}
25
+ <RetroMenuBar
26
+ items={[
27
+ { label: 'IIIF Studio', onClick: onBack },
28
+ { label: 'Fichier' },
29
+ { label: 'Edition' },
30
+ { label: 'Aide' },
31
+ { label: 'Disabled', disabled: true },
32
+ ]}
33
+ right={
34
+ <span className="text-retro-xs text-retro-darkgray px-2">
35
+ Design System Demo
36
+ </span>
37
+ }
38
+ />
39
+
40
+ <div className="p-4 flex flex-col gap-4 max-w-4xl mx-auto">
41
+ {/* ── Section: Buttons ──────────────────────────────── */}
42
+ <RetroWindow title="RetroButton" statusBar="Boutons avec bevel 3D outset/inset">
43
+ <div className="p-3 flex flex-wrap gap-2 items-center">
44
+ <RetroButton>Normal</RetroButton>
45
+ <RetroButton size="sm">Small</RetroButton>
46
+ <RetroButton pressed>Pressed</RetroButton>
47
+ <RetroButton disabled>Disabled</RetroButton>
48
+ <RetroButton onClick={onBack}>Retour Accueil</RetroButton>
49
+ </div>
50
+ </RetroWindow>
51
+
52
+ {/* ── Section: Window variants ─────────────────────── */}
53
+ <div className="flex gap-4">
54
+ <RetroWindow
55
+ title="Fenetre active"
56
+ onClose={() => {}}
57
+ statusBar="Active window"
58
+ className="flex-1"
59
+ active
60
+ >
61
+ <div className="p-3 text-retro-sm">
62
+ Contenu de la fenetre avec barre de titre noire.
63
+ Le bouton [x] ferme la fenetre.
64
+ </div>
65
+ </RetroWindow>
66
+
67
+ <RetroWindow
68
+ title="Fenetre inactive"
69
+ onClose={() => {}}
70
+ statusBar="Inactive window"
71
+ className="flex-1"
72
+ active={false}
73
+ >
74
+ <div className="p-3 text-retro-sm">
75
+ Une fenetre non focusee a une barre de titre grise.
76
+ </div>
77
+ </RetroWindow>
78
+ </div>
79
+
80
+ {/* ── Section: Scrollable window ───────────────────── */}
81
+ <RetroWindow title="Scrollable Window" scrollable className="h-[150px]">
82
+ <div className="p-3 text-retro-sm">
83
+ {Array.from({ length: 20 }, (_, i) => (
84
+ <p key={i}>Ligne {i + 1} — Lorem ipsum dolor sit amet, consectetur adipiscing elit.</p>
85
+ ))}
86
+ </div>
87
+ </RetroWindow>
88
+
89
+ {/* ── Section: Icons ───────────────────────────────── */}
90
+ <RetroWindow title="RetroIcon — Desktop Icons" statusBar="Cliquez pour selectionner">
91
+ <div className="p-4 flex flex-wrap gap-2 bg-retro-dither-light">
92
+ {[
93
+ { glyph: '\u{1F4DC}', label: 'Beatus' },
94
+ { glyph: '\u{1F4D6}', label: 'Cartulaire' },
95
+ { glyph: '\u{1F4C4}', label: 'Charte' },
96
+ { glyph: '\u{1F5BC}', label: 'Enluminures' },
97
+ { glyph: '\u{1F4BE}', label: 'Export' },
98
+ { glyph: '\u{2699}', label: 'Config' },
99
+ ].map((icon) => (
100
+ <RetroIcon
101
+ key={icon.label}
102
+ glyph={icon.glyph}
103
+ label={icon.label}
104
+ selected={selectedIcon === icon.label}
105
+ onClick={() => setSelectedIcon(icon.label)}
106
+ />
107
+ ))}
108
+ </div>
109
+ </RetroWindow>
110
+
111
+ {/* ── Section: Form controls ───────────────────────── */}
112
+ <RetroWindow title="Form Controls" statusBar="Inputs, textareas, selects, checkboxes">
113
+ <div className="p-3 flex flex-col gap-3">
114
+ <div className="flex gap-4">
115
+ <div className="flex-1">
116
+ <RetroInput
117
+ label="Identifiant du corpus"
118
+ placeholder="ex: beatus-lat8878"
119
+ value={inputVal}
120
+ onChange={(e) => setInputVal(e.target.value)}
121
+ />
122
+ </div>
123
+ <div className="flex-1">
124
+ <RetroSelect
125
+ label="Profil de corpus"
126
+ value={selectVal}
127
+ onChange={(e) => setSelectVal(e.target.value)}
128
+ options={[
129
+ { value: 'medieval-illuminated', label: 'Medieval Illuminated' },
130
+ { value: 'medieval-textual', label: 'Medieval Textual' },
131
+ { value: 'early-modern-print', label: 'Early Modern Print' },
132
+ { value: 'modern-handwritten', label: 'Modern Handwritten' },
133
+ ]}
134
+ />
135
+ </div>
136
+ </div>
137
+
138
+ <RetroTextarea
139
+ label="Transcription diplomatique"
140
+ value={textareaVal}
141
+ onChange={(e) => setTextareaVal(e.target.value)}
142
+ rows={4}
143
+ />
144
+
145
+ <div className="flex gap-4">
146
+ <RetroCheckbox
147
+ label="OCR diplomatique"
148
+ checked={checked1}
149
+ onChange={setChecked1}
150
+ />
151
+ <RetroCheckbox
152
+ label="Iconographie"
153
+ checked={checked2}
154
+ onChange={setChecked2}
155
+ />
156
+ <RetroCheckbox
157
+ label="Disabled"
158
+ checked={false}
159
+ onChange={() => {}}
160
+ disabled
161
+ />
162
+ </div>
163
+ </div>
164
+ </RetroWindow>
165
+
166
+ {/* ── Section: Badges ──────────────────────────────── */}
167
+ <RetroWindow title="RetroBadge — Status Indicators">
168
+ <div className="p-3 flex flex-wrap gap-2 items-center">
169
+ <RetroBadge>default</RetroBadge>
170
+ <RetroBadge variant="success">validated</RetroBadge>
171
+ <RetroBadge variant="warning">needs_review</RetroBadge>
172
+ <RetroBadge variant="error">failed</RetroBadge>
173
+ <RetroBadge variant="info">machine_draft</RetroBadge>
174
+ </div>
175
+ </RetroWindow>
176
+
177
+ {/* ── Section: Typography ──────────────────────────── */}
178
+ <RetroWindow title="Typography & Utilities">
179
+ <div className="p-3 flex flex-col gap-2">
180
+ <p className="text-retro-2xl font-bold">Heading 2XL — IIIF Studio</p>
181
+ <p className="text-retro-xl font-bold">Heading XL — Beatus de Saint-Sever</p>
182
+ <p className="text-retro-lg font-semibold">Heading LG — Folio 13r</p>
183
+ <p className="text-retro-base">Body base — Explicit liber primus incipit secundus</p>
184
+ <p className="text-retro-sm">Body SM — Metadata and labels</p>
185
+ <p className="text-retro-xs text-retro-darkgray">Caption XS — Timestamps and fine print</p>
186
+ <hr className="border-retro-darkgray" />
187
+ <div className="flex gap-4">
188
+ <div className="p-2 bg-retro-dither text-retro-xs">bg-retro-dither</div>
189
+ <div className="p-2 bg-retro-dither-dark text-retro-xs text-retro-white">bg-retro-dither-dark</div>
190
+ <div className="p-2 bg-retro-dither-light text-retro-xs">bg-retro-dither-light</div>
191
+ <div className="p-2 bg-retro-lines text-retro-xs">bg-retro-lines</div>
192
+ </div>
193
+ </div>
194
+ </RetroWindow>
195
+ </div>
196
+ </div>
197
+ )
198
+ }
frontend/tailwind.config.js CHANGED
@@ -2,7 +2,53 @@
2
  export default {
3
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4
  theme: {
5
- extend: {},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  },
7
  plugins: [],
8
  }
 
2
  export default {
3
  content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
4
  theme: {
5
+ extend: {
6
+ /* ── Retro palette ─────────────────────────────────────────── */
7
+ colors: {
8
+ retro: {
9
+ white: '#ffffff',
10
+ light: '#dfdfdf',
11
+ gray: '#c0c0c0',
12
+ darkgray: '#808080',
13
+ black: '#000000',
14
+ /* Accent for selected / active items */
15
+ select: '#000080',
16
+ 'select-text': '#ffffff',
17
+ },
18
+ },
19
+
20
+ /* ── Retro fonts ───────────────────────────────────────────── */
21
+ fontFamily: {
22
+ retro: ['"IBM Plex Mono"', '"Courier New"', 'Courier', 'monospace'],
23
+ mono: ['"IBM Plex Mono"', '"Courier New"', 'Courier', 'monospace'],
24
+ },
25
+
26
+ /* ── Hard drop shadows (no blur) ───────────────────────────── */
27
+ boxShadow: {
28
+ 'retro': '2px 2px 0px 0px #000000',
29
+ 'retro-lg': '3px 3px 0px 0px #000000',
30
+ /* Outset bevel (button at rest) */
31
+ 'retro-outset': 'inset -1px -1px 0 #808080, inset 1px 1px 0 #ffffff',
32
+ /* Inset bevel (button pressed) */
33
+ 'retro-inset': 'inset 1px 1px 0 #808080, inset -1px -1px 0 #ffffff',
34
+ /* Window inner area */
35
+ 'retro-well': 'inset 1px 1px 0 #808080, inset -1px -1px 0 #dfdfdf',
36
+ },
37
+
38
+ /* ── Spacing / sizing tokens ───────────────────────────────── */
39
+ borderWidth: {
40
+ 'retro': '2px',
41
+ },
42
+
43
+ fontSize: {
44
+ 'retro-xs': ['11px', { lineHeight: '16px' }],
45
+ 'retro-sm': ['12px', { lineHeight: '18px' }],
46
+ 'retro-base': ['13px', { lineHeight: '20px' }],
47
+ 'retro-lg': ['15px', { lineHeight: '22px' }],
48
+ 'retro-xl': ['18px', { lineHeight: '26px' }],
49
+ 'retro-2xl': ['22px', { lineHeight: '30px' }],
50
+ },
51
+ },
52
  },
53
  plugins: [],
54
  }