Skip to content

Commit 9e710b7

Browse files
committed
docs: add landing page for GitHub Pages
- Three clipboard slots showcase with menu bar popover mockup - Animated How-it-works with Slot B as example slot - Demo, comparison vs history managers, privacy claims - Homebrew install with copy-to-clipboard - Tweaks panel hidden by default (enable with ?tweaks=1) - Includes .nojekyll to bypass Jekyll processing
1 parent 34dff70 commit 9e710b7

12 files changed

Lines changed: 1902 additions & 0 deletions

File tree

docs/.nojekyll

Whitespace-only changes.

docs/app.jsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/* App entry — composes all sections + Tweaks */
2+
3+
const { useTweaks } = window;
4+
5+
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
6+
"theme": "dark",
7+
"accent": "violet-blue",
8+
"headlineVariant": "zero-history",
9+
"showShortcutCards": true
10+
}/*EDITMODE-END*/;
11+
12+
const ACCENT_PRESETS = {
13+
"violet-blue": { violet: "#6B5BFF", blue: "#2D6CFF", teal: "#2DD4BF", a: "#8B7BFF", b: "#4D8AFF", c: "#2DD4BF" },
14+
"macos-blue": { violet: "#0A84FF", blue: "#0A84FF", teal: "#30D158", a: "#0A84FF", b: "#5E5CE6", c: "#30D158" },
15+
"sunset": { violet: "#FF6B6B", blue: "#FF9F0A", teal: "#FFD60A", a: "#FF6B6B", b: "#FF9F0A", c: "#FFD60A" },
16+
"monochrome": { violet: "#FFFFFF", blue: "#A8A8B3", teal: "#6B6B78", a: "#FFFFFF", b: "#A8A8B3", c: "#6B6B78" },
17+
};
18+
19+
const HEADLINES = {
20+
"zero-history": (<>Three clipboards.<br/><span className="headline-em">Zero history.</span></>),
21+
"where-not-when": (<><span className="headline-em">Where</span>, not when.</>),
22+
"by-the-letter": (<>Stash by the letter.<br/><span className="headline-em">A, B, or C.</span></>),
23+
};
24+
25+
function applyAccent(name) {
26+
const a = ACCENT_PRESETS[name] || ACCENT_PRESETS["violet-blue"];
27+
const r = document.documentElement;
28+
r.style.setProperty('--accent-violet', a.violet);
29+
r.style.setProperty('--accent-blue', a.blue);
30+
r.style.setProperty('--accent-teal', a.teal);
31+
r.style.setProperty('--slot-a', a.a);
32+
r.style.setProperty('--slot-b', a.b);
33+
r.style.setProperty('--slot-c', a.c);
34+
}
35+
36+
const App = () => {
37+
const [tweaks, setTweak] = useTweaks(TWEAK_DEFAULTS);
38+
39+
React.useEffect(() => {
40+
document.documentElement.setAttribute('data-theme', tweaks.theme);
41+
}, [tweaks.theme]);
42+
43+
React.useEffect(() => {
44+
applyAccent(tweaks.accent);
45+
}, [tweaks.accent]);
46+
47+
// Override hero headline at runtime
48+
React.useEffect(() => {
49+
const el = document.querySelector('.hero-headline');
50+
if (!el) return;
51+
// No-op: handled by passing variant down via context-less prop is overkill.
52+
// Instead, mutate after mount:
53+
}, [tweaks.headlineVariant]);
54+
55+
return (
56+
<>
57+
<div id="top"/>
58+
<Hero headlineVariant={tweaks.headlineVariant} showShortcuts={tweaks.showShortcutCards}/>
59+
<Insight/>
60+
<HowItWorks/>
61+
<Features/>
62+
<Demo/>
63+
<Compare/>
64+
<Privacy/>
65+
<Install/>
66+
<Footer/>
67+
{typeof window !== 'undefined' && new URLSearchParams(window.location.search).has('tweaks') && (
68+
<DualClipTweaks tweaks={tweaks} setTweak={setTweak}/>
69+
)}
70+
</>
71+
);
72+
};
73+
74+
const DualClipTweaks = ({ tweaks, setTweak }) => {
75+
const { TweaksPanel, TweakSection, TweakRadio, TweakSelect, TweakToggle } = window;
76+
if (!TweaksPanel) return null;
77+
return (
78+
<TweaksPanel title="Tweaks">
79+
<TweakSection title="Appearance">
80+
<TweakRadio
81+
label="Theme"
82+
value={tweaks.theme}
83+
onChange={v => setTweak('theme', v)}
84+
options={[{ value: 'dark', label: 'Dark' }, { value: 'light', label: 'Light' }]}
85+
/>
86+
<TweakSelect
87+
label="Accent palette"
88+
value={tweaks.accent}
89+
onChange={v => setTweak('accent', v)}
90+
options={[
91+
{ value: 'violet-blue', label: 'Violet → Blue (default)' },
92+
{ value: 'macos-blue', label: 'macOS Blue' },
93+
{ value: 'sunset', label: 'Sunset' },
94+
{ value: 'monochrome', label: 'Monochrome' },
95+
]}
96+
/>
97+
</TweakSection>
98+
<TweakSection title="Hero">
99+
<TweakSelect
100+
label="Headline"
101+
value={tweaks.headlineVariant}
102+
onChange={v => setTweak('headlineVariant', v)}
103+
options={[
104+
{ value: 'zero-history', label: 'Three clipboards. Zero history.' },
105+
{ value: 'where-not-when', label: 'Where, not when.' },
106+
{ value: 'by-the-letter', label: 'Stash by the letter.' },
107+
]}
108+
/>
109+
<TweakToggle
110+
label="Show shortcut cards"
111+
checked={tweaks.showShortcutCards}
112+
onChange={v => setTweak('showShortcutCards', v)}
113+
/>
114+
</TweakSection>
115+
</TweaksPanel>
116+
);
117+
};
118+
119+
Object.assign(window, { App, HEADLINES });
120+
121+
ReactDOM.createRoot(document.getElementById('root')).render(<App/>);

docs/assets/demo-image.gif

5.99 MB
Loading

docs/assets/demo-text.gif

1.65 MB
Loading

docs/assets/icon.png

52.5 KB
Loading

docs/components/hero.jsx

Lines changed: 189 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,189 @@
1+
/* Hero — the menu-bar popover mockup is the showpiece */
2+
3+
const MenuBarPopover = ({ active = 'A' }) => {
4+
const slots = [
5+
{ id: 'A', shortcut: ['⌘', 'C'], paste: ['⌘', 'V'], preview: 'github.com/RAKKUNN/DualClip', kind: 'link' },
6+
{ id: 'B', shortcut: ['⌥', '⌘', 'C'], paste: ['⌥', '⌘', 'V'], preview: 'const handlePaste = async () => {\n const text = await navigator.clipboard.readText()', kind: 'code' },
7+
{ id: 'C', shortcut: ['⌃', '⌘', 'C'], paste: ['⌃', '⌘', 'V'], preview: '— image —', kind: 'image' },
8+
];
9+
return (
10+
<div className="popover" role="dialog" aria-label="DualClip menu bar popover">
11+
<div className="popover-header">
12+
<div className="popover-header-left">
13+
<div className="popover-app-dot" />
14+
<span className="popover-app-name">DualClip</span>
15+
</div>
16+
<div className="popover-status mono">RAM-only · idle</div>
17+
</div>
18+
<div className="popover-slots">
19+
{slots.map(s => (
20+
<div key={s.id} className={`slot-row ${active === s.id ? 'is-active' : ''}`}>
21+
<div className="slot-row-left">
22+
<span className={`slot-chip slot-${s.id}`}>{s.id}</span>
23+
<div className="slot-content">
24+
<div className="slot-label mono">Slot {s.id}</div>
25+
<div className={`slot-preview ${s.kind === 'image' ? 'slot-preview-image' : ''}`}>
26+
{s.kind === 'image' ? (
27+
<div className="slot-image-thumb">
28+
<span className="slot-image-bars" />
29+
<span className="mono">PNG · 1.2 MB</span>
30+
</div>
31+
) : (
32+
<span className="mono">{s.preview}</span>
33+
)}
34+
</div>
35+
</div>
36+
</div>
37+
<div className="slot-row-right">
38+
<Shortcut keys={s.paste} sm />
39+
</div>
40+
</div>
41+
))}
42+
</div>
43+
<div className="popover-footer">
44+
<div className="footer-hint mono">
45+
<span className="hint-dot" /> Press <Shortcut keys={['⌥','⌘','C']} sm/> to stash · <Shortcut keys={['⌥','⌘','V']} sm/> to recall
46+
</div>
47+
</div>
48+
</div>
49+
);
50+
};
51+
52+
const HEADLINE_VARIANTS = {
53+
"zero-history": <>Three clipboards.<br/><span className="headline-em">Zero history.</span></>,
54+
"where-not-when": <><span className="headline-em">Where</span>, not when.</>,
55+
"by-the-letter": <>Stash by the letter.<br/><span className="headline-em">A, B, or C.</span></>,
56+
};
57+
58+
const Hero = ({ headlineVariant = 'zero-history', showShortcuts = true }) => {
59+
const [activeSlot, setActiveSlot] = React.useState('A');
60+
React.useEffect(() => {
61+
const i = setInterval(() => {
62+
setActiveSlot(s => (s === 'A' ? 'B' : s === 'B' ? 'C' : 'A'));
63+
}, 2400);
64+
return () => clearInterval(i);
65+
}, []);
66+
67+
return (
68+
<header className="hero" data-screen-label="Hero">
69+
<div className="spotlight" />
70+
<div className="grain" />
71+
<Nav />
72+
<div className="container hero-grid">
73+
<div className="hero-copy">
74+
<div className="eyebrow"><span className="dot"/> macOS · Menu bar</div>
75+
<h1 className="hero-headline">
76+
{HEADLINE_VARIANTS[headlineVariant] || HEADLINE_VARIANTS["zero-history"]}
77+
</h1>
78+
<p className="lede hero-lede">
79+
DualClip gives you three dedicated clipboard slots — A, B, C — with their own
80+
keyboard shortcuts. Decide where things go. Recall them instantly. Nothing ever
81+
touches the disk.
82+
</p>
83+
84+
<div className="hero-install">
85+
<div className="code-block hero-code">
86+
<span><span className="prompt">$</span><span className="mono">brew install RAKKUNN/tap/dualclip</span></span>
87+
<CopyButton text="brew install RAKKUNN/tap/dualclip" />
88+
</div>
89+
<div className="hero-cta-row">
90+
<a className="btn btn-primary" href="https://github.com/RAKKUNN/DualClip/releases" target="_blank" rel="noreferrer">
91+
<Icon.Download size={14}/> Download for macOS
92+
</a>
93+
<a className="btn btn-ghost" href="https://github.com/RAKKUNN/DualClip" target="_blank" rel="noreferrer">
94+
<Icon.Github size={14}/> View source
95+
</a>
96+
</div>
97+
</div>
98+
99+
<div className="hero-meta mono">
100+
<span><Icon.Lock size={12}/> MIT licensed</span>
101+
<span className="meta-dot"/>
102+
<span>macOS 13+</span>
103+
<span className="meta-dot"/>
104+
<span>Apple Silicon</span>
105+
</div>
106+
</div>
107+
108+
<div className="hero-visual" aria-hidden="false">
109+
<div className="hero-menubar">
110+
<div className="menubar-bar">
111+
<span className="menubar-apple">
112+
<svg width="13" height="14" viewBox="0 0 17 20" fill="currentColor"><path d="M13.97 10.6c-.02-2.43 1.99-3.6 2.08-3.65-1.13-1.66-2.9-1.89-3.53-1.91-1.5-.15-2.93.88-3.69.88-.77 0-1.94-.86-3.19-.84-1.64.02-3.16.96-4 2.42-1.71 2.97-.44 7.36 1.23 9.78.81 1.18 1.78 2.5 3.04 2.46 1.22-.05 1.69-.79 3.16-.79 1.47 0 1.89.79 3.18.77 1.31-.02 2.14-1.2 2.95-2.39.93-1.37 1.32-2.71 1.34-2.78-.03-.01-2.56-.98-2.58-3.95M11.5 3.41c.68-.82 1.13-1.96 1.01-3.1-.97.04-2.16.65-2.86 1.47-.62.72-1.17 1.88-1.02 3 1.08.08 2.19-.55 2.87-1.37"/></svg>
113+
</span>
114+
<span className="menubar-text">File</span>
115+
<span className="menubar-text">Edit</span>
116+
<span className="menubar-text">View</span>
117+
<span className="menubar-spacer" />
118+
<span className="menubar-icon-group">
119+
<span className="menubar-icon menubar-icon--app" aria-label="DualClip menu bar icon">
120+
<span className="menubar-pill">
121+
<span className="menubar-pill-half" />
122+
</span>
123+
</span>
124+
<span className="menubar-icon">
125+
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
126+
</span>
127+
<span className="menubar-icon mono menubar-time">10:12</span>
128+
</span>
129+
</div>
130+
<div className="menubar-arrow" />
131+
<MenuBarPopover active={activeSlot} />
132+
</div>
133+
{showShortcuts && <div className="hero-shortcuts">
134+
<div className="shortcut-card">
135+
<div className="shortcut-card-label mono">copy →</div>
136+
<div className="shortcut-card-keys"><Shortcut keys={['⌘','C']}/></div>
137+
<div className="shortcut-card-slot"><span className="slot-chip slot-A">A</span></div>
138+
</div>
139+
<div className="shortcut-card">
140+
<div className="shortcut-card-label mono">copy →</div>
141+
<div className="shortcut-card-keys"><Shortcut keys={['⌥','⌘','C']}/></div>
142+
<div className="shortcut-card-slot"><span className="slot-chip slot-B">B</span></div>
143+
</div>
144+
<div className="shortcut-card">
145+
<div className="shortcut-card-label mono">copy →</div>
146+
<div className="shortcut-card-keys"><Shortcut keys={['⌃','⌘','C']}/></div>
147+
<div className="shortcut-card-slot"><span className="slot-chip slot-C">C</span></div>
148+
</div>
149+
</div>}
150+
</div>
151+
</div>
152+
</header>
153+
);
154+
};
155+
156+
const Nav = () => {
157+
const [theme, setTheme] = React.useState('dark');
158+
React.useEffect(() => {
159+
document.documentElement.setAttribute('data-theme', theme);
160+
}, [theme]);
161+
return (
162+
<nav className="nav container">
163+
<a className="nav-brand" href="#top">
164+
<img src="assets/icon.png" alt="DualClip" width="28" height="28" className="nav-icon"/>
165+
<span className="nav-name">DualClip</span>
166+
</a>
167+
<div className="nav-links">
168+
<a href="#how">How it works</a>
169+
<a href="#features">Features</a>
170+
<a href="#compare">vs History</a>
171+
<a href="#install">Install</a>
172+
</div>
173+
<div className="nav-actions">
174+
<button
175+
className="nav-theme"
176+
onClick={() => setTheme(t => t === 'dark' ? 'light' : 'dark')}
177+
aria-label="Toggle theme"
178+
>
179+
{theme === 'dark' ? <Icon.Sun size={14}/> : <Icon.Moon size={14}/>}
180+
</button>
181+
<a className="nav-github" href="https://github.com/RAKKUNN/DualClip" target="_blank" rel="noreferrer">
182+
<Icon.Github size={16}/>
183+
</a>
184+
</div>
185+
</nav>
186+
);
187+
};
188+
189+
Object.assign(window, { Hero, Nav, MenuBarPopover });

0 commit comments

Comments
 (0)