// While² — macOS variants with glyph-scramble navigation
// 1. Menu-bar popover (interactive) 2. Full terminal window (interactive)
const _MAC_INK = '#e8e6df';
// Reuse glyph-scramble hook from while2-scramble.jsx
// (Scram component is exposed there but we redefine a slim version to avoid load-order coupling.)
const _MAC_POOL = '!<>-_\\/[]{}—=+*^?#·░▒▓█'.split('');
function _useMacScramble(target, key, dur = 520) {
const [out, setOut] = React.useState(target);
React.useEffect(() => {
if (typeof target !== 'string') { setOut(target); return; }
const start = performance.now();
const reveals = Array.from({ length: target.length }, (_, i) => {
const wave = (i / Math.max(1, target.length)) * 0.55;
const jitter = Math.random() * 0.45;
return (wave + jitter) * dur;
});
let raf;
const tick = () => {
const t = performance.now() - start;
let s = '';
for (let i = 0; i < target.length; i++) {
const ch = target[i];
if (ch === ' ' || ch === '\n') { s += ch; continue; }
s += t >= reveals[i] ? ch : _MAC_POOL[Math.floor(Math.random() * _MAC_POOL.length)];
}
setOut(s);
if (t < dur) raf = requestAnimationFrame(tick);
else setOut(target);
};
raf = requestAnimationFrame(tick);
return () => cancelAnimationFrame(raf);
}, [key, target, dur]);
return out;
}
const _S = ({ text, k, dur, style }) => {
const out = _useMacScramble(text, k, dur);
return {out};
};
// Desktop wallpaper
function _Desktop({ children, w = 1280, h = 800 }) {
return (
);
}
function _MenuBar({ activeIcon = false }) {
return (
While²
File
Edit
View
Help
↑↓
◐
📶
🔋
w²
Mon 10:24
);
}
function _PopoverArrow({ left }) {
return (
);
}
// ── Popover bodies (compact, scramble-aware) ──
function _PopToday({ k }) {
const today = [
{ name: 'burnable trash', rule: 'weekly · tue' },
{ name: 'stretch', rule: 'daily' },
{ name: 'haircut', rule: 'every 4w · overdue', warn: true },
];
const upcoming = [
{ t: '04/30', name: 'non-burnable trash', rule: 'biweekly' },
{ t: '05/03', name: 'pay rent', rule: 'monthly · 3' },
{ t: '05/05', name: 'restock shampoo', rule: 'every 6w' },
];
return (
<_S text="// today" k={k} />
{today.map((t, i) => (
<_S text={t.name} k={k} dur={520 + i * 60} />
<_S text={t.rule} k={k} dur={520 + i * 60}
style={{ fontSize: 9.5, color: t.warn ? W2.cyan : 'rgba(232,230,223,0.45)' }} />
))}
<_S text="// upcoming" k={k} />
{upcoming.map((t, i) => (
<_S text={t.t} k={k} dur={580 + i * 60} style={{ color: 'rgba(232,230,223,0.55)' }} />
<_S text={t.name} k={k} dur={580 + i * 60} />
<_S text={t.rule} k={k} dur={580 + i * 60}
style={{ fontSize: 9.5, color: 'rgba(232,230,223,0.45)' }} />
))}
);
}
function _PopNew({ k }) {
const Field = ({ kk, v, hint }) => (
<_S text={kk} k={k} />
<_S text={v} k={k} dur={620} />
{hint && (
<_S text={hint} k={k} />
)}
);
return (
?{' '}
<_S text="done 04/28 → next 05/26 → 06/23 …" k={k} dur={680} />
);
}
function _PopConfig({ k }) {
const rows = [
['operator', 'user.local'],
['timezone', 'asia/tokyo'],
['notify_at', '09:00'],
['theme', 'dark.cyan'],
['icloud', 'on'],
['last_synced', '2 min ago'],
];
return (
{rows.map(([kk, v], i) => (
<_S text={kk} k={k} dur={500 + i * 30} />
=
<_S text={v} k={k} dur={520 + i * 30} />
))}
);
}
function _PopoverInteractive() {
const [screen, setScreen] = React.useState('today');
const [tick, setTick] = React.useState(0);
const goto = (next) => {
if (next === screen) return;
setScreen(next);
setTick(t => t + 1);
};
const k = `${screen}-${tick}`;
const promptText = screen === 'today' ? 'today' : screen === 'new' ? 'new_' : 'config';
return (
> while²{' '}
<_S text={promptText} k={k} dur={420} />
_
{screen === 'today' && <_PopToday k={k} />}
{screen === 'new' && <_PopNew k={k} />}
{screen === 'config' && <_PopConfig k={k} />}
{[
['today', 'today'],
['new', '+ new_'],
['config', 'config'],
].map(([id, label]) => (
goto(id)} style={{
cursor: 'pointer', userSelect: 'none',
color: screen === id ? W2.cyan : 'rgba(232,230,223,0.55)',
}}>
{screen === id ? > : ' '}
{label}
))}
);
}
// ── Variant 1 — Menu bar popover ──
function MacMenuBar() {
return (
<_Desktop w={1280} h={800}>
<_MenuBar activeIcon />
<_PopoverArrow left={300} />
<_PopoverInteractive />
{[1,2,3,4,5,6,7].map(i => (
))}
);
}
// ── Terminal window bodies (scramble-aware) ──
function _TermToday({ k }) {
const today = [
{ name: 'burnable trash', rule: 'weekly · tue' },
{ name: 'stretch', rule: 'daily' },
{ name: 'haircut', rule: 'every 4w · overdue', warn: true },
];
const upcoming = [
{ t: '04/30', name: 'non-burnable trash', rule: 'biweekly' },
{ t: '05/03', name: 'pay rent', rule: 'monthly · 3' },
{ t: '05/05', name: 'restock shampoo', rule: 'every 6w' },
{ t: '05/06', name: 'book checkup', rule: 'every 12m' },
];
return (
<>
<_S text="// today" k={k} />
{today.map((t, i) => (
[{i + 1}]
<_S text={t.name} k={k} dur={520 + i * 60} />
<_S text={t.rule} k={k} dur={520 + i * 60}
style={{ fontSize: 10, color: t.warn ? W2.cyan : 'rgba(232,230,223,0.45)' }} />
))}
<_S text="// upcoming" k={k} />
{upcoming.map((t, i) => (
<_S text={t.t} k={k} dur={580 + i * 50} style={{ color: 'rgba(232,230,223,0.55)' }} />
<_S text={t.name} k={k} dur={580 + i * 50} />
<_S text={t.rule} k={k} dur={580 + i * 50}
style={{ fontSize: 10, color: 'rgba(232,230,223,0.45)' }} />
))}
>
);
}
function _TermNew({ k }) {
const Field = ({ kk, v, hint }) => (
<_S text={kk} k={k} />
<_S text={v} k={k} dur={620} />
{hint && (
<_S text={hint} k={k} />
)}
);
return (
?{' '}
<_S text="done 04/28 → next 05/26 → 06/23 …" k={k} dur={680} />
);
}
function _TermConfig({ k }) {
const rows = [
['operator', 'user.local'],
['timezone', 'asia/tokyo'],
['week_starts', 'mon'],
[null, null],
['notify_at', '09:00'],
['recap_at', '21:00'],
['overdue_ping', 'on'],
[null, null],
['theme', 'dark.cyan'],
['reduce_motion', 'off'],
[null, null],
['icloud', 'on'],
['last_synced', '2 min ago'],
];
return (
{rows.map(([kk, v], i) => kk === null ? (
) : (
<_S text={kk} k={k} dur={500 + i * 25} />
=
<_S text={v} k={k} dur={520 + i * 25} />
))}
);
}
// ── Variant 2 — Full terminal window (interactive) ──
function MacTerminal() {
const [screen, setScreen] = React.useState('today');
const [tick, setTick] = React.useState(0);
const goto = (next) => {
if (next === screen) return;
setScreen(next);
setTick(t => t + 1);
};
const k = `${screen}-${tick}`;
const cmd = screen === 'today' ? 'ls today' : screen === 'new' ? 'new --interactive' : 'config';
return (
<_Desktop w={1280} h={800}>
<_MenuBar />
{/* title bar */}
while²@local — {screen} — 80×24
{/* body */}
while²@local
~
$
<_S text={cmd} k={k} dur={420} />
{screen === 'today' && <_TermToday k={k} />}
{screen === 'new' && <_TermNew k={k} />}
{screen === 'config' && <_TermConfig k={k} />}
while²@local
~
$
_
{/* footer nav */}
{[
['today', 'today'],
['new', 'new_'],
['config', 'config'],
].map(([id, label]) => (
goto(id)} style={{
cursor: 'pointer', userSelect: 'none',
color: screen === id ? W2.cyan : 'rgba(232,230,223,0.55)',
}}>
{screen === id ? > : ' '}
{label}
))}
iCloud · synced 2m ago
);
}
window.MacMenuBar = MacMenuBar;
window.MacTerminal = MacTerminal;
window._PopToday = _PopToday;
window._PopNew = _PopNew;
window._PopConfig = _PopConfig;