Base64 encodes binary data as text by packing every 3 bytes into 4 characters from a fixed 64-symbol alphabet, so raw bytes can travel through text-only channels like URLs, JSON, and email. In the browser, btoa does the encoding and atob does the decoding. You will implement both from scratch — the bit-packing, the alphabet lookup, and the padding — without calling the built-in btoa / atob or Node's Buffer.
btoa works on a binary string: a string where each character's code is a single byte from 0 to 255. atob takes base64 text and returns that binary string back.
btoa(binaryString) // -> base64 string
atob(base64String) // -> binary string (each char code 0-255)
Both live on one object: atobBtoa = { btoa, atob }.
atobBtoa.btoa('Man'); // 'TWFu' (3 bytes -> 4 chars, no padding)
atobBtoa.btoa('Ma'); // 'TWE=' (2 bytes -> 3 chars + one '=')
atobBtoa.btoa('M'); // 'TQ==' (1 byte -> 2 chars + two '=')
atobBtoa.btoa('Hello, World!'); // 'SGVsbG8sIFdvcmxkIQ=='
atobBtoa.atob('TWFu'); // 'Man'
atobBtoa.atob('TQ=='); // 'M'
atobBtoa.atob(atobBtoa.btoa('any Latin-1 string')); // round-trips to the input
A-Z, then a-z, then 0-9, then + and /.= so its length stays a multiple of 4: one = for a 2-byte tail, two for a 1-byte tail.btoa handles only single bytes (Latin-1). If a character's code is above 255, throw, exactly as the real btoa does.atob(btoa(s)) must return s for every Latin-1 string s, including lengths that need 1 or 2 pad characters.btoa / atob, no Buffer. String.fromCharCode and charCodeAt are allowed as byte primitives.