Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 25 additions & 19 deletions src/components/vue/EasterEggs.vue
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,10 @@ const onKey = (e: KeyboardEvent) => {
word = '';
window.dispatchEvent(new CustomEvent('rnp:replay-hero'));
toast('Replaying decryption…');
} else if (word.endsWith('encrypt')) {
word = '';
window.dispatchEvent(new CustomEvent('rnp:encrypt-hero'));
toast('Encrypting…');
} else if (word.endsWith('rnp') || word.endsWith('pgp')) {
word = '';
hexRain();
Expand Down Expand Up @@ -370,27 +374,29 @@ const onDaveReq = () => playDaveScene();

/** `help()` in the console — list every egg. */
const HELP_TEXT = `Type anywhere on the page (not in inputs):
rnp · pgp → oracle rain (philosophy)
decrypt → replay hero decryption
rnp · pgp → oracle rain (philosophy)
decrypt → replay hero decryption (garbage → text)
encrypt → scramble the hero text (text → garbage → text)

In the fingerprint box (home page):
decrypt → also replays hero decryption
42 → DON'T PANIC
dave → open the pod bay doors (screenwide)
dh → Diffie–Hellman paint exchange
enigma → ³-rotor cipher, live stepping
hal · 9000 → HAL 9000 (screenwide)
librepgp → the open OpenPGP spec
matrix → wake up, Neo
openpgp · 4880 → ASCII-armored message
otp → one-time pad (XOR of your input)
phil → PGP was a munition (1993–1996)
pqc · quantum → harvest now, decrypt later
privacy → cypherpunk's manifesto
ribose → who's behind RNP
snowden · nsa → classified
thunderbird → powered by Thunderbird's wings
rnp · pgp → also triggers oracle rain
decrypt → same — replay hero decryption
encrypt → same — scramble the hero text
42 → DON'T PANIC
dave → open the pod bay doors (screenwide)
dh → Diffie–Hellman paint exchange
enigma → ³-rotor cipher, live stepping
hal · 9000 → HAL 9000 (screenwide)
librepgp → the open OpenPGP spec
matrix → wake up, Neo
openpgp · 4880 → ASCII-armored message
otp → one-time pad (XOR of your input)
phil → PGP was a munition (1993–1996)
pqc · quantum → harvest now, decrypt later
privacy → cypherpunk's manifesto
ribose → who's behind RNP
snowden · nsa → classified
thunderbird → powered by Thunderbird's wings
rnp · pgp → also triggers oracle rain

Type help() or rnp.help() to reprint.`;

Expand Down
22 changes: 14 additions & 8 deletions src/components/vue/FingerprintPlayground.vue
Original file line number Diff line number Diff line change
Expand Up @@ -209,15 +209,21 @@ watch(input, (val) => {
}
});

/* Typing 'decrypt' in the input also replays the hero decryption animation. */
let lastDecryptInput = '';
/* Typing 'decrypt' or 'encrypt' in the input drives the hero animation:
decrypt → garbage settles to readable; encrypt → readable scrambles
(then auto-settles back so the page isn't stuck). Substring match so
either word fires whether appended to the default seed, typed alone,
or part of a longer word. Decrypt wins if both are present. */
let lastHeroInput = '';
watch(input, (val) => {
const matches = /\bdecrypt\b/i.test(val);
if (matches && val !== lastDecryptInput) {
lastDecryptInput = val;
window.dispatchEvent(new CustomEvent('rnp:replay-hero'));
} else if (!matches) {
lastDecryptInput = '';
let evt: 'rnp:replay-hero' | 'rnp:encrypt-hero' | null = null;
if (/decrypt/i.test(val)) evt = 'rnp:replay-hero';
else if (/encrypt/i.test(val)) evt = 'rnp:encrypt-hero';
if (evt && val !== lastHeroInput) {
lastHeroInput = val;
window.dispatchEvent(new CustomEvent(evt));
} else if (!evt) {
lastHeroInput = '';
}
});

Expand Down
34 changes: 32 additions & 2 deletions src/components/vue/HeroDecrypt.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const GLYPHS = '0123456789ABCDEF$#@%&*+=~';
const display = ref(props.text);
let raf = 0;

const isPunct = (ch: string) => ch === ' ' || ch === '.' || ch === ',';

const run = () => {
cancelAnimationFrame(raf);
const text = props.text;
Expand All @@ -21,7 +23,7 @@ const run = () => {
let out = text.slice(0, settled);
for (let i = settled; i < total; i++) {
const ch = text[i];
out += ch === ' ' || ch === '.' || ch === ',' ? ch : GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
out += isPunct(ch) ? ch : GLYPHS[Math.floor(Math.random() * GLYPHS.length)];
}
display.value = out;
if (t < 1) raf = requestAnimationFrame(tick);
Expand All @@ -30,18 +32,46 @@ const run = () => {
raf = requestAnimationFrame(tick);
};

/** Encrypt: scramble wave sweeps L→R, leaving the text fully mangled.
* Stays mangled — type 'decrypt' (or refresh) to restore. */
const runEncrypt = () => {
cancelAnimationFrame(raf);
const text = props.text;
const total = text.length;
const duration = 1400;
const start = performance.now();

const tick = (now: number) => {
const t = Math.min(1, (now - start) / duration);
const edge = Math.floor(total * t * t * (3 - 2 * t));
let out = '';
for (let i = 0; i < total; i++) {
const ch = text[i];
out += i < edge && !isPunct(ch)
? GLYPHS[Math.floor(Math.random() * GLYPHS.length)]
: ch;
}
display.value = out;
if (t < 1) raf = requestAnimationFrame(tick);
// else: final state is fully scrambled — leave it
};
raf = requestAnimationFrame(tick);
};

const reducedMotion = () => matchMedia('(prefers-reduced-motion: reduce)').matches;

onMounted(() => {
// Passive auto-play respects reduced-motion…
if (!reducedMotion()) run();
// …but a user explicitly typing 'decrypt' always gets the replay.
// …but a user explicitly typing 'decrypt' or 'encrypt' always gets the effect.
window.addEventListener('rnp:replay-hero', run);
window.addEventListener('rnp:encrypt-hero', runEncrypt);
});

onUnmounted(() => {
cancelAnimationFrame(raf);
window.removeEventListener('rnp:replay-hero', run);
window.removeEventListener('rnp:encrypt-hero', runEncrypt);
});
</script>

Expand Down
Loading